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 instructions 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.

Documents de cours : open document ou pdf

Evaluation ✎ : questions 19-21-22-23-24

Evaluation ✌ : question 20

1 - Rappel préalable : le module random

(RAPPELS) 1 Fonction randint() du module random

A - Fonction randint()

Le module random contient beaucoup de fonctions permettant de gérer l'aléatoire.

Il existe notamment la fonction randint() qui permet de générer un integer aléatoire entre une valeur de départ incluse et une valeur de fin incluse.

Cette fonction doit recevoir deux entrées : la première est la valeur de départ autorisée, et la deuxième la valeur finale autorisée. Ainsi en fournissant 5 et 8, on génère un nombre aléatoire entre 5 et 8 pouvant valoir 5, 6, 7 ou 8.

Si on veut simuler un d6, un dé à 6 faces, il suffit de taper ceci (notez bien qu'on importe randint() depuis le module, pas tout le module) :

>>> from random import randint >>> randint(1, 6) 4 >>> randint(1, 6) 1 >>> randint(1, 6) 6 >>> randint(1, 6) 2
B - Exemple d'un programme créant des notes au hasard

Ci-dessous, un programme générant 10 notes aléatoires entre 0 et 20 (notez bien qu'ici, nous importons uniquement le module random) :

1 2 3 4 5 6 7
import random # Importation du module notes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # Tableau de dix entiers for i in range(10): # Pour i variant de 0 à 9 notes[i] = random.randint(0, 20) # Case i contient un nbr entre 0 et 20 print(notes) # Affiche lorsque la boucle est finie
>>> %Run portee.py [19, 14, 5, 7, 3, 18, 16, 1, 8, 7] >>> %Run portee.py [4, 7, 13, 13, 11, 16, 4, 7, 9, 12]
C - Remarque importante

Notez bien :

  • qu'avec randint(2, 5), la valeur finale est incluse dans les choix possibles : 2, 3, 4 ou 5.
  • qu'avec range(5), la valeur "finale" 5 n'est pas incluse : nous allons générer progressivement 0, 1, 2, 3, 4 mais PAS 5.

A vous de le savoir, cela a été défini comme cela.

01° Taper ceci dans la console pour comprendre comment réaliser un tirage aléatoire entre 5 et 10 : le résultat sera toujours soit 5, soit 6, soit 7, soit 8, soit 9, soit 10.

>>> import random >>> random.randint(5, 10) ? >>> random.randint(5, 10) ? >>> random.randint(5, 10) ?

Remarque : attention, ici le 10 est une valeur incluse dans les choix possible. Ce n'est pas le cas avec range, où la valeur "finale" est exclue.

Questions

  1. Que doit-on taper dans la console pour obtenir un résultat simulant un dé à 6 faces ?
  2. Réaliser une fonction de() recevant un paramètre nb qui est le nombre de faces d'un dé et qui renvoie un nombre entier aléatoire entre 1 et nb.
  3. 1 2 3 4 5 6 7 8 9 10
    import random def de(nb): """Lance un dé à nb faces et renvoie le résultat :: param nb(int) :: un entier >= 1 :: return (int) :: un entier compris entre 1 et nb. """ pass

    PRECONDITION : lors de l'appel de la fonction, il faudra envoyer un nombre nb qui soit un entier positif strictement supérieur à 1.

...CORRECTION...

>>> random.randint(1, 6)
1 2 3 4 5 6 7 8 9 10
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat :: param nb(int) :: un entier >= 1 :: return (int) :: un entier compris entre 1 et nb. """ return random.randint(1, nb)

2 - Boucle non bornée tant que / while

Erreur à éviter pendant la séance

Comme pour if for def, c'est l'indentation de 4 espaces qui permet d'indiquer quelles sont les instructions du bloc à exécuter en boucle.

Veillez donc à faire attention à vos indentations.

Nous allons donc parler de la boucle TANT QUE ou WHILE en anglais.

(RAPPELS) 2.1 Boucle TANT QUE

A - Principe

Le mot-clé while réalise une boucle sans prévoir à l'avance combien de tours on va la réaliser exactement. C'est l'indentation qui renseigne sur le bloc à réaliser :

L1 début du programme L2 L3 while condition de poursuite: L4 instruction A L5 instruction B L6 instruction C L7 L8 suite du programme

A chaque début de tour, on teste la condition de poursuite :

  • si poursuite est True en L3, on réalise L4-L5-L6 puis on revient en ligne 3.
  • si poursuite est False en L3, on saute en L8.

Il s'agit donc d'une sorte de mélange entre un if et un for : tant que la condition est vraie, on réalise le bloc et on revient au début de la boucle.

B - Un classique : valeur de seuil

Le programme suivant permet de répondre à ce problème :

  • on considère un gain initial de 10 euros.
  • On double le gain tous les mois.
  • Tant qu'il y a moins de 5000 euros, on laisse la somme sur le compte.
  • Au bout de combien de mois va-t-on atteindre 5000 euros ou plus ?
1 2 3 4 5 6 7 8 9
gain = 10 nb_mois = 0 while gain < 5000: gain = gain * 2 nb_mois = nb_mois + 1 print(gain) print(nb_mois)

Si on lance le programme :

>>> %Run boucleTQ.py 5120 9

On voit qu'on atteint 5120 euros au bout de 9 mois. Un compte bancaire qui rapporte 100% tous les mois, c'est rentable mais malheureusement totalement imaginaire.

Traduction et déroulé

  • L1 (gain=10) - L2 (nb_mois=0)
  • L4 (condition True car gain vaut 10) - L5 (gain=10*2=20) - L6 (nb_mois=0+1=1)
  • L4 (condition True car gain vaut 20) - L5 (gain=20*2=40) - L6 (nb_mois=1+1=2)
  • ...
  • L4 (condition True car gain vaut 1280) - L5 (gain=1280*2=2560) - L6 (nb_mois=8+1=9)
  • L4 (condition True car gain vaut 2560) - L5 (gain=2560*2=5120) - L6 (nb_mois=9+1=10)
  • L4 (condition False) : FIN DU TANT QUE.
  • L8-L9 : affichage des variables

Commençons par un exemple simple : réaliser l'équivalent d'une boucle FOR partant de 0 jusqu'à atteindre 40, de 10 en 10 : 0 puis 10 puis 20 puis 30 puis 40.

02° Placer le programme sur Python Tutor. On notera qu'il existe une connexion automatique depuis le menu Executer de Thonny. Observer son fonctionnement.

  1. Pourquoi peut-on dire qu'une boucle TANT QUE est un mélange de boucle POUR et d'instruction conditionnelle ?
  2. Retrouver la succession des lignes suivies par l'interpréteur Python (vous pouvez lancer l'animation ci-dessous pour vérifier votre réponse).
1 2 3 4 5 6 7
x = 0 while x < 40: print(x) x = x + 10 print("Fin du TANT QUE")

CLIQUEZ ICI POUR VOIR LE DEROULEMENT SEQUENTIEL

x :

x < 40 ? :

...CORRECTION...

Avant chaque nouveau tour de boucle, on évalue la condition, un peu comme avec un IF.

Si la condition renvoie VRAI, on réalise l'action puis on renvoie au début de la boucle où on va évaluer une nouvelle fois la condition pour voir si on recommence, ou pas.

Il s'agit donc d'une sorte de "boucle conditionnelle".

Voici le déroulé :

  1. L1 (x=0)
  2. L3 (tour de boucle car 0 < 40) - L4 - L5(x=10)
  3. L3 (tour de boucle car 10 < 40) - L4 - L5(x=20)
  4. L3 (tour de boucle car 20 < 40) - L4 - L5(x=30)
  5. L3 (tour de boucle car 30 < 40) - L4 - L5(x=40)
  6. L3 (arrêt de TQ car 0 < 40) - L7

03° Réaliser un programme utiliser un while pour afficher les nombres 15, 35, 55, 75, 95... jusqu'à atteindre 20015 pour le dernier affichage. De 20 en 20 donc.

...CORRECTION...

1 2 3 4 5 6 7
x = 15 while x < 20016: print(x) x = x + 20 print("Fin du TANT QUE")

04° Quelle est l'erreur présente ici, qui provoque une boucle infinie ?

1 2 3 4 5 6 7
x = 0 while x < 31: print(x) x = x + 10 print("FIN DU TANT QUE")

...CORRECTION...

Il y a une mauvaise indentation de la ligne 5 : l'incrémentation de la variable x n'est plus à l'intérieur de la boucle.

On ne va exécuter cette ligne qu'après être sorti de la boucle.

Le problème ? La condition pour continuer dépend de x, or on ne change jamais sa valeur lors de l'exécution de la boucle.

x vaut 0 au début et garde cette valeur... à l'infini.

Comme vous pouvez le voir, cette structure est plus complexe à utiliser qu'une boucle FOR :

  • Ligne 1 : il faut d'abord initialiser la variable de boucle x.
  • Ligne 3 : il faut préciser fournir la condition de poursuite.

Question : pourquoi vouloir utiliser un while alors qu'on peut utiliser un for qui a l'air plus simple à utiliser ?

1 2 3 4 5
x = 0 while x < 31: print(x) x = x + 10
1 2
for x in range(0, 31, 10): print(x)

Réponse : Un TANT QUE permet de faire plus de choses qu'une boucle POUR.

2.2 Boucle bornée ou boucle non bornée

  • Si on connait à l'avance le nombre de fois où on va devoir agir, on peut utiliser une boucle POUR.
  • La boucle POUR est une boucle bornée.

  • Si on ne peut pas savoir à l'avance le nombre de fois où on va devoir agir, on utilise une boucle TANT QUE.
    C'est la condition de poursuite fournie qui va permettre de faire le choix de continuer ou pas.
  • Si l'action peut potentiellement se dérouler à l'infini, il faut utiliser une boucle TANT QUE.
  • C'est pour cela que la boucle TANT est une boucle non bornée.

Exemple 1

Un exemple concret avec le remplissage de votre sac pour aller au lycée :

1 2 3 4 5
while vous_avez_encore_des_choses_a_prendre: prendre_objet_suivant() mettre_cet_objet_dans_votre_sac() partir_au_lycée()

Vous allez donc faire les lignes 1-{2-3} en boucle tant que vos affaires ne sont pas totalement faites.

Exemple 2

Un autre exemple : le passage en cuisine pour se nourrir avec l'utilisation du prédicat j_ai_faim() qui renvoie True si vous avez encore faim.

1 2 3 4 5 6 7 8
rentrer_en_cuisine() while j_ai_faim(): manger_un_truc() boire__un_verre() attendre_un_peu() quitter_la_cuisine()

Vous allez donc faire les lignes 3-{4-5-6} en boucle à chaque fois que la fonction prédicat j_ai_faim() renvoie encore et encore True.

05-A° Mettre l'exemple ci-dessous en mémoire. Lancer le programme.

Quelques explications : à chaque tour de boucle

  • Ligne 10 : on tire 3 fois un dé à 6 faces et on calcule le total obtenu.
  • Ligne 11 : on affiche ce total. Le total est d'ailleurs compris entre 3 (on tire 3 fois un 1 par malchance) et 18 (on tire 3 fois un 6 par chance).

La condition de poursuite de la boucle est d'avoir obtenu un total inférieur à 16 sur les dés : tant que le total est inférieur à 16, on relance les dés.

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) total = 0 while total < 16: total = de(6) + de(6) + de(6) print(total) print(f"La valeur obtenue avec les 3 trois dés est {total}")

Questions

  1. Aurait-on pu faire pareil avec une boucle bornée ?
  2. Pourquoi avoir placé 0 initialement dans la variable total sur la ligne 7 ?

...CORRECTION...

  1. La boucle bornée n'est pas possible ici à cause du côté aléatoire : on ne sait absolument pas combien de lancer de dés il va falloir faire pour obtenir 16, 17 ou 18 et ainsi sortir de la boucle.
  2. On place 0 car on est ainsi certain de pouvoir réaliser au moins une fois la boucle.

05-B° Erreur courante n°1. Quelqu'un a mal copié le programme précédent et a oublié de mettre la ligne 7.

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) while total < 16: total = de(6) + de(6) + de(6) print(total) print(f"La valeur obtenue avec les 3 trois dés est {total}")

Question : Expliquer l'erreur d'exécution qui apparaît alors.

while total < 16: NameError: name 'total' is not defined

...CORRECTION...

Lorsqu'on arrive en ligne 9, on demande à l'interpréteur d'évaluer l'expression total < 16. Or, la variable total n'existe pas en mémoire puisqu'on ne l'a pas initialisée sur la ligne 7 manquante.

L'erreur typique vient du fait qu'en lisant vite, on voit qu'il y a une affectation de total en ligne 10. Oui, mais cela veut bien dire qu'on ne peut pas l'évaluer en ligne 9.

05-C° Erreur courante n°2. Quelqu'un a encore mal copié le programme précédent.

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) totol = 0 while total < 16: total = de(6) + de(6) + de(6) print(total) print(f"La valeur obtenue avec les 3 trois dés est {total}")

Question : Expliquer l'erreur d'exécution qui apparaît alors.

while total < 16: NameError: name 'total' is not defined

...CORRECTION...

Même principe : total n'existe pas car ligne 7 on crée la variable totol... Pour un humain, cela pourrait passer. Pas pour l'ordinateur qui n'a aucune compréhension de ce que vous voulez faire.

2.3 Première évaluation de la condition

Les variables nécessaires à l'évaluation du TANT QUE doivent toutes être définies avant la ligne du TANT QUE.

Si la condition est basée sur une variable qui est calculée dans le bloc de la boucle elle-même, comment faire ? Initialiser cette variable avec une "fausse première valeur" de façon à rentrer dans la boucle au départ.

Exemple : en ligne 7, on place 0 dans total, ce qui permet de valider la condition de poursuite : le total est inférieur à 16.

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) total = 0 while total < 16: total = de(6) + de(6) + de(6) print(total) print(f"La valeur obtenue avec les 3 trois dés est {total}")

Un exemple sur le thème économique :

06-A° Imaginons que vous ayez une somme d'argent à investir. Tous les mois, votre placement vous rapporte 2% d'intérêt par mois (cela veut dire que la somme d'argent est multipliée par 1,02 tous les mois).

Nous voudrions savoir au bout de combien de mois vous allez avoir 2000 euros ou plus sur ce compte.

Question : comment écrire la condition_de_poursuite de l'action laisser_l_argent_un_mois_de_plus() ?

  1. while argent > 2000:
        laisser_l_argent_un_mois_de_plus()
  2. while argent >= 2000:
        laisser_l_argent_un_mois_de_plus()
  3. while argent < 2000:
        laisser_l_argent_un_mois_de_plus()
  4. while argent <= 2000:
        laisser_l_argent_un_mois_de_plus()

...CORRECTION...

On veut 2000 euros ou plus pour arrêter.

Donc, il faut continuer à laisser l'argent tant qu'il y a moins de 2000 euros sur le compte. Il faut donc choisir la proposition C :

while argent < 2000:

    laisser_l_argent_un_mois_de_plus()

06-B° Voici un algorithme de calcul de la somme d'argent au fil des mois :

  1. On crée un compteur de mois valant 0 initialement
  2. On définit la somme initiale d'argent sur le compte (1200 euros sur notre exemple).
  3. TANT QUE l'argent sur le compte est inférieur à 2000 euros :
    1. On multiplie par 1.02 l'argent sur le compte (l'intérêt est de 2 % sur l'exemple)
    2. On incrémente de 1 le compteur de mois
  4. Une fois sorti de la boucle, on affiche la valeur du compteur qui correspond au nombre de mois nécessaires pour atteindre au moins 2000 euros sur le compte.

Questions :

  1. Comprendre le principe de l'algorithme
  2. Calculer (sans faire de programme) la somme sur le compte après 3 mois (3 tours de boucle donc) en plaçant 1200 euros initialement. On prendra un taux d'intérêt de 2 % par mois. Réalisez les calculs en utilisant la console de Python comme une simple calculatrice.

...CORRECTION...

On commence avec un compteur nb_mois faisant référence à 0 et une variable argent faisant référence à 1200 euros.

On fait un premier tour de boucle puisque argent vaut moins que 2000 euros.

  • On incrémente nb_mois qui passe à 1
  • La nouvelle somme d'argent vaut 1200*1.02, soit 1224.0 euros.
  • On affiche la somme sur le compte, 1224.0 euros.
  • Le programme attend que l'utilisateur valide en appuyant sur ENTREE

On fait un deuxième tour de boucle puisque argent vaut moins que 2000 euros.

  • On incrémente nb_mois qui passe à 2
  • La nouvelle somme d'argent vaut 1224.0*1.02, soit 1248.48 euros.
  • On affiche la somme sur le compte, 1248.48 euros.
  • Le programme attend que l'utilisateur valide en appuyant sur ENTREE

On fait un troisième tour de boucle puisque argent vaut moins que 2000 euros.

  • On incrémente nb_mois qui passe à 2
  • La nouvelle somme d'argent vaut 1248.48.0*1.02, soit 1273.4496000000001 euros.
  • On affiche la somme sur le compte, 1273.4496000000001 euros.
  • Le programme attend que l'utilisateur valide en appuyant sur ENTREE

06-C° Lancer le programme ci-dessous pour voir comment il fonctionne. Il faut taper sur ENTREE pour confirmer la poursuite du déroulement.

1 2 3 4 5 6 7 8 9 10
nb_mois = 0 argent = 1200 while argent < 2000: nb_mois = nb_mois + 1 argent = argent * 1.02 print(f"Il y a maintenant {argent} euros sur le compte") input() print(f"Pour atteindre cette somme, il a fallu {nb_mois} mois.")

Explication de la ligne 8 : la fonction native input() permet simplement ici d'attendre l'appui sur la touche ENTREE pour permettre la suite de l'exécution. Le programme ne repartira donc en ligne 4 qu'après votre appui sur ENTREE.

Question finale

Trouver les lignes du programme qui correspondent aux actions suivantes :

  1. Ligne ? : On crée un compteur de mois valant 0 initialement
  2. Ligne ? : On définit la somme initiale sur le compte.
  3. Ligne ? : Tant que l'argent sur le compte est inférieur à 2000 euros :
    1. Ligne ? : On calcule la nouvelle somme d'argent sur le compte à la fin du mois
    2. Ligne ? : On affiche la somme d'argent sur le compte
    3. Ligne ? : On incrémente le compteur de mois de 1
    4. Ligne ? : On crée une pause en demandant à l'utilisateur d'appuyer sur ENTREE
  4. Ligne ? : Une fois sorti de la boucle, on affiche la valeur du compteur qui correspond au nombre de mois nécessaire pour avoir 2000 euros sur le compte.

...CORRECTION...

  1. Ligne 1 : On crée un compteur de mois valant 0 initialement
  2. Ligne 2 : On définit la somme d'argent initialement sur le compte.
  3. Ligne 4 : Tant que l'argent sur le compte est inférieur à 2000 euros :
    1. Ligne 6 : On calcule la nouvelle somme d'argent sur le compte à la fin du mois
    2. Ligne 7 : On affiche la somme d'argent sur le compte
    3. Ligne 5 : On incrémente le compteur de mois de 1
    4. Ligne 8 : On crée une pause en demandant à l'utilisateur d'appuyer sur ENTREE
  4. Ligne 10 : Une fois sorti de la boucle, on affiche la valeur du compteur qui correspond au nombre de mois nécessaire pour avoir 2000 euros sur le compte.

2 % par mois c'est beaucoup ou pas ?

Pour répondre à la question, vous devriez vous rendre compte maintenant, qu'il suffit de multiplier la somme initiale par 1.02 pendant 12 mois :

>>> 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 * 1.02 1.2682417945625455

Et cela donne presque 1.27, soit un taux d'intérêt de 27 % par an !

Note : plutôt que de taper les multiplications, vous pouvez également juste demander à Python de calculer 1.02 puissance 12.

>>> 1.02 ** 12 1.2682417945625455
2.4 TANT QUE : condition d'arrêt ou de poursuite

False est la valeur complémentaire de True. On passe de l'une à l'autre à l'aide du mot-clé not.

A - Lien entre poursuite et arrêt

Pour résoudre un problème avec un TANT QUE, on utilise une condition de poursuite.

1 2
while poursuite: faire_des_chose()

Lorsqu'on réfléchit, on tombe parfois plutôt sur une condition d'arrêt de la boucle... Comment faire alors ? C'est facile :

  • Si poursuite est True, c'est que arret est False.
  • Si poursuite est False, c'est que arret est True.

On peut donc trouver la condition de poursuite en inversant la condition d'arrêt avec not.

B - Deux écritures de la boucle TANT QUE

Pour décrire un même TANT QUE, on a donc deux possibilités d'écriture en fonction de ce qui vient d'abord à l'esprit :

  • Si vous connaissez la condition de poursuite de la boucle :
  • 1 2
    while poursuite: faire_des_chose()
  • Si vous connaissez la condition d'arret de la boucle :
  • 1 2
    while not arret: faire_des_chose()
C - Exemple
  1. si on sait qu'on doit continuer tant qu'on a moins de 2000 euros sur le compte :
  2. 1
    while argent < 2000:
  3. cela veut dire qu'on doit s'arrêter dès qu'on a 2000 ou plus :
  4. 1
    while not argent >= 2000:

Parfois on trouve facilement les deux conditions (arrêter ou continuer). Parfois, seule l'une des deux conditions est facile à trouver.

C'est pour cela que connaitre la technique du not est important.

D - Cas typique qui cause des erreurs
  • L'opérateur complémentaire de > est <=
  • L'opérateur complémentaire de >= est <

07° La condition de poursuite de la boucle était d'avoir obtenu un résultat r strictement inférieur à 16 sur les dés.

La condition d'arrêt est donc d'avoir un résultat de 16 ou plus avec nos dés.

Action : reécrire le TANT QUE en utilisant plutôt la condition d'arrêt. Tester pour vérifier que le programme a exactement le même fonctionnement.

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) r = 0 while r < 16: r = de(6) + de(6) + de(6) print(r) print(f"La valeur obtenue avec les 3 trois dés est {r}")

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13
import random def de(nb): """Lance un dé à nb faces et renvoie le résultat""" return random.randint(1, nb) r = 0 while not r >= 16: r = de(6) + de(6) + de(6) print(r) print(f"La valeur obtenue avec les 3 trois dés est {r}")

08° On veut créer un jeu vidéo.

  • Le joueur commence sur un premier niveau de jeu relativement facile pour se faire la main.
  • Lorsqu'il a acquis 500 points, il passera au niveau 2.
  • Lorsqu'il aura acquis 1000 points, il passera au niveau 3.

Question : Quel est le programme imaginaire (A ou B) adapté à la situation décrite ?

Programme A :

1 2 3 4 5 6 7 8 9
xp = 0 while xp > 500: jouer_niveau_1() while xp > 1000: jouer_niveau_2() jouer_niveau_3()

Programme B :

1 2 3 4 5 6 7 8 9
xp = 0 while xp < 500: jouer_niveau_1() while xp < 1000: jouer_niveau_2() jouer_niveau_3()

On imagine bien entendu que les fonctions jouer_niveau_X() parviennent à modifier la valeur de la variable xp.

...CORRECTION...

Il s'agit de la solution B : on doit changer sur le niveau 1 TANT QUE les xp sont inférieurs à 500.

Voyons maintenant un exemple plus graphique :

  • On place une tortue avec un angle d'orientation et des coordonnées (x, y) aléaotoires.
  • On lui demande d'avancer TANT QUE sa coordonnée x est inférieure à 300.

09° Placer ce code en mémoire. Lancer pour 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 25
import turtle as trt import random as rd def stylo() -> trt.Turtle: """Crée, déplace et oriente un crayon aléatoirement""" 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 feutre = trt.Turtle() # on crée une tortue 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 return feutre # Instructions du programme principal s = stylo() # on crée un stylo-tortue while s.xcor() < 300: s.forward(1)

Questions

Remarque ligne 24 : xcor() est une méthode des objets Turtle permettant de récupérer la coordonnée x de l'objet.

  1. Quelles sont les valeurs initiales possibles des valeurs initiales en x et de l'angle de base du "stylo"  stocké dans la variable s ?
  2. Avec la valeur initiale de x imposée en ligne 7, pourquoi est-on certain de parvenir à rentrer au moins une fois dans la boucle TANT QUE ?
  3. Avec l'angle créé en ligne 9 (entre -45° et +45°), est-on certain de percuter le "mur" de droite ?

...CORRECTION...

Ligne 7 : x = rd.randint(-200, 200) : on voit que x va avoir aléatoirement une valeur entre -200 et +200, donc nécessairement inférieure à 300. 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 9) un angle entre -45° et +45° : on va donc percuter le mur (on ne risque pas d'avoir un stylo qui remonte avec un angle de +90° ou qui redescend avec un angle de -90°).

10° Remplacer la ligne 9 par ceci : l'angle est maintenant aléatoire entre 90° et 180°. La tortue va donc se diriger quelque part à gauche, mais jamais vers la droite.

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

Question

  • Pourquoi obtient-on une boucle TANT QUE infinie ?

...CORRECTION...

On ne pourra jamais percuter le mur de droite car la tortue part à gauche...

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

D'où le terme "BOUCLE INFINIE" : le programme va faire la séquence L23-L24-L23-L24-L23... sans jamais pouvoir s'interrompre.

Voyons maintenant le principe de la boucle infinie.

2.5 - Boucle infinie

Pour créer une boucle qui ne s'arrête jamais, on choisit une condition de poursuite qui ne pourra jamais être évaluée à False. Commme quoi ? C'est simple : True !

1 2 3
while True: faire_un_truc() faire_un_autre_truc()

La condition de poursuite étant juste True, on va donc effectuer L2-L3 à l'infini.

Ce bouclage peut être voulu (comme sur un système embarqué qui ne doit jamais arrêter son programme) ou involontaire. Si c'est involontaire, c'est le drame car on ne pourra pas prendre la main à moins de stopper le programme.

11° Mettre le programme en mémoire.

Question 1 : Quelle est la seule instruction de ce programme de 128 lignes ?

Question 2 : Localiser la boucle et vérifier que l'animation ne s'arrête jamais. Ca peut être long :o)

Question 3 : Trouver le nom de chacune des fonctions qui permettent de gérer ces tâches :

  1. effacer le dessin de cercle réalisé par l'un des crayons
  2. déplacer le crayon de la balle
  3. redessiner la balle à cet endroit

Question 4 (optionnelle) : Si vous vous sentez assez à l'aise, prenez le temps de comprendre vraiment comment le programme fonctionne.

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 124 125 126 127 128
"""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) : Modifie l'angle d'orientation après cas de contact avec une paroi verticale + gerer_collision_sol(feutre) : Modifie l'angle d'orientation après cas de contact avec une paroi horizontale + gerer_deplacement(feutre) : déplace et oriente le crayon servant à dessiner la balle, en simulant un déplacement élémentaire + deplacer_balle(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:int) -> trt.Screen: """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:int) -> trt.Turtle: """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:str, epaisseur:int, dim:int) -> trt.Turtle: """Crée et renvoie un objet-Turtle qui servira à tracer une balle""" 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:trt.Turtle, x:int, y:int, angle:int) -> None: """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:trt.Turtle) -> None: """Efface le dessin réalisé avec ce feutre""" feutre.clear() def dessiner(feutre:trt.Turtle) -> None: """Dessine un cercle avec le feutre""" feutre.circle(5, 360) def gerer_collision_mur(feutre:trt.Turtle) -> None: """Modifie l'angle d'orientation après contact avec une paroi verticale""" angle = feutre.heading() angle = 180 - angle feutre.setheading(angle) def gerer_collision_sol(feutre:trt.Turtle) -> None: """Modifie l'angle d'orientation après contact avec une paroi horizontale""" angle = feutre.heading() angle = -angle feutre.setheading(angle) def gerer_deplacement(feutre:trt.Turtle) -> None: """déplace et oriente le crayon servant à dessiner la balle, en simulant un déplacement élémentaire""" # 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_balle(feutre:trt.Turtle) -> None: """Bouge le dessin associé à ce feutre""" effacer(feutre) # on efface le dessin précédent réalisé avec ce feutre gerer_deplacement(feutre) # on modifie les données de la balle dessiner(feutre) # on met à jour l'affichage de la balle 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_balle(s1) # déplace la balle 1 deplacer_balle(s2) deplacer_balle(s3) deplacer_balle(s4) deplacer_balle(s5) app.update() # NEW : on affiche le ou les dessins juste à ce moment # Partie Importation lancer_animation()

...CORRECTION...

  1. C'est l'appel à lancer_animation() de la ligne 128. Les autres lignes ne sont que des déclarations de fonctions.
  2. Le TANT QUE générant la boucle infinie se trouve en ligne 117.
  3. Les fonctions sont
    • effacer() pour effacer le crayon
    • deplacer() pour déplacer le crayon
    • dessiner() pour dessiner un cercle

3 - Jeu interactif console

Nous allons réaliser un jeu demandant en boucle à l'utilisateur de trouver un nombre compris entre 0 et 100, tant qu'il ne trouve pas la bonne réponse.

Nous allons donc réaliser une interface entre notre programme et l'utilisateur humain et nous utiliserons la console, c'est plus facile que de construire une interface graphique.

  • ORDINATEUR vers UTILISATEUR en utilisant la console :
    • la fonction native print() affiche des choses dans la console.
  • UTILISATEUR vers ORDINATEUR en utilisant la console :
    • la fonction native input() renvoie ce que l'utilisateur tape sur le clavier. L'utilisateur valide sa réponse en appuyant sur ENTREE.

Bref, petit complément.

(RAPPELS) 3.1 Fonction input() pour récupérer les entrées clavier

(RAPPELS) 3.1.1 Principe fondamental

La fonction native input() récupère la réponse reçue au clavier sous forme d'un string. Pour valider la réponse, il faut appuyer sur entrée (sur l'exemple, on répond Bonjour).

>>> reponse = input() Bonjour >>> reponse 'Bonjour' >>> reponse * 3 'BonjourBonjourBonjour'

Le problème vient du fait que TOUT ce qu'on récupère depuis le clavier est interprété comme un string. D'où cette situation bizarre si on ne fait pas attention :

>>> note = input() 18 >>> note '18' >>> note * 2 '1818'
(RAPPELS) 3.1.2 Récupérer un entier ou un flottant

Puisqu'on ne récupère que des strings, il faut simplement tenter de convertir le string reçu en integer, en utilisant la fonction native int().

>>> reponse = int(input()) 5 >>> reponse 5 >>> reponse * 3 15
>>> reponse = float(input()) 5 >>> reponse 5.0 >>> reponse * 3 15.0
(RAPPELS) 3.1.3 Utilisation complète

Puisque input() permet de poser une question à l'utilisateur, on peut lui transmettre un string contenant la question voulue.

On peut ainsi taper ceci :
reponse = input("Veuillez fournir un nombre entier : ")

L'interpréteur Python va alloir faire trois choses

  1. Afficher votre message, comme si vous aviez utilisé un print() mais sans passage à la ligne automatique.
  2. Récupérer la réponse de l'utilisateur sous forme d'un string
  3. Placer ce string dans la variable reponse
>>> reponse = int(input("Veuillez fournir une note : ")) Veuillez fournir une note : 5 >>> reponse 5 >>> reponse * 3 15
Hors Programme  : et en cas d'impossibilité de conversion ?

Cela ne tombera pas le jour de l'examen ou dans un DS, mais c'est pratique et nous allons l'utiliser aujourd'hui. Rien à retenir, il suffit de voir un peu commment cela fonctionne.

Il est possible que le string récupéré ne soit pas interprétable en tant qu'entier. Dans ce cas, cela lève une exception :

>>> reponse = int(input("Veuillez fournir une note : ")) Veuillez fournir une note : cinq ValueError: invalid literal for int() with base 10: 'trois'

Le seul moyen de palier à ceci est d'utiliser quelques notions hors programme en NSI. Il s'agit de try except : si Python lève une exception pendant le bloc try, plutôt que d'interrompre brutalement le programme, il saute simplement dans le bloc except. On peut alors y gérer le problème.

1 2 3 4 5 6 7 8 9
def recuperer_une_note(): reponse = "" # "Fausse réponse" pour rentre dans le while while type(reponse) != int: # TQ reponse n'est pas un entier rep_clavier = input("Note : ") # récupère le choix de l'utilisateur try: # Essaye de faire ceci : reponse = int(rep_clavier) # transforme rep_clavier en int except: # Si cela déclenche une Exception pass # on ne fait rien, on passe... return reponse

Voici une utilisation possible en mode interactif (une fois la fonction mise en mémoire bien entendu)

>>> %Run portee.py >>> note = recuperer_une_note() Note : cinq Note : 5 >>> note 5

13° Tester ce programme qui parvient à transformer la donnée fournie par l'utilisateur en integer en utilisant la fonction native int().

1 2 3 4 5 6 7
nom = input("Quel est votre nom ? ") age = input("Quel est votre age ? ") age = int(age) print(f"Bonjour {nom}") print(f"Lorsque vous serez deux fois plus agé, vous aurez {age*2} ans !")

Et un exemple d'utilisation :

Quel est votre nom ? BobENTREE Quel est votre age ? 17ENTREE Bonjour Bob Lorsque vous serez deux fois plus agé, vous aurez 34 ans !

Commençons notre jeu de recherche d'un nombre aléatoire compris entre 0 et 100.

14° Regarder le programme fourni qui permet de jouer et dans lequel il faut trouver un nombre POSITIF. Il manque uniquement la "fausse" valeur initiale à placer dans la variable proposition en ligne 17.

Question : Expliquer pourquoi il n'est pas malin de mettre 0 sur cette ligne 17 cette fois.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# 1 - Importation import random # 2 - Déclaration des constantes MINI = 0 MAXI = 100 # 3 - Déclaration des fonctions def affichage(m, p): if m != p: print("Raté") else: print("Bien vu !") # 4 - Programme principal mystere = random.randint(MINI, MAXI) proposition = ??? while proposition != mystere: proposition = input("Quel est le nombre mystère à votre avis ? ") proposition = int(proposition) affichage(mystere, proposition) print("FIN")

...CORRECTION...

Mettre 0 n'est pas malin car il est possible que la bonne réponse soit 0 : ce nombre est aléatoirement possible.

Il faudrait placer un nombre impossible à obtenir, comme -1 par exemple : on précise que le nombre à trouver doit être positif.

✎ 15° Expliquer comment fonctionne la boucle proposée sur les lignes 19-22 :

  1. Utilise-t-on directement une condition de poursuite ou indirectement une condition d'arrêt ?
  2. dans quel cas va-t-on faire un tour de boucle ?
  3. dans quel cas va-t-on sortir de la boucle ?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
# 1 - Importation import random # 2 - Déclaration des constantes MINI = 0 MAXI = 100 # 3 - Déclaration des fonctions def affichage(m, p): if m != p: print("Raté") else: print("Bien vu !") # 4 - Programme principal mystere = random.randint(MINI, MAXI) proposition = -1 while not proposition == mystere: proposition = input("Quel est le nombre mystère à votre avis ? ") proposition = int(proposition) affichage(mystere, proposition) print("FIN")

✌ 16° Modifier la fonction affichage() pour obtenir un jeu plus facile :

  1. on donne des indices sur la valeur du nombre si la valeur n'est pas bonne. Voir l'exemple ci-dessous.
  2. le jeu ne s'arrête pas sur une erreur lorsqu'on tape quelque chose qui ne peut pas être interprété comme un entier.
  3. Finaliser correctement le programme en rajoutant la documentation (sous forme rapide) à la fonction affichage().
Quel est le nombre mystère à votre avis ? 50 Le nombre mystère est plus petit Quel est le nombre mystère à votre avis ? 25 Le nombre mystère est plus grand Quel est le nombre mystère à votre avis ? 37 Le nombre mystère est plus petit Quel est le nombre mystère à votre avis ? 31 Le nombre mystère est plus petit Quel est le nombre mystère à votre avis ? 28 Le nombre mystère est plus grand Quel est le nombre mystère à votre avis ? 29 Le nombre mystère est plus grand Quel est le nombre mystère à votre avis ? 30 Bien vu ! FIN

4 - Transformation d'une boucle en l'autre

4.1 TANT QUE : transformer un POUR en TANT QUE

Nous avons vu que la boucle bornée POUR est programmable naturellement en Python avec un for.

Néanmoins, on peut remplacer les for par des while.

Ce n'est pas "naturel", une boucle bornée devrait plutôt être implémentée avec for.

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_v1(n:int) -> int: """Renvoie la somme de 1 + 2 + 3... jusqu'à n.""" reponse = 0 for x in range(n+1): # on a donc x < n+1 reponse = reponse + x return reponse def somme_v2(n:int) -> int: """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

La version while est plus complexe puisqu'on doit écrire ce que le for réalise en sous-main :

  • Ligne 11 : on doit initialiser la variable de boucle x alors que c'est fait automatiquement avec for.
  • Ligne 12 : on doit transformer l'implicite x in range(n+1) en condition de poursuite explicite x < n+1.
  • Ligne 14 : on doit incrémenter la variable de boucle alors que c'est fait automatiquemnet avec for.

17° 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_v1(5) 15 >>> somme_v2(5) 15
4.2 TANT QUE : transformer (parfois) un TANT QUE en POUR

A - Cas des fonctions

Puisqu'on sort définitivement de la fonction une fois qu'on rencontre un return, on peut interrompre un POUR avant sa fin réelle si la boucle est à l'intérieur d'une fonction,.

On peut donc parfois programmer une boucle non bornée en utilisant un for associé à return plutôt que d'utiliser directement un while.

Exemple : deux versions d'une fonction qui renvoie le premier mot de plus de 5 lettres trouvés dans une phrase.

On notera qu'on utilise l'espace comme caractère de séparation dans la phrase pour créer le tableau des mots.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
def premier_mot_v1(phrase:'str NON VIDE') ->'int|None': """Renvoie le premier mot de plus de 5 lettres de la phrase""" t = phrase.split(" ") i = 0 while len(t[i]) < 5: i = i + 1 if i < len(t): return t[i] def premier_mot_v2(phrase:'str NON VIDE') -> 'int|None': """Renvoie le premier mot de plus de 5 lettres de la phrase""" t = phrase.split(" ") for i in range(len(t)): if len(t[i]) >= 5: return t[i]
B - Est-ce malin de faire cela ?

Tout dépend du point de vue.

AVANTAGE du for+return : en terme de programmation, on doit noter moins de choses en utilisant un for associé à return.

DESAVANTAGE du for+return : cela crée une fonction qui possède deux sorties : n'oubliez pas que si Python ne rencontre pas le return de la ligne 8, on sortira de la fonction comme si vous aviez tapé return None. Deux sorties. Algorithmiquement, c'est donc plutôt sale.

C - Pas de remplacement possible pour la boucle infinie

Pour remplacer le while par un for, il faut qu'il existe un nombre maximale de fois où on réalise la boucle. Si ce nombre n'existe pas, c'est à dire qu'il est possible de tourner à l'infini sans jamais sortir, il est bien entendu impossible de remplacer le TANT par un POUR équivalent.

D - Hors d'une fonction ? (Ne pas utiliser en NSI)

Ceci ne doit pas être utilisé dans une copie de NSI. Algorithmiquement, si vous devez faire une action en boucle à interrompre sous condition, on prend un boucle TANT QUE. Point.

Il existe un mot-clé permettant de sortir instantanément d'une boucle : break.

On peut donc utiliser break à la place de return si on est en dehors d'une fonction.

A titre d'exemple, voici un programme qui permet de stocker dans mot le premier de 5 lettres ou plus, s'il existe.

1 2 3 4 5 6 7
phrase = "Phrase permettant de réaliser un exemple" mot = None t = phrase.split(" ") for i in range(len(t)): if len(t[i]) >= 5: mot = t[i] break

✎ 18° Tester les deux versions du programme en faisant les appels pour une même phrase.

>>> premier_mot_v1("Bon, alors ça marche ou pas ce truc ?") 'alors' >>> premier_mot_v2("Bon, alors ça marche ou pas ce truc ?") 'alors'

Question

Noter un commentaire pour chaque ligne de ces fonctions : les commentaires devront expliquer en français ce que vont chaque ligne de la fonction.

Si vous bloquez, pensez à me contacter.

4.3 Conclusion : BORNEE ou NON BORNEE

Avant de programmer une boucle, il convient de se demander si on a affaire à une boucle bornée ou une boucle non bornée car cela évitera de partir dans la mauvaise direction.

  • Boucle bornée : for
  • Boucle non bornée sans nombre maximum de tours prévisible : while
  • Boucle non bornée avec un nombre maximum prévisible : while ou for+return

5 - Exos

✎ 19° Compléter la condition du programme ci-dessous : il génére en boucle le lancer de 3 dés à 6 faces. Vous devez faire relancer tant que la somme des trois dés n'est pas supérieure ou égale à 13.

1 2 3 4 5 6 7 8 9 10 11
import random somme = 0 while CONDITION : de1 = random.randint(1,6) de2 = random.randint(1,6) de3 = random.randint(1,6) somme = de1 + de2 + de3 print(f"Les dés donnent {de1} - {de2} - {de3} pour un total de {somme}")

Votre petite soeur ou votre petit frère, ou un cousin ou une cousine, apprend que vous êtes en informatique au lycée. Malheur ! Il n'arrête pas de vous demander de lui fabriquer un jeu. Pour calmer ses ardeurs, vous décidez de lui concevoir un jeu de multiplication : la fonction tire deux nombres au hasard et demande le résultat de la multiplication tant que le résultat n'est pas bon. La fonction renvoie le nombre de tentatives, un au minimum donc.

✎ 20° Compléter la fonction multiplication() pour qu'elle fonctionne correctement.

Il faudra juste trouver la bonne condition à mettre sur le while à la place de True.

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
import random as rd def multiplication(max_a, max_b): """Fonction qui tire au hasard deux nombres et demande à l'utilisateur le résultat de leur multiplication, tant que le résultat n'est pas bon ! ::param max_a(int) :: la valeur maximale du premier tirage ::param max_b(int) :: la valeur maximale du deuxième tirage ::return (int) :: renvoie le nombre essai de tentatives """ a = rd.randint(1, max_a) b = rd.randint(1, max_b) m = 0 # m contiendra la valeur que l'utilisateur pense être a*b essai = 0 while True : m = input(f"Que vaut {a} x {b} = ? : ") m = int(m) essai = essai + 1 return essai print("Donne le résultat des multiplications suivantes.") nbr = 0 for x in range(10): nbr = multiplication(10, 10) + nbr print(f"{nbr} tentatives pour trouver 10 multiplications !")

✎ 21° Créer un programme qui demande "Voulez-vous continuer ?". Tant qu'on ne tape pas "N", le programme doit afficher un message puis reposer la question.

6 - 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 : 20 07 2023
Auteur : ows. h.