Les boucles

Une des taches les plus courantes en informatique est la répétition d’une action: imprimer toutes les photos dans un répertoire, jouer séquentiellement toutes les musiques d’une playlist, gérer tous les joueurs dans un jeu vidéo.

Imaginons que l’on souhaite faire une punition donnée par un professeur avec le langage python. Une solution naïve serait la suivante:

print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")
print("Je ne parle pas sans lever la main")

À l’exécution, ce script affiche le résultat suivant dans la console:

Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main

Cette solution est à proscrire à tout prix: l’usage du copier/coller en informatique est en général le signe que l’on aurait dû faire appel à la notion de boucle.

Une boucle est l’outil fondamental de la programmation permettant la répétition d’une ou plusieurs instructions.

On distingue essentiellement deux types de boucles.

Les boucles pour

La boucle pour est représentée par l’algorithme suivant:

Pour i allant de 1 à 10 faire:
    <instructions>

Elle est à utiliser lorsque l’on a besoin de répéter des instructions un nombre déterminé de fois.

Ses avantages est que le langage gère automatiquement le changement de valeur du compteur i, qui variera automatiquement de 1 à 10 par pas de 1.

Le désavantage est que l’on doit connaître à l’avance les bornes de la boucle: il n’est pas possible d’interrompre la boucle plus tôt si le besoin s’en fait sentir (ce n’est pas tout à fait vrai, mais dans un premier temps on s’en tiendra à cela).

En python, la syntaxe de la boucle pour est un peu différente de celle proposée par l’algorithme ci-dessus. Elle correspond plutôt à l’algorithme:

Pour i prenant toutes les valeurs de la liste [x1, x2, ...] faire:
    <instructions>

On peut néanmoins écrire notre punition d’une manière plus concise:

for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
    print("Je ne parle pas sans lever la main")

Avec le résultat attendu:

Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main

Bien sûr, il semblerait que l’on soit obligé d'énumérer les 10 valeurs de i dont on a besoin. Cela ne serait guère praticable pour une boucle devant aller de 1 à 1000 par exemple. Heureusement, il existe un moyen plus rapide: utiliser la fonction spéciale range(a, b) qui créera pour nous la liste des valeurs entières entre a inclu et b exclu.

for i in range(1, 10):
    print("Je ne parle pas sans lever la main")
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main
Je ne parle pas sans lever la main

On a cependant un soucis: il n’y a que 9 lignes. En effet, la valeur d’arrivée étant toujours exclue, la variable i parcourera les valeurs entières entre 1 et 9, soit 9 valeurs en tout. On peut d’ailleurs mettre cela en évidence en préfixant chaque ligne par son rang:

for i in range(1, 10):
    print("Ligne n°", i, ": je ne parle pas sans lever la main")
Ligne n° 1 : je ne parle pas sans lever la main
Ligne n° 2 : je ne parle pas sans lever la main
Ligne n° 3 : je ne parle pas sans lever la main
Ligne n° 4 : je ne parle pas sans lever la main
Ligne n° 5 : je ne parle pas sans lever la main
Ligne n° 6 : je ne parle pas sans lever la main
Ligne n° 7 : je ne parle pas sans lever la main
Ligne n° 8 : je ne parle pas sans lever la main
Ligne n° 9 : je ne parle pas sans lever la main

Comment rétablir le bon compte ? Une possibilité est d’aller une unité plus loin:

for i in range(1, 11):
    print("Ligne n°", i, ": je ne parle pas sans lever la main")

Et le résultat est enfin celui espéré:

Ligne n° 1 : je ne parle pas sans lever la main
Ligne n° 2 : je ne parle pas sans lever la main
Ligne n° 3 : je ne parle pas sans lever la main
Ligne n° 4 : je ne parle pas sans lever la main
Ligne n° 5 : je ne parle pas sans lever la main
Ligne n° 6 : je ne parle pas sans lever la main
Ligne n° 7 : je ne parle pas sans lever la main
Ligne n° 8 : je ne parle pas sans lever la main
Ligne n° 9 : je ne parle pas sans lever la main
Ligne n° 10 : je ne parle pas sans lever la main

Cela fonctionne, mais n’est pas très éléguant visuellement: on ne sait pas trop ce que vient faire le 11 ici, et calculer de tête le nombre de fois que la boucle s’exécutera n’est pas complètement aisé.

On privilégiera plutôt la méthode alternative, très prisée des informaticiens (quel que soit leur langage de programmation) qui consiste à partir de zéro:

for i in range(0, 10):
    print("Ligne n°", i, ": je ne parle pas sans lever la main")

Et le résultat est presque le même:

Ligne n° 0 : je ne parle pas sans lever la main
Ligne n° 1 : je ne parle pas sans lever la main
Ligne n° 2 : je ne parle pas sans lever la main
Ligne n° 3 : je ne parle pas sans lever la main
Ligne n° 4 : je ne parle pas sans lever la main
Ligne n° 5 : je ne parle pas sans lever la main
Ligne n° 6 : je ne parle pas sans lever la main
Ligne n° 7 : je ne parle pas sans lever la main
Ligne n° 8 : je ne parle pas sans lever la main
Ligne n° 9 : je ne parle pas sans lever la main

Les informaticiens comptent quasiment tout le temps à partir de zéro, mais les simples mortels préfèrent en général compter à partir de un. C’est possible par une petite astuce:

for i in range(0, 10):
    print("Ligne n°", i + 1, ": je ne parle pas sans lever la main")

Et on retrouve le résultat initial:

Ligne n° 1 : je ne parle pas sans lever la main
Ligne n° 2 : je ne parle pas sans lever la main
Ligne n° 3 : je ne parle pas sans lever la main
Ligne n° 4 : je ne parle pas sans lever la main
Ligne n° 5 : je ne parle pas sans lever la main
Ligne n° 6 : je ne parle pas sans lever la main
Ligne n° 7 : je ne parle pas sans lever la main
Ligne n° 8 : je ne parle pas sans lever la main
Ligne n° 9 : je ne parle pas sans lever la main
Ligne n° 10 : je ne parle pas sans lever la main

Notons que, puisque range(0, arrivée) est utilisé extrêmement souvent, python autorise de ne pas mettre la première borne: dans ce cas, il considérera toujours que la boucle démarre à zéro:

for i in range(10):
    print("Ligne n°", i + 1, ": je ne parle pas sans lever la main")
Ligne n° 1 : je ne parle pas sans lever la main
Ligne n° 2 : je ne parle pas sans lever la main
Ligne n° 3 : je ne parle pas sans lever la main
Ligne n° 4 : je ne parle pas sans lever la main
Ligne n° 5 : je ne parle pas sans lever la main
Ligne n° 6 : je ne parle pas sans lever la main
Ligne n° 7 : je ne parle pas sans lever la main
Ligne n° 8 : je ne parle pas sans lever la main
Ligne n° 9 : je ne parle pas sans lever la main
Ligne n° 10 : je ne parle pas sans lever la main

Mis à part le fait que la variable boucle à partir de zéro, ce qui requiert une certaine habitude, cette écriture a l’avantage énorme de montrer visuellement le nombre de fois que la boucle s’exécutera.

On peut à présent utiliser le concept de boucle pour effectuer des tâches plus intéressantes qu’une punition, comme par exemple afficher la liste de 10 premiers carrés:

#begin_src python for i in range(10): print(“Le carré de”, i + 1, “est”, (i + 1)**2) #+end_src

Le carré de 1 est 1
Le carré de 2 est 4
Le carré de 3 est 9
Le carré de 4 est 16
Le carré de 5 est 25
Le carré de 6 est 36
Le carré de 7 est 49
Le carré de 8 est 64
Le carré de 9 est 81
Le carré de 10 est 100

Enfin, pour clôre ce paragraphe, notons que python offre la possibilité de boucler sur des listes quelconques, ce qui dans certains cas est bien pratique:

for fruit in ["banane", "pomme", "fraise", "tomate"]:
    print("J'adore manger de la", fruit, ".")
J'adore manger de la banane .
J'adore manger de la pomme .
J'adore manger de la fraise .
J'adore manger de la tomate .

Les boucles tant que

La boucle tant que fonctionne de manière fondamentalement différente de la boucle pour: algorithmiquement, elle correspond à:

Tant que <condition est satisfaite> faire:
    <instructions>

Elle est bien plus souple que la boucle pour: en effet, on ne connaît pas nécessairement à l’avance le nombre de fois que la boucle s’exécutera.

Remarquons qu’il est cependant tout à fait possible de simuler une boucle pour à l’aide d’une boucle tant que, comme le montre l’exemple suivant:

i = 0
while i < 10:
    print("Le carré de", i + 1, "est", (i + 1)**2)
    i = i + 1

Avec le résultat attendu:

Le carré de 1 est 1
Le carré de 2 est 4
Le carré de 3 est 9
Le carré de 4 est 16
Le carré de 5 est 25
Le carré de 6 est 36
Le carré de 7 est 49
Le carré de 8 est 64
Le carré de 9 est 81
Le carré de 10 est 100

Cette boucle produit exactement le même résultat que l’exemple précédent utilisant une boucle pour. Que constate-t-on ?

  • Il est nécessaire d’initialiser le compteur avant la boucle;
  • La fin de la boucle est gérée par une condition: ici, on boucle tant que le compteur est inférieur à 10 pour simuler ce que fait range(10);
  • Il est indispensable d’incrémenter le compteur manuellement, généralement en fin de boucle (mais pas nécessairement).

Cet exemple est à prendre comme un exemple type d’une boucle pour simulée à l’aide d’une boucle tant que. Il est toujours possible de remplacer une boucle pour par une boucle tant que (bien que, dans le cas présent, cela soit moins lisible et donc moins pratique), mais l’inverse est fausse.

Voici un exemple où l’on souhaite chercher le plus petit entier dont le carré est inférieur ou égal à 350 (nombre choisi arbitrairement). La boucle pour ne permet pas de réaliser cela, car on ne peut pas savoir à l’avance quelle est la réponse (\(\dots\) justement). On utilise alors une boucle tant que ressemblant un peu à l’exemple précédent:

n = 1
while n*n <= 350:
    n = n + 1
print("Le plus petit entier dont le carré est supérieur à 350 est", n)
print("En effet:")
print(n - 1, "*", n - 1, "=", (n-1)**2)
print(n, "*", n, "=", n*n)

\(\ldots\) ce qui donne le résultat suivant:

Le plus petit entier dont le carré est supérieur à 350 est 19
En effet:
18 * 18 = 324
19 * 19 = 361

Un autre exemple, toujours mathématique: on peut démontrer que la somme \[1 + \frac12 + \frac14 + \frac18 + \cdots + \frac1{2^n}\] s’approche de plus en plus de 2 au fur et à mesure que \(n\) grandit (on dit d’ailleurs en mathématiques que 2 est la limite de cette somme lorsque \(n\rightarrow +\infty\)).

Cherchons à partir de quelle valeur de \(n\) la somme sera supérieure à 1,999999. Là encore, une boucle pour n’est guère envisageable, puisque l’on n’a aucune idée de la valeur de \(n\) à atteindre. On utilise alors une boucle tant que:

somme = 0.0
n = 0
while somme < 1.999999:
    somme = somme + 1 / (2**n)
    n = n + 1

print("Pour n = ", n, "la somme vaut", somme)

Ce petit algorithme nous donne le résultat recherché:

Pour n =  21 la somme vaut 1.9999990463256836

Les boucles jusqu'à ce que

En algorithmique, on trouve parfois un autre type de boucle, représentée par exemple par l’algorithme suivant demandant à un utilisateur de rentrer un mot de passe (et ne s’arrêtant qu’une fois le bon mot de passe trouvé):

répéter:
    Demander le mot de passe à l'utilisateur et le stocker dans la variable mdp
jusqu'à ce que mdp soit égal à 42

Certains langages de programmation offrent la possibilité de coder une boucle jusqu'à ce que (en général do <instructions> until <condition>), mais ce n’est pas le cas en python.

Il est cependant toujours possible de transformer une boucle jusqu'à ce que en boucle tant que, avec la petite complication supplémentaire que la condition est toujours testée en début de boucle pour la boucle tant que, alors qu’elle l’est en fin de boucle pour la boucle jusqu'à ce que.

L’algorithme devient alors:

mdp prend la valeur -1
tant que mdp n'est pas égal à 42:
    Demander le mot de passe à l'utilisateur et le stocker dans la variable mdp

La petite astuce consiste simplement à initialiser la variable mdp de façon à ce que le premier test soit vrai. Dans le cas contraire, la boucle ne s’exécuterait jamais.