Boucles


Nécessité des boucles

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

Imaginons que lon souhaite sacquitter dune punition donnée par un professeur (du XIXe siècle) 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")

À lexé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: lusage du copier/coller en informatique est en général le signe que lon aurait dû faire appel à la notion de boucle.

En informatique, une boucle est une structure de contrôle permettant la répétition dune ou plusieurs instructions.

En python, on distingue essentiellement deux types de boucles.

Les boucles pour

La boucle pour est représentée par lalgorithme suivant:

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

Elle est à utiliser lorsque lon 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 lon doit connaître à lavance les bornes de la boucle: il nest pas possible dinterrompre la boucle plus tôt si le besoin sen fait sentir (en réalité, ce nest pas tout à fait vrai, mais dans un premier temps on sen tiendra à cette contrainte qui nous forcera à concevoir proprement nos algorithmes).

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

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

On peut néanmoins écrire notre punition dune 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 lon 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 à 1000000 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 ny a que 9 lignes. En effet, la valeur darrivée étant toujours exclue, la variable i parcourera les valeurs entières entre 1 et 9, soit 9 valeurs en tout. On peut dailleurs 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 daller un rang 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 nest 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 sexécutera nest pas complètement aisé (doit on faire 11 1 = 10 ? Est-ce tout simplement 11 fois ? )

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 (et la plupart des langages de programmation imposent ce schéma de pensée), mais les simples mortels préfèrent en général compter à partir de un. Il est possible de réconcilier la pensée de linformaticient avec lintuition de lutilisateur en utilisant lastuce suivante:

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 lavantage énorme de montrer visuellement le nombre de fois que la boucle sexécutera, sans nécessiter de faire des calculs mentaux.

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

for i in range(10):
    print("Le carré de", i + 1, "est", (i + 1)**2)
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 ["bananes", "pommes", "fraises", "tomates"]:
    print("J'adore manger des", fruit)
J'adore manger des bananes
J'adore manger des pommes
J'adore manger des fraises
J'adore manger des tomates

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 à lavance le nombre de fois que la boucle sexécutera.

Remarquons quil est cependant tout à fait possible de simuler une boucle pour à laide dune boucle tant que, comme le montre lexemple 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 lexemple précédent utilisant une boucle pour. Que constate-t-on ?

  • Il est nécessaire dinitialiser 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 dincrémenter le compteur manuellement, généralement en fin de boucle (mais pas nécessairement).

Cet exemple est à prendre comme un exemple type dune boucle pour simulée à laide dune 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 linverse est fausse.

Voici un exemple où lon 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 à lavance quelle est la réponse (\(
dots\) sauf si on est à laise en mathématiques). On utilise alors une boucle tant que ressemblant un peu à lexemple précédent:

n = 1
while n*n <= 350:
    n = n + 1

# À ce stade, n est l'entier recherché.
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, "< 350")
print(n, "*", n, "=", n*n, ">= 350")

Cela donne le résultat suivant:

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

Un autre exemple, toujours mathématique: on peut démontrer que la somme \[1 + \frac12 + \frac14 + \frac18 + \cdots + \frac1{2^n}\\] sapproche de plus en plus de 2 au fur et à mesure que \(n\) grandit (on dit dailleurs 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 nest guère envisageable, puisque lon na aucune idée de la valeur de \(n\) à atteindre (sauf si on déjà étudié les suites géométriques en spécialité mathématiques, et plus spécifiquement la somme des termes). 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

Complément: Les boucles jusquà ce que

En algorithmique, on trouve parfois un autre type de boucle, représentée par exemple par lalgorithme suivant demandant à un utilisateur de rentrer un mot de passe (et ne sarrêtant quune 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 nest 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 quelle lest en fin de boucle pour la boucle jusquà ce que.

Lalgorithme 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, en utilisant une valeur qui ne peut pas être la valeur recherchée, et idéalement même une valeur qui nest pas un mot de passe valide. Dans le cas contraire, la boucle ne sexécuterait jamais.