Outils Boucles

Identification

Infoforall

12 - Boucles non bornées


Nous avons vu les boucles bornées : les boucles qui travaillent en boucle un nombre de fois défini à l'avance.

Nous avons vu les instrutions conditionnelles : comment agir en fonction des besoins.

Voyons aujourd'hui les boucles non bornées : on agit en boucle tant qu'une condition est évaluée à True.

Version non finalisée qui reprend des morceaux de l'ancienne version. A terme, tout va changer.

Evaluation ✎ : questions 07-08-09

1 - Boucle non bornée tant que / while

Voyons maintenant le tant que / while, bien pratique avec les applications graphiques qui ne sont censés s'arrêter que dans certains cas.

On pourrait utiliser un test SI mais cela ne fonctionnerait qu'une fois ...

On pourrait utilser une boucle bornée mais elle finira par s'arrêter...

Boucle non bornée : la boucle while

Heureusement, il existe le test TANT QUE. Il s'agit d'instructions qui seront executés en boucle TANT QUE la condition testée est VRAI.

Puisqu'on ne peut pas connaitre à l'avance le nombre de fois où la boucle d'exécution va être réalisée, on parle également de boucle non bornée : il n'y a pas de nombre limite au bout duquel on quitte la boucle.

Elle se code de la façon suivante :

while condition_pour_boucler :

Comme son nom l'indique, on va donc boucler TANT QUE l'expression condition_pour_bouclercode> est évaluée à True.

C'est comme si nous avions noté ceci :

while condition_pour_boucler == True :

Bien entendu, il est possible que vous avez à l'inverse une condition_pour_sortircode>.

Nous avons vu qu'il y a deux versions :

La version propre :

while not condition_pour_sortir :

La version explicite

while condition_pour_sortir == False :

Comme pour le IF, c'est l'indentation de 4 espaces qui permet d'indiquer quelles instructions sont à faire en boucle.

Voici à titre d'exemple la façon d'utiliser un while (TANT QUE).

Commençons par un exemple graphique :

  • On crée une tortue à des coordonnées et un angle aléatoire.
  • On lui demande d'avancer TANT QUE sa coordonnée x est inférieure à 200.

01° Placer ce code en mémoire et visualiser le déplacement de la tortue.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
import turtle as trt import random as rd def stylo() : feutre = trt.Turtle() # on crée une tortue x = rd.randint(-200, 200) # on tire une valeur aléatoire en x y = rd.randint(-200, 200) # on tire une valeur aléatoire en y angle = rd.randint(-45, 45) # on tire un angle aléatoire deplacer(feutre, x, y, angle) return feutre def deplacer(feutre, x, y, angle) : '''Déplace en (x,y) et réoriente en fonction de angle le feutre''' feutre.penup() # On lève la pointe feutre.setpos(x, y) # On déplace le crayon feutre.setheading(angle) # On le tourne dans le bon sens feutre.pendown() # On abaisse la pointe # Programme principal s = stylo() # on crée un stylo-tortue while s.xcor() < 300 : s.forward(1)

02° En regardant le code, que peut-on dire des valeurs initiales en x et de l'angle de base ?

Avec cette valeur initiale en x, est-on certain de parvenir à rentrer au moins une fois dans la boucle TANT QUE ?

Avec l'angle de base, est-on certain de percuter le "mur" de droite ?

Rappel : s.xcor() permet de récupérer la coordonnée horizontale du stylo ("x" donc).

...CORRECTION...

Ligne 6 : x = rd.randint(-200, 200) : on voit que x est inférieure à 200. Comme la condition pour rentrer dans la boucle est (ligne 23) d'être inférieure à 300, nous sommes certain de rentrer au moins une fois.

Pour l'angle, on a (ligne 8) un angle entre -45° et +45° : on va donc percuter le mur.

03° Remplacer la ligne 8 par ceci : l'angle est maintenant aléatoire entre 90° et 180°.

8
angle = rd.randint(90, 180) # on tire un angle aléatoire

Que va-t-il se passer cette fois au niveau de la boucle TANT QUE 

Pourquoi parle-t-on de boucle infinie ?

...CORRECTION...

On ne pourra jamais percuter le mur de droite.

Nous ne sortirons donc jamais de la boucle car la condition restera VRAI à jamais.

D'où le terme "BOUCLE INFINIE".

Condition initiale

Lorsqu'on écrit une boucle TANT QUE, il est impératif de surveiller s'il est possible de rentrer (ou pas) une première fois dans la boucle. Si la condition travaille à partir d'une donnée qui arrivera plus tard, il est alors important de placer une "fausse valeur" dans cette variable de façon à rentrer dans la boucle.

Erreur typique :

19 20 21 22 23 24 25
# Programme principal s = stylo() # on crée un stylo-tortue while x < 300 : s.forward(1) x = s.xcor()

L'erreur ? La condition de la ligne 23 a besoin de x alors qu'elle n'est définie qu'en ligne 25 !

Correction possible :

Soit la solution de l'exercice, soit placer une fausse valeur dans x avant la ligne 23.

19 20 21 22 23 24 25 26
# Programme principal s = stylo() # on crée un stylo-tortue x = 0 while x < 300 : s.forward(1) x = s.xcor()

Si nous avions voulu créer une boucle infinie avec nos balles de l'activité précédente, nous aurions donc pu remplacer la boucle POUR réglée à 8000 par une simple boucle TANT QUE réglèe sur VRAI. On trouve ce type de code assez souvent dans les programmes des micro-contrôleurs : on veut que le programme ne s'arrête jamais !

04° Utiliser le code ci-dessous qui contient une boucle infinie. Localiser la boucle et vérifier que l'animation ne s'arrête jamais. Ca peut être long :o)

>>> lancer_animation()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
'''Description des fonctions d'interface proposées --> Fonctions fournissant une réponse : + application_graphique(dim) : Créé et renvoie la référence d'une application Turtle + bordure(dim) : Crée et renvoie un objet-Turle ayant dessiné la bordure + balle(couleur, epaisseur, dim) : Crée et renvoie un objet-Turtle qui servira à tracer le cercle --> Fonctions sans réponse (modification de l'état par effet de bord) + deplacer(feutre, x, y, angle) : Déplace en (x,y) et réoriente en fonction de angle le feutre + effacer(feutre) : Efface le dessin réalisé avec ce feutre + dessiner(feutre) : Dessine un cercle avec le feutre + gerer_collision_mur(feutre) : Renvoi l'angle d'orientation après cas de contact avec une paroi verticale + gerer_collision_sol(feutre) : Renvoi l'angle d'orientation après cas de contact avec une paroi horizontale + bouger(feutre) : Gère le déplacement d'une des balles + deplacer_image(feutre) : Bouge le dessin associé à ce feutre + lancer_animation() : Crée l'application graphique, les balles et lance l'animation ''' # Partie Importation import turtle as trt import random as rd # Partie Fonctions d'interface def application_graphique(dim) : '''Créé et renvoie la référence d'une application Turtle''' fenetre = trt.Screen() # création d'un objet-application fenetre.setup(dim*3,dim*3) fenetre.tracer(0) # NEW: affiche uniquement par update return fenetre def bordure(dim) : '''Crée et renvoie un objet-Turle ayant dessiné la bordure''' feutre = trt.Turtle() # Création de l'objet-Turtle feutre.color('black') feutre.pensize(10) deplacer(feutre, -dim, -dim, 0) for etape in range(4) : feutre.forward(2*dim) feutre.left(90) return feutre def balle(couleur, epaisseur, dim) : '''Crée et renvoie un objet-Turtle qui servira à tracer le cercle''' feutre = trt.Turtle() feutre.color(couleur) feutre.pensize(epaisseur) feutre.speed(0) x0 = rd.randint(-dim,dim) y0 = rd.randint(-dim,dim) angle = rd.randint(0,359) deplacer(feutre, x0, y0, angle) feutre.hideturtle() # on n'affiche que les dessins, pas la tortue return feutre def deplacer(feutre, x, y, angle) : '''Déplace en (x,y) et réoriente en fonction de angle le feutre''' feutre.penup() # On lève la pointe feutre.setpos(x, y) # On déplace le crayon feutre.setheading(angle) # On le tourne dans le bon sens feutre.pendown() # On abaisse la pointe def effacer(feutre) : '''Efface le dessin réalisé avec ce feutre''' feutre.clear() def dessiner(feutre) : '''Dessine un cercle avec le feutre''' feutre.circle(5, 360) def gerer_collision_mur(feutre) : '''Renvoi d'langle d'orientation après cas de contact avec une paroi verticale''' angle = feutre.heading() angle = 180 - angle feutre.setheading(angle) def gerer_collision_sol(feutre) : '''Renvoi l'angle d'orientation après cas de contact avec une paroi horizontale''' angle = feutre.heading() angle = -angle feutre.setheading(angle) def bouger(feutre) : '''Gère le déplacement d'une des balles''' # Gestion de la coordonné x et des risques de collisions avec les murs x = feutre.xcor() if x > 200 or x < -200 : gerer_collision_mur(feutre) # Gestion de la coordonné y et des risques de collisions avec plafond / sol y = feutre.ycor() if y > 200 or y < -200 : gerer_collision_sol(feutre) # Déplacement une fois que le feutre a été tourné dans une nouvelle direction feutre.forward(1) def deplacer_image(feutre) : '''Bouge le dessin associé à ce feutre''' effacer(feutre) # on efface le dessin précédent réalisé avec ce feutre bouger(feutre) # on déplace le feutre dessiner(feutre) # on réalise le dessin suivant def lancer_animation() : '''Crée l'application graphique, les balles et lance l'animation''' dim = 200 # De base, on veut un terrain de jeu de 200 x 200 app = application_graphique(dim) # création de l'application graphique b = bordure(dim) # création de la bordure s1 = balle('red', 3, dim) # création des balles s2 = balle('blue', 3, dim) s3 = balle('green', 3, dim) s4 = balle('magenta', 3, dim) s5 = balle('orange', 3, dim) while True : # BOUCLE INFINIE ! deplacer_image(s1) # gère le déplacement de la balle 1 deplacer_image(s2) deplacer_image(s3) deplacer_image(s4) deplacer_image(s5) app.update() # NEW : on affiche le ou les dessins juste à ce moment

2 - Equivalence

Nous avons vu que :

  • La boucle bornée POUR / FOR est programmable naturellement en Python avec un for.
  • La boucle non bornée TANT QUE / WHILE est programmable naturellement avec un while.

Néanmoins, on peut remplacer les boucles FOR par des boucles TANT QUE et donc dans les programmes les for par des while.

Ce n'est pas "naturel" mais on peut.

Deux fonctions qui de l'extérieur donneront la même chose :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
def somme(n) : '''Renvoie la somme de 1 + 2 + 3... jusqu'à n.''' reponse = 0 for x in range(n+1) : # ou range(0, n+1, 1) reponse = reponse + x return reponse def somme2(n) : '''Renvoie la somme de 1 + 2 + 3... jusqu'à n.''' reponse = 0 x = 0 while x < n+1 : reponse = reponse + x x = x + 1 return reponse

05° Tester les deux versions pour vérifier qu'elles font bien la même chose.

On devrait bien calculer ici 1 + 2 + 3 + 4 + 5 = 15.

>>> somme(5) 15 >>> somme2(5) 15

On remarquera néanmoins qu'utiliser un while alors que l'algorithme théorique montre qu'on va au bout du calcul à chaque fois (boucle bornée) crée au final un code plus compliqué.

  • Il faut recréer une variable de boucle x
  • Il ne faut pas oublier de l'augmenter de 1 à chaque tour de boucle, sinon on crée une boucle infinie !

Voyons maintenant l'inverse : on peut en réalité coder les boucles non bornées en utilisant un TANT QUE mais également à l'aide d'un POUR. Cela veut dire qu'en terme de programmation, on peut remplacer les while par des for. Mais là, c'est plus compliqué : il faudra l'associer par exemple à la propriété des return dans les fonctions : on sort immédiatement. Cela permettra d'interrompre le déroulement normal de la boucle non bornée qui n'est pas censée s'interrompre justement !

Exemple : deux versions d'une fonction qui renvoie le plus petit carré strictement inférieur à l'entier n que vous avez transmis : on transmet 105, elle renvoie 100 par exemple. Pour 100, elle renvoie 81.

1 2 3 4 5 6 7 8 9 10 11 12 13
def plus_petit_carre_en_dessous(n) : '''Renvoie le plus petit carré inférieur au paramètre n (n>0)''' x = 0 while x*x < n : x = x + 1 x = x - 1 return x*x def plus_petit_carre_en_dessous2(n) : '''Renvoie le plus petit carré inférieur au paramètre n (n>0)''' for x in range(n) : if x*x > n : return (x-1)*(x-1)

06° Tester les deux versions du programme en faisant les appels pour une même valeur. 27 devrait provoquer le renvoi de 25 par exemple.

>>> plus_petit_carre_en_dessous2(27) 25 >>> plus_petit_carre_en_dessous2(27) 25

Pour finir et bien comprendre qu'on peut implémenter les boucles des deux façons, proposer une fonction-procédure utilisant un for dont l'effet sera similaire à la fonction ci-dessous, qui contient un while.

1 2 3 4 5 6
def afficher() : x = 15 while x < 26 : print(x) x = x + 1 return x-1

Attention au retour final. La fonction native print n'est là que pour comprendre ce qui se passe sans utiliser le mode pas à pas.

...CORRECTION...

1 2 3 4
def afficher() : for x in range(15,26,1) : print(x) return x

3 - Exos

✎ 07° Expliquer étape par étape comment fonctionne la fonction carre_inf qui renvoie le nombre carré juste inférieur au nombre entier naturel n fourni en paramètre.

Pour 10 à 15, la fonction renvoie 9 (3*3).

Pour 16 à 24, la fonction renvoie renvoie 16 (4*4).

1 2 3 4 5 6 7 8 9 10 11 12 13 14
def carre_inf(n) : '''Fonction qui renvoie le carré juste inférieur ou égal à n ::param n(int) :: le nombre dont on cherche le carré juste inférieur ::return (int) :: la valeur du carré juste inférieur ou égal à n ''' nombre = 0 while (nombre+1)*(nombre+1) <= n : nombre = nombre + 1 return nombre*nombre x = carre_inf(10) print(x) x2 = carre_inf(18) print(x2)

✎ 08° Même question mais avec la fonction carre_inf2 qui fait pareil mais avec un code légerement différent.

Pour 10 à 15, la fonction renvoie toujours 9 (3*3).

Pour 16 à 24, la fonction renvoie toujours renvoie 16 (4*4).

1 2 3 4 5 6 7 8 9 10 11 12 13 14
def carre_inf2(n) : '''Fonction qui renvoie le carré juste inférieur ou égal à n ::param n(int) :: le nombre dont on cherche le carré juste inférieur ::return (int) :: la valeur du carré juste inférieur ou égal à n ''' nombre = 0 while nombre*nombre <= n : nombre = nombre + 1 return (nombre-1)*(nombre-1) x = carre_inf2(10) print(x) x2 = carre_inf2(18) print(x2)

4 - FAQ

Rien pour l'instant

Les conditions IF, les boucles bornées FOR et non bornées WHILE seront désormais utilisées de façon régulière.

D'autres activités ou articles les détailleront car il y a encore beaucoup à dire à leur sujet.

Activité publiée le 01 11 2020
Dernière modification : 01 11 2020
Auteur : ows. h.