Allez tiens, un bon post bien geek aujourd'hui. Franchement si vous n'êtes
pas fan de combinatoire et de python ne perdez pas trop de temps à lire… En
échange je vous mets le clip de Djiin. (*)
Alors un midi comme ça on discutait des schémas de déverrouillage pour
smartphones. Ces glyphes qu'on trace du doigt pour dire à notre
smartphone que c'est bien nous. On s'est demandé combien
de combinaisons ça pouvait bien faire. L’intuition ambiante oscillait entre pas
tant que ça et pas bézef.
Posons un peu mieux le problème
On a neuf touches, numérotées de un à neuf. Attends je vérifie... ouais
c'est ça, neuf. On fixe la longueur max du code à huit mouvements, j'expleek
pourquoi plus loin. La longueur minimum c'est zéro mouvement. Zéro mouvement ça
correspond au gros fainéant qui appuie juste sur une touche et fait même pas
glisser son doigt. J'ai honte pour lui.
D’abord le cas simple
Le cas le plus facile à calculer c'est quand il n'y a pas de contrainte particulière
: on peut aller d'une touche vers n'importe quelle autre touche, on peut
repasser plusieurs fois par une même touche. Ben oui c'est simple parce qu’on
choisit une touche de départ (neufs possibilités) et de là on peut glisser vers
n'importe quelle autre touche. À chaque étape il y a donc huit mouvements
possibles. En tout il existe 9 codes de longueur zéro, plus 9 x 8 codes de
longueur un, plus 9 x 8 x 8 codes de longueur deux, etc. Jusqu'à la longueur
maximale fixée à huit mouvements, j'expleek pourquoi plus loin, je l'ai déjà
dit. Je nous fais les multiplication et les sommes gratuitement et ça donne cent soixante-douze million cinq cent soixante-cinq
mille six cent quarante combinaisons. De quoi voir venir, surtout si le
téléphone se bloque genre trente secondes au bout de cinq essais. Faut près
d'un siècle pour tout essayer, et encore, en dormant pas beaucoup.
Maintenant le cas intéressant
En prateek tous les mouvements ne sont pas nécessairement autorisés. On
s'est demandé combien il y avait de combinaisons lorsque premièrement on
interdit de réutiliser une touche et deuxièmement on n'autorise que les
mouvements vers des touches contiguës (**). Voir croquis.
On peut remarquer que sous ces conditions la longueur maximale d'un code
est huit mouvements. En effet, au bout de huit mouvements, toutes les touches
ont été visitées une fois chacune. Voilà, je l'avais bien dit que j’expleekerai
plus loin.
Alors je vous donne direct le résultat. Des codes comme ça il n'y en a que :
dix mille trois cent cinq. Soit à
peine plus que des PIN codes de carte bancaire à quatre chiffres. C'est peu :
en même pas une semaine on peut tout essayer, et encore, en dormant bien.
Marrant, à l'intuition j'aurais cru qu'il y avait encore moins de combinaisons
que ça. À la louche j’aurais dit quelques centaines pas plus. Je me foutais donc
le doigt dans l’œil sur deux ordres de grandeurs. Ça va, j’ai fait pire…
Un bout de python pour calculer ça
Le calcul n'est pas aussi direct que dans le cas simple. C’est même carrément
coton. Pas moyen de trouver une formule explicite. Du coup j'ai fait un bout de
python pour énumérer tout ça. Je vous le copie-colle ici et j'expleek après.
Version texte à la fin du post (si quelqu'un sait comment utiliser pygmentize dans blogspot, je veux bien un coup de main) |
Quelques remarques sur le bout de python
1) Je n'ai pas mis le programme sur github. Ouais, j'aime bien à
l'ancienne, copié-collé crade direct dans le post.
2) Je n’ai pas mis non plus de licence particulière. En fait c'est un
Metallurgeeciel: tu peux en faire ce que tu veux mais je veux bien qu'on aille
se boire une bière.
3) Les variables et les noms de fonctions font tou.t.e.s exactement quatre
caractères. Un vieux T.O.C. ça m’a pris en classe de cinquième ça finira bien
par passer. Dans le même esprit les commentaires sont tous obsessionnellement
alignés au caractère près. Et le score sous pylint est de 10.00/10, comme quoi
je respecte la PEP8 au pied de la lettre.
4) Les remarques 1) 2) et 3) n'ont rien à voir avec l'algorithme et le
sujet du post. La remarque 4) non plus.
5) La fonction "code(path, many)" lignes 9-15 prend le début d'un
code en argument et compte les suites possibles. Pour ça elle s'appelle récursivement
autant de fois qu'il y a de mouvements possibles. Le programme connait par cœur
les mouvements possibles à partir d'une touche. C'est à ça que sert le
dictionnaire "MOVE" lignes 3-7. On est confiant que la récursion
terminera car à chaque appel une nouvelle touche est visitée et des touches y
en a que neuf (attends je vérifie... ouais c'est ça, neuf)
6) Pour faire moins de calculs, j'utilise le fait qu'il n'y a que trois
sortes de codes : ceux qui commencent par un coin (touches 1, 3, 7, 9), ceux
qui commencent par un milieu (touches 2, 4, 6, 8) ou ceux qui commencent pile
par le centre (touche 5).
7) Du coup, je calcule le nombre de codes possibles en partant du coin 1 et
le nombre de codes possibles en partant du milieu 2. Ces nombres là je les
multiplie par quatre parce qu'il y a quatre coins et quatre milieux. Reste à
ajouter le nombre de codes qui commencent par le centre.
8) On peut aussi calculer tous les chemins sans exploiter les symétries. C’est
ce que fait la ligne 18 en commentaire. Truc de ouf, on trouve le même
résultat.
Conclusion
Si le téléphone prend ton empreinte digitale et analyse ton ADN pendant que tu traces le code, et ben le nombre de combinaisons on s'en fout un peu.
Le programme à copier-coller
'''Pattern locks on a 9 digits keypad with just vert/horiz/diag moves and no digit reuse.'''
MOVE = {
'1':'254', '3':'652', '9':'856', '7':'458', # Possible moves from corner
'2':'36541', '6':'98523', '8':'74569', '4':'12587', # Possible moves from middle
'5':'12369874' # Possible moves from center
}
def code(path, many): # Recursively research paths
'''Count the codes starting with some path.'''
#print(path) # Un-comment for enumeration
for move in MOVE[path[-1]]: # Extend path with netx move
if not move in path: # Bypass already used digits
many = code(path+move, many+1) # Count one path and recurse
return many # Return the number of paths
print(code('1', 1)*4+code('2', 1)*4+code('5', 1), __doc__) # 4 symmetries except center
#print(sum([code(base, 1) for base in MOVE.keys()]), __doc__) # Alternate counting formula
(*) Djiin avec deux "i" comme dans Giin Tooneek (ta race).
(**) Contiguës ça s'écrit exactement comme Noël, sauf tu mets un C au début
comme dans "Christmas".
Aucun commentaire:
Enregistrer un commentaire