Différences

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
travaux:game_of_life [2010/11/16 18:54]
nico
travaux:game_of_life [2010/11/16 20:49] (Version actuelle)
nico
Ligne 14: Ligne 14:
 Les voisines d'une cellule sont les huit cellules l'​entourant : N, S, E, O, NE, NO, SE, SO. Les voisines d'une cellule sont les huit cellules l'​entourant : N, S, E, O, NE, NO, SE, SO.
  
-La programmation de cet automate est très simple, il s'agit généralement de créer et manipuler un tableau de deux dimensions.+=====Introduction=====
  
-Ce travail - en python - visait à créer le moteur fonctionnel ​de l'automate ​(résultat affiché en console) en moins de lignes possible ​et le plus optimisé possible.+La programmation ​de cet automate ​est assez simple, il s'agit généralement ​de créer ​et manipuler un tableau de deux dimensions.
  
-=====Version 2=====+Ce travail - en python - vise à créer le moteur fonctionnel de l'​automate (et son affichage en console) le plus optimisé possible... en moins de lignes possible.
  
-//​15/​11/​2010//​+=====Source=====
  
-Cette version ​utilise au maximum générateurs,​ listes et diverses syntaxes courtes, et un tableau d'une seule dimension parcouru par une seule boucle associé à une technique d'​index relatifs permettant de l'​analyser comme une grille en deux dimensions.+Ce programme ​utilise au maximum générateurs,​ listes et diverses syntaxes courtes, et un tableau d'une seule dimension parcouru par une seule boucle associé à une technique d'​index relatifs permettant de l'​analyser comme une grille en deux dimensions.
  
-Le code fait 14 lignes, les trois lignes d'affichage ​en console ​comprises.+Le programme crée une grille de taille donnéela remplit aléatoirement selon un taux de remplissage,​ la calcule et met à jour selon les règles de l'automate cellulaire puis l'​affiche ​en console ​(en indiquant chaque cellule vivante par son nombre de voisins) quand l'​utilisateur le demande, et permet de quitter le programme simplement. 
 + 
 +Le code fait 13 lignes et 334 caractères.
  
 <code python> <code python>
 from random import random from random import random
-l = 500 +l=10 
-rnge = range(l*l) +r=range(l*l) 
-nbrs = (1,​-1,​l,​-l,​l+1,​l-1,​-l+1,​-l-1) +n=(1,​-1,​l,​-l,​l+1,​l-1,​-l+1,​-l-1) 
-grid = list(int(random()*1.8) for i in rnge)+g=list(int(random()*1.8) for i in r)
 while not raw_input():​ while not raw_input():​
-  ​last = list(grid+  ​= list(g
-  for i in rnge+  for i in r
-    s = sum(last[i+n] for n in nbrs if 0 < i+< l*l)     +    s = sum(p[i+x] for x in n if 0<i+x<​l*l) ​    
-    if not last[i] and s == 3 : grid[i] = 1 +    if not p[i] and s==3 : g[i]=1 
-    if last[i] and (s < 2 or s > 3): grid[i] = 0 +    if p[i] and (s<2 or s>​3): ​g[i]=0 
-    print str(grid[i]),+    print chr(32+p[i]*(16+s)),
     if i+1 and not (i+1)%l: print     if i+1 and not (i+1)%l: print
-  print 
 </​code>​ </​code>​
  
-====Variables====+Appuyer sur <​key>​Enter</​key>​ pour calculer un cycle, rester appuyé pour calculer en boucle. 
 + 
 +Appuyer sur n'​importe quelle touche et appuyer sur <​key>​Enter</​key>​ pour quitter. 
 + 
 +=====Explication===== 
 + 
 +Je décrit ci-dessous le fonctionnement du programme, ligne par ligne. 
 + 
 +====Préparation des variables====
  
 <code python> <code python>
Ligne 51: Ligne 60:
  
 <code python> <code python>
-l = 500+l=10
 </​code>​ </​code>​
-''​l''​ définit la largeur (et longueur) de la grille en nombre de cases. Pour être visible sans problème en console, préférer un tableau inférieur à 50x50 cases. Le programme peut toutefois calculer sans problème un tableau de 600x600 cases par exemple (hors affichage).+''​l'' ​comme **length** : définit la largeur (et longueur) de la grille en nombre de cases. Pour être visible sans problème en console, préférer un tableau inférieur à 50x50 cases. Le programme peut toutefois calculer sans problème un tableau de 600x600 cases par exemple (hors affichage).
  
 <code python> <code python>
-rnge = range(l*l)+r=range(l*l)
 </​code>​ </​code>​
-Cette liste correspond aux index des cases de la grille ​et sera utilisée pour la créer et la parcourir. La grille est de taille ''​l*l''​afin de correspondre à une grille de deux-dimensions de côté ''​l''​.+''​r''​ comme **range** : liste des index des cases de la grille utilisée pour la créer et la parcourir. La grille est de taille ''​l*l''​ afin de correspondre à une grille de deux-dimensions de côté ''​l''​.
  
-Elle est pré-calculée pour une question de performance,​ cf. [[http://​nliautaud.fr/​wiki/​articles/​python_benchmark#​while_et_for|python benchmark]].+Cette liste est pré-calculée pour une question de performance,​ cf. [[http://​nliautaud.fr/​wiki/​articles/​python_benchmark#​while_et_for|python benchmark]].
  
 <code python> <code python>
-nbrs = (1,​-1,​l,​-l,​l+1,​l-1,​-l+1,​-l-1)+n=(1,​-1,​l,​-l,​l+1,​l-1,​-l+1,​-l-1)
 </​code>​ </​code>​
-Cette liste décrit les index relatifs des huit voisins ​(//​neighbours//​) ramenés au tableau d'une seule dimension. Le processus est détaillé plus bas.+''​n''​ comme **neighbours** : Cette liste décrit les index relatifs des huit voisins ​dans le tableau d'une seule dimension. Le processus est détaillé plus bas.
  
 ====Initialisation de la grille==== ====Initialisation de la grille====
  
 <code python> <code python>
-grid = list(int(random()*1.8) for i in rnge)+g=list(int(random()*1.8) for i in r)
 </​code>​ </​code>​
 +''​g''​ comme **grid**.
  
-Il s'agit d'​un ​"générateur de liste" ​:+Il s'agit d'un générateur de liste utilisant la syntaxe ​:
 <code python> <code python>
 list(value for i in range) list(value for i in range)
 </​code>​ </​code>​
  
-Proche ​de syntaxe courte de création de liste :+C'est proche ​de la syntaxe courte de boucle ​:
 <code python>​[value for i in range]</​code>​ <code python>​[value for i in range]</​code>​
  
Ligne 85: Ligne 95:
 La valeur ajoutée à la liste est une manière d'​obtenir aléatoirement soit ''​0''​ soit ''​1''​ : convertir en entier la valeur à virgule flottante aléatoire comprise entre ''​0''​ et ''​1''​ renvoyée par ''​random()''​. La valeur ajoutée à la liste est une manière d'​obtenir aléatoirement soit ''​0''​ soit ''​1''​ : convertir en entier la valeur à virgule flottante aléatoire comprise entre ''​0''​ et ''​1''​ renvoyée par ''​random()''​.
 <code python>​int(random()*1.8)</​code>​ <code python>​int(random()*1.8)</​code>​
-''​1.8''​ est un "taux de remplissage"​ de la grille.+''​1.8''​ est donc un "taux de remplissage"​ de la grille ​pouvant être manipulé entre ''​0''​ (''​0*0 < résultat < 0*1'',​ donc grille toujours vide) et ''​1.99''​ (''​1.99*0 < résultat < 1.99*1'',​ donc grille aléatoirement remplie).
  
  
Ligne 92: Ligne 102:
 <code python> <code python>
 while not raw_input():​ while not raw_input():​
-  ​last = list(grid) +  ​p=list(g)
-  # analyse et mise à jour de la grille +
-  print+
 </​code>​ </​code>​
-Cette boucle permet de mettre à jour la grille en appuyant sur <​key>​Enter</​key>​. Pour stopper le programme, entrer ​n'importe quelle valeur puis appuyer sur <​key>​Enter</​key>​.+Cette boucle permet de mettre à jour la grille en appuyant sur <​key>​Enter</​key> ​(la condition d'​exécution est vraie puisque ''​raw_input()'' ​n'a rien renvoyé).
  
-''​last'' ​est une copie de la grille permettant par la suite l'analyse de la grille dans son état précédent,​ non modifié, tout en modifiant l'état actuel.+Pour stopper le programme, entrer n'importe quelle valeur puis appuyer sur <​key>​Enter</​key>​ (la condition d'exécution est fausse puisque ​''​raw_input()'' ​a renvoyé une valeur).
  
-Le ''​print'' ​vide inscrit un espace dans la console entre l'affichage ​de deux cycles.+''​p'' ​comme **previous** est une copie de la grille permettant par la suite l'analyse ​de la grille dans son état précédent,​ non modifié, tout en modifiant l'​état actuel.
  
 ====Analyse et modification de la grille==== ====Analyse et modification de la grille====
  
 <code python> <code python>
-  for i in rnge:+  for i in r:
 </​code>​ </​code>​
-On répète l'​opération suivante pour chaque élément de ''​rnge'',​ i correspondant ​alors à l'​index ​des cellules ​de la grille.+On répète l'​opération suivante pour chaque élément de ''​r'',​ i correspondant ​donc tour à tour à l'​index ​de chaque cellule ​de la grille.
  
 ===Somme des voisins=== ===Somme des voisins===
Ligne 114: Ligne 122:
  
 <code python> <code python>
-    s = sum(last[i+n] for n in nbrs if 0 < i+< l*l)    +    s=sum(p[i+x] for x in n if 0<i+x<​l*l) ​
 </​code>​ </​code>​
-Il s'agit encore d'​un ​"générateur de liste" ​créant la liste des états des huit voisins, intégré à la fonction ''​sum()'' ​faisant ​directement la somme chacun des éléments de la liste.+Il s'agit encore d'un générateur de liste créant la liste des états des huit voisins, intégré à la fonction ''​sum()'' ​retournant ​directement la somme chacun des éléments de la liste.
  
 Cette ligne est similaire à : Cette ligne est similaire à :
Ligne 122: Ligne 130:
 <code python> <code python>
 nbrsList = [] nbrsList = []
-for in nbrs+for in n
-  if 0 < i+< l*l: +  if 0 < i+< l*l: 
-    nbrsList.append(last[i+n])+    nbrsList.append(last[i+x])
 s = sum(nbrsList) ​   s = sum(nbrsList) ​  
 </​code>​ </​code>​
  
-On ajoute les index relatifs stockés dans ''​nbrs''​ à l'​index courant ''​i''​ pour déterminer l'​index des voisins ''​i+n''​. L'​état des voisin lors du cycle précédent est alors déterminé par ''​last[i+n]''​.+On ajoute les index relatifs stockés dans ''​n''​ à l'​index courant ''​i''​ pour déterminer l'​index des voisins ''​i+x''​. L'​état des voisin lors du cycle précédent est alors déterminé par ''​last[i+x]''​.
  
-Par exemple ''​nbr[0]''​ est ''​1'',​ l'​index de ce voisin est ''​i+1''​ et son état ''​last[i+1]'',​ ce qui correspond à la case suivante du tableau, donc au voisin de droite.+Par exemple ''​n[0]''​ est ''​1'',​ l'​index de ce voisin est ''​i+1''​ et son état ''​p[i+1]'',​ ce qui correspond à la case suivante du tableau, donc au voisin de droite.
  
 Description des index relatifs : Description des index relatifs :
Ligne 146: Ligne 154:
 La condition impose que l'​index du voisin soit bien dans le tableau (une cellule au bord de la grille va observer des cases qui n'​existent pas), c'est à dire que son index soit supérieur à ''​0''​ et inférieur à la taille du tableau : La condition impose que l'​index du voisin soit bien dans le tableau (une cellule au bord de la grille va observer des cases qui n'​existent pas), c'est à dire que son index soit supérieur à ''​0''​ et inférieur à la taille du tableau :
 <code python> <code python>
-  if 0 < i+< l*l+  if 0<i+x<l*l
 </​code>​ </​code>​
 La syntaxe ''​x < y < z'',​ peu courante dans d'​autres langages, correspond à ''​x < y and y < z''​. La syntaxe ''​x < y < z'',​ peu courante dans d'​autres langages, correspond à ''​x < y and y < z''​.
  
-//La technique de la première version de "​[[#​bordures_mortes|bordure morte]]"​ n'a pas été réutilisée car elle s'​avère peu utile dans un tableau uni-dimensionnel n'​ayant que deux bords à vérifier.//+La technique de la première version de "​[[#​bordures_mortes|bordure morte]]"​ n'a pas été réutilisée car elle s'​avère peu utile dans un tableau uni-dimensionnel n'​ayant que deux bords à vérifier. ​Mais du coup les bords droite/gauche de la grille sont infinis (un objet sortant à droite réapparaît à gauche).
  
 ===Modification de la grille=== ===Modification de la grille===
Ligne 156: Ligne 164:
 Les règles énoncées plus haut peuvent être décrites en deux règles générales de changement. Les règles énoncées plus haut peuvent être décrites en deux règles générales de changement.
 <code python> <code python>
-    if not last[i] and s == 3 : grid[i] = 1+    if not p[i] and s==3:g[i]=1
 </​code>​ </​code>​
 Si l'​état de la cellule courante est ''​0''​ (morte), mais que la somme des voisins vivants est égal à ''​3'',​ on change son état à ''​1''​ (vivante). Si l'​état de la cellule courante est ''​0''​ (morte), mais que la somme des voisins vivants est égal à ''​3'',​ on change son état à ''​1''​ (vivante).
  
 <code python> <code python>
-    if last[i] and (s < 2 or s > 3): grid[i] = 0+    if p[i] and(s<2 or s>3):g[i]=0
 </​code>​ </​code>​
 Si l'​état de la cellule courante est ''​1''​ (vivante), mais que la somme des voisins vivants n'est pas comprise entre ''​2''​ et ''​3'',​ on change son état à ''​0''​ (morte). Si l'​état de la cellule courante est ''​1''​ (vivante), mais que la somme des voisins vivants n'est pas comprise entre ''​2''​ et ''​3'',​ on change son état à ''​0''​ (morte).
Ligne 170: Ligne 178:
  
 <code python> <code python>
-    print str(grid[i]), +    print chr(32+p[i]*(16+s)),
-    if i+1 and not (i+1)%l: print+
 </​code>​ </​code>​
-On affiche l'​état de la cellule courante, et l'on ne va à la ligne que pour les index multiplicateurs de la largeur ​de la grille afin d'​afficher ​le tableau ​en deux dimensions..+Cette ligne affiche un espace ​pour les cellules mortes et le nombre ​de voisins pour les cellules vivantes, en générant ​le code ASCII en base 10 du caractère visé. Cf. [[wp>​ASCII]] et [[http://​pyref.infogami.com/​chr|chr()]]
  
-====Benchmark====+Si la cellule est morte, le calcul sera ''​32+0*(16+s)''​ donc toujours ''​32'',​ qui est le code ASCII du caractère ''​Espace''​.
  
-Le programme s'exécute ​(hors affichage en consoleen ''​0.6934'' ​secondes par cycle (1.67 cycles par seconde) pour une grille de 500x500soit 250.000 cellules.+Si la cellule est vivante, le calcul sera ''32+1*(16+s)'' ​donc toujours ''​48+s''​A ''​48''​ (le code du chiffre ''​0''​) est rajouté le nombre de voisins, ce qui donne ''​49''​ (le code du chiffre ''​1''​),​ ''​50''​ (le code du chiffre ''​2''​),​ etc... 
 + 
 +<code python>​ 
 +    if i+1 and not(i+1)%l:print 
 +</​code>​ 
 +On va à la ligne pour les index multiplicateurs de la largeur de la grille ​afin d'​afficher le tableau en deux dimensions. 
 + 
 +=====Benchmark===== 
 + 
 +Les temps ci-après concernent l'​exécution du programme légèrement modifié afin d'​indiquer une moyenne (sur 100 cycles) des temps de calcul sans affichage de la grille en console : 
 + 
 +<code python>​ 
 +from time import time 
 +t=time() 
 +cycles = 100 
 + 
 +from random import random 
 +l=100 
 +r=range(l*l) 
 +n=(1,-1,​l,​-l,​l+1,​l-1,​-l+1,​-l-1) 
 +g=list(int(random()*1.8) for i in r) 
 +for bench in range(cycles):​ 
 +  p=list(g) 
 +  for i in r: 
 +    s=sum(p[i+x] for x in n if 0<​i+x<​l*l) 
 +    if not p[i] and s==3:​g[i]=1 
 +    if p[i] and(s<2 or s>​3):​g[i]=0 
 + 
 +print str((time()-t) / cycles) 
 +</​code>​
  
-=====Version ​1=====+^  Taille ​ ^  Nombre de cellules ​ ^  Secondes par cycle  ^  Cycles par seconde ​ ^ 
 +| ''​50'' ​  ​| ​      ''​5.000'' ​     |      ''​0.0068'' ​     |    ''​147'' ​     | 
 +| ''​100'' ​ |      ''​10.000'' ​     |      ''​0.0267'' ​     |    ''​37.2'' ​    | 
 +| ''​500'' ​ |      ''​250.000'' ​    ​| ​     ''​0.6934'' ​     |    ''​1.67'' ​    | 
 +| ''​600'' ​ |      ''​360.000'' ​    ​| ​     ''​1.0087'' ​     |    ''​0.99'' ​    |
  
-//​03/​11/​2010//​+=====Version précédente=====
  
-Cette version utilise certaines syntaxes courtes ​et un tableau à deux dimensions.+Cette version utilise certaines syntaxes courtesun tableau à deux dimensions ​et affiche l'​état de toutes les cellules un certain nombre de fois.
  
 19 lignes, les trois lignes d'​affichage en console comprises. 19 lignes, les trois lignes d'​affichage en console comprises.
Ligne 208: Ligne 248:
 </​code>​ </​code>​
  
-====Bordures mortes====+===Bordures mortes===
  
 Un problème à résoudre pour l'​optimisation du code était de trouver un moyen court et efficace d'​éviter les cases inexistantes. Lorsqu'​une cellule est au bord de la grille, elle n'a pas huit voisins, le processus doit donc s'​assurer ​ de ne pas demander l'​état de cases en dehors du tableau. Un problème à résoudre pour l'​optimisation du code était de trouver un moyen court et efficace d'​éviter les cases inexistantes. Lorsqu'​une cellule est au bord de la grille, elle n'a pas huit voisins, le processus doit donc s'​assurer ​ de ne pas demander l'​état de cases en dehors du tableau.
Ligne 221: Ligne 261:
 Toutes les cellules mises à jour ayant alors bien huit voisines et les bordures n'​ayant aucune influence sur le résultat des règles car toujours définies à ''​0'',​ il n'est plus besoin d'​ajouter une quelconque condition durant le processus. Toutes les cellules mises à jour ayant alors bien huit voisines et les bordures n'​ayant aucune influence sur le résultat des règles car toujours définies à ''​0'',​ il n'est plus besoin d'​ajouter une quelconque condition durant le processus.
  
-====Benchmark====+===Benchmark===
  
 Cette version s'​exécute (hors affichage en console) en ''​0.9613''​ secondes par cycle (1.04 cycles par seconde) pour une grille de 500x500, soit 250.000 cellules. Cette version s'​exécute (hors affichage en console) en ''​0.9613''​ secondes par cycle (1.04 cycles par seconde) pour une grille de 500x500, soit 250.000 cellules.