Outils pour utilisateurs

Outils du site


boucles_imbriquees

Des boucles imbriquées entièrement définies par programme (en quantité et en valeurs)

Objectif

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.

Premier codage

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:

  • 1er chiffre = borne inférieure
  • 2ème chiffre = borne supérieure (exclue)
  • 3ème chiffre = incrément

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:

  • avec la liste:
boucles = [[1,5,2],3,[1,4]]
  • si certains éléments de la liste sont donnés par un nombre entier (ex: 3) au lieu d'une sous-liste (ex: [3]), on va reconstituer la sous-liste:
boucles = B = [(type(x)==list and [x] or [[x]])[0] for x in boucles]
print boucles
[[1,5,2],[3],[1,4]]
  • si la sous-liste n'a qu'un seul élément, il s'agit de la borne supérieure. On va donc ajouter la borne inférieure=0 avec:
boucles = [(len(x)==1 and [[0] + x] or [x])[0] for x in boucles]
print boucles
[[1,5,2],[0,3],[1,4]]
  • si la sous-liste n'a que 2 éléments, il s'agit des bornes inférieures et supérieures. On va donc ajouter le pas=1:
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:

  • j'ai une boucle “while” globale qui contient ce qui devrait être dans la partie la plus intérieure des boucles imbriquées
  • et j'ai une boucle “for” située au début de la boucle while globale, dont le but est d'incrémenter les compteurs en commençant par le dernier et en remontant, et ceci jusqu'à ce que les conditions de sortie soit atteintes.

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:

  • on commence par incrémenter le dernier compteur (x=n). S'il ne “déborde” pas, on arrête la boucle. S'il “déborde”, on le remet à la valeur de la borne inférieure, et on passe au compteur précédent x=x-1
  • on fait la même chose pour le compteur précédent
  • etc…
  • et on arrête cette boucle for d'incrémentation des compteurs au premier compteur qui peut être incrémenté sans débordement.

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”

Codage sous forme de classe

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!

boucles_imbriquees.txt · Dernière modification: 2008/12/17 08:20 de tyrtamos

Outils de la page