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 s’acquitter d’une 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")
À 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.
En informatique, une boucle est une structure de contrôle permettant la répétition d’une ou plusieurs instructions.
En python, on distingue essentiellement deux types de boucles.
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 (en réalité, ce n’est pas tout à fait vrai, mais dans un premier temps on s’en 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 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 à 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 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 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 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é (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 l’informaticient avec l’intuition de l’utilisateur en utilisant l’astuce 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 l’avantage énorme de montrer visuellement le nombre de fois que la boucle s’exé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 qu’une 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
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 ?
range(10)
;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\) sauf si on est à l’aise en mathématiques). On utilise alors une boucle tant que ressemblant
un peu à l’exemple 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
\
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 (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
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, en utilisant une valeur qui ne peut pas être la valeur recherchée, et idéalement même une valeur qui n’est pas un mot de passe valide. Dans le cas contraire, la boucle ne s’exécuterait jamais.