Dans un programme, on doit avoir des boucles “for” imbriquées, comme par exemple:
for i in range(1,5,2): for j in range(3): for k in range(1,4): ...
Mais on n'en connait pas d'avance le nombre, et on veut donc pouvoir le définir au cours du programme.
Comme on ne connait pas d'avance la quantité de boucle, on met les conditions de boucle dans une liste.
Par exemple, avec le cas précédent:
boucles = [[1,5,2],[0,3,1],[1,4,1]]
Avec pour chaque boucle:
Avec une telle structure, on met autant de boucles qu'on veut, et on les détermine au cours du programme!
Bien entendu, la borne inférieure peut être différente de zéro.
On peut ajouter - c'est le cas ici - des possibilités de compléter automatiquement des listes incomplètes. Par exemple:
boucles = [[1,5,2],3,[1,4]]
boucles = B = [(type(x)==list and [x] or [[x]])[0] for x in boucles] print boucles [[1,5,2],[3],[1,4]]
boucles = [(len(x)==1 and [[0] + x] or [x])[0] for x in boucles] print boucles [[1,5,2],[0,3],[1,4]]
boucles = [(len(x)==2 and [x + [1]] or [x])[0] for x in boucles] print boucles [[1,5,2],[0,3,1],[1,4,1]]
A partir de cette liste qui définit les boucles imbriquées, on va initialiser une nouvelle liste qui contiendra les compteurs de chaque boucle, chaque compteur démarrant à la borne inférieure de la boucle en question:
compteurs = [x[0] for x in boucles] print compteurs [1, 0, 1]
Dans chaque boucle, ce compteur sera incrémenté avec le pas, et le résultat sera comparé à la borne supérieure.
La structure du code proposé est:
Voilà le code complet:
# donnée boucles = [[1,5,2],3,[1,4]] # initialisation de la boucle boucles = [(type(x)==list and [x] or [[x]])[0] for x in boucles] boucles = [(len(x)==1 and [[0] + x] or [x])[0] for x in boucles] boucles = [(len(x)==2 and [x + [1]] or [x])[0] for x in boucles] compteurs = [x[0] for x in boucles] # => initialisation des compteurs de boucle (= valeur initiale de boucle) compteurs[-1] -= boucles[-1][2] # le 1er incrément sera pour rien indmax = len(boucles)-1 # => index de la dernière boucle (la plus interne) finboucle = False # => drapeau pour condition de fin de la boucle globale while True: # incrémentation des compteurs, test de boucle et condition de sortie for x in range(indmax, -1, -1): compteurs[x] += boucles[x][2] if compteurs[x]>=boucles[x][1]: if x==0: finboucle = True break compteurs[x] = boucles[x][0] else: break if finboucle: break # =====> partie utile de la boucle <===== # ... # ... print compteurs # ... # ...
A l'intérieur de la “partie utile” de la boucle, on affiche la situation des compteurs de boucles:
[1, 0, 1] [1, 0, 2] [1, 0, 3] [1, 1, 1] [1, 1, 2] [1, 1, 3] [1, 2, 1] [1, 2, 2] [1, 2, 3] [3, 0, 1] [3, 0, 2] [3, 0, 3] [3, 1, 1] [3, 1, 2] [3, 1, 3] [3, 2, 1] [3, 2, 2] [3, 2, 3]
On voit bien que l'évolution des compteurs, la situation de début et celle de fin sont correctes.
A l'intérieur de la partie utile de la grande boucle while, la valeur de chaque compteur i (qui remplace les i,j,k,…) est récupérée par compteurs[i], i étant l'indice de la boucle (de 0 à len(boucles)-1).
Le principe de l'incrémentation des compteurs (la boucle “for”) est simple:
Ceci jusqu'à ce qu'on essaye d'incrémenter le compteur 0 alors qu'il a déjà atteint sa borne maxi. Auquel cas, la boucle while globale doit aussi s'arrêter! Ce qui est fait avec la condition “finboucle = True”
On va reprendre les codes précédents, mais on va les présenter sous forme de classe. Cela va apparemment compliquer le code lui-même, mais cela va considérablement simplifier son utilisation. Cela permettra aussi d'éviter des collisions dans les noms de variables.
Voilà la classe:
class Boucles(object): def __init__(self, B): # sauvegarder la liste des boucles self.B = B self.indmax = len(self.B)-1 # reconstituer une sous-liste quand elle est donnée sous forme d'entier self.B = [(type(x)==list and [x] or [[x]])[0] for x in self.B] # insérer la borne inférieure (=0) de chaque boucle si elle n'est pas donnée self.B = [(len(x)==1 and [[0] + x] or [x])[0] for x in self.B] # ajouter le pas (=1) en 3ème paramètre s'il n'est pas donné self.B = [(len(x)==2 and [x + [1]] or [x])[0] for x in self.B] # initialiser les compteurs de boucle à la valeur de leur borne inférieure self.C = [x[0] for x in self.B] # vérification qu'aucune borne inférieure n'est >= la borne supérieure correspondante self.stopboucle = False for i in range(0,len(self.B)): if self.B[i][0]>=self.B[i][1]: self.stopboucle = True # initialiser la redirection pour le 1er test de boucle (=le 1er test est différent des suivants) self.encore = self.encore1 def encore1(self): """méthode pour faire le 1er test par while""" if self.stopboucle: # fin de boucle immédiate, car il existe au moins une borne inf >= borne sup return False else: # Au 1er test par while, les compteurs ne doivent pas être incrémentés self.encore = self.encore2 return True def encore2(self): """incrémentation des compteurs et test de poursuite ou de fin de boucle""" for x in range(self.indmax, -1, -1): # on parcourt tous les compteurs du dernier au premier self.C[x] += self.B[x][2] # on incrémente le compteur x avec son pas if self.C[x]>=self.B[x][1]: # est-ce que le compteur déborde? if x==0: # en plus, est que c'est le compteur 0 qui déborde? return False # tous les compteurs sont arrivés au bout du comptage self.C[x] = self.B[x][0] # le compteur x est arrivé au bout: il faut le réinitialiser else: break # on a pu incrémenter le compteur x sans débordement: la boucle globale peut continuer return True # donner l'accord de poursuite de la boucle while globale
Et voilà son utilisation dans un programme:
B = [[1,5,2],3,[1,4]] boucles = Boucles(B) while boucles.encore(): # ... # ... partie utile de la boucle # ... valeur du compteur i récupérable par boucles.C[i] # ... print boucles.C # ...
Comme dans le 1er code, on peut introduire une liste de boucles incomplète (ex: 3 au lieu de [0, 3, 1]), comme d'ailleurs on le ferait avec le “for i in range(3)” au lieu de “for i in range(0, 3, 1)”.
Dans la partie utile de la boucle, on peut récupérer chaque compteur de boucle i comme: boucles.C[i]. Mais ici, le nom “boucles”, instance de classe, est un choix de l'utilisateur.
Ce code utilisateur affiche, bien entendu, la même chose que précédemment (affichage des compteurs à chaque tour):
[1, 0, 1] [1, 0, 2] [1, 0, 3] [1, 1, 1] [1, 1, 2] [1, 1, 3] [1, 2, 1] [1, 2, 2] [1, 2, 3] [3, 0, 1] [3, 0, 2] [3, 0, 3] [3, 1, 1] [3, 1, 2] [3, 1, 3] [3, 2, 1] [3, 2, 2] [3, 2, 3]
Petite particularité: comme on se met uniquement à la partie la plus interne des boucles imbriquées, il suffit qu'un seul des compteurs ait une borne inf >= à la borne sup, pour qu'aucune boucle ne tourne (c'est aussi le cas dans les boucles imbriquées python).
La seule chose qui manque encore, éventuellement, ce sont, à l'initialisation de la classe, les vérifications qu'on ne rentre pas n'importe quoi comme “B”. Avec génération d'une exception, par exemple, voire avec création d'une classe d'erreur supplémentaire (BoucleError ?).
Amusez-vous bien!