Solutions — série 02-3 (while : répéter tant que)

Rappel de la règle d'or : on n'ouvre ce fichier qu'après avoir terminé (ou sérieusement séché sur) la série. Compare le raisonnement, pas seulement le code : une solution différente qui marche est souvent valable.


Exercice facile a — compte à rebours 10 → 1

Raisonnement — Les trois pièces : initialisation n = 10, condition « on continue tant que n est au moins 1 » (n > 0 ou n >= 1, équivalents sur des entiers), mise à jour n -= 1. Contrôle des bornes sur papier AVANT d'exécuter : premier tour affiche 10, dernier tour affiche 1 (à 0 la condition tombe) — dix valeurs.

Solution

# ex-02-3-a.py
n = 10
while n > 0:
    print(n)
    n -= 1
print("Decollage !")

Sortie : 10 9 8 7 6 5 4 3 2 1 (une valeur par ligne) puis Decollage !.

Pourquoi ça marche — À chaque tour : test (vrai tant que n >= 1), affichage, décrément. Quand n atteint 0, le test 0 > 0 est faux → sortie, et le print final — HORS du bloc, indentation zéro — s'exécute une fois.

Erreur classique sur cet exercicewhile n >= 0: affiche un 0 de trop, while n > 1: s'arrête à 2 : les DEUX off-by-one possibles sur une même condition. Le contrôle première/dernière valeur sur papier attrape les deux en dix secondes.

Variante plus difficile — Fais décoller la fusée depuis n'importe quelle hauteur : n = int(input("Depart ? ")). Que se passe-t-il si l'utilisateur tape 0 ? Et -5 ? (Zéro tour dans les deux cas — la condition fausse d'entrée saute le bloc : comportement correct obtenu gratuitement.)


Exercice facile b — la somme de 1 à n

Raisonnement — Le motif double de la leçon : un compteur i qui parcourt 1, 2, …, n et un accumulateur total qui engrange. Les deux initialisés AVANT la boucle, chacun à sa valeur neutre (compteur au départ 1, somme à 0).

Solution

# ex-02-3-b.py
n = int(input("n ? "))
total = 0
i = 1
while i <= n:
    total += i
    i += 1
print(f"Somme de 1 a {n} : {total}")

Contrôle : n = 100Somme de 1 a 100 : 5050. (Et n = 515, vérifiable de tête.)

Pourquoi ça marchei <= n (avec <=, pas <) inclut n dans la somme. total += i s'exécute AVANT i += 1 dans le tour : chaque valeur de 1 à n est comptée exactement une fois.

Erreur classique sur cet exercicetotal = 0 DANS la boucle : remis à zéro à chaque tour, le total final vaut juste n. Le motif accumulateur exige l'initialisation hors boucle — c'est aussi le piège 9-a de la leçon 02-5, autant l'apprendre ici.

Variante plus difficile — Même programme avec le PRODUIT 1 × 2 × … × n (la factorielle). Quelle est la valeur neutre d'initialisation d'un accumulateur de produit ? (1, pas 0 — sinon tout s'annule.) Contrôle : n = 5120.


Exercice facile c — la boucle infinie volontaire

Raisonnement — Aucun piège caché : on écrit la boucle de la section 7, on la lance, on observe le gel, on appuie sur Ctrl+C, on lit.

Solution

# forever.py
n = 1
while n > 0:
    n = n + 1

Lancement → le terminal gèle (aucune sortie : pas de print dans la boucle). Ctrl+C

Traceback (most recent call last):
  File "c:\Users\jtron\Claude\learnpython\forever.py", line 3, in <module>
    n = n + 1
    ^
KeyboardInterrupt

Pourquoi ça marche (enfin, pourquoi ça ne s'arrête pas) — n ne fait que croître : n > 0 est vrai pour l'éternité. Le traceback du Ctrl+C pointe la ligne en cours d'exécution — en plein cœur de la boucle : c'est le GPS de la boucle infinie.

Erreur classique sur cet exercice — Paniquer et fermer le terminal entier au lieu de Ctrl+C. Ou croire que KeyboardInterrupt signale un bug à corriger dans le code : non, c'est la trace de TON interruption. L'exercice est réussi quand ce traceback t'est devenu banal.

Variante plus difficile — Ajoute print(n) dans la boucle et relance : le défilement ultra-rapide est l'AUTRE visage de la boucle infinie (pas de gel, un torrent). Interromps, puis regarde la dernière valeur affichée : en une seconde, combien de tours Python a-t-il faits ?


Exercice moyen a — le compte à rebours qui gèle

Raisonnement — On inspecte les trois pièces. Initialisation : n = 3, correcte. Condition : n > 0, correcte. Mise à jour : n = n - 1 existe… mais son indentation la place HORS du bloc (au niveau du while, pas du print). Dans la boucle, il ne reste que print(n) : n vaut 3 pour toujours → gel après un torrent de 3.

Solution

# ex-02-3-d.py — version corrigée
n = 3
while n > 0:
    print(n)
    n = n - 1      # ré-indentée : DANS le bloc
print("Partez !")

Sortie : 3 2 1 (une par ligne) puis Partez !.

Pourquoi ça marche — La pièce cassée n'était pas ABSENTE mais mal placée : l'indentation décide de ce qui se répète. C'est l'erreur n°3 de la leçon — aucun message, symptôme boucle infinie, diagnostic par relecture du bloc (« qu'est-ce qui est VRAIMENT dedans ? »).

Erreur classique sur cet exercice — Diagnostiquer « il manque la mise à jour » et la RÉÉCRIRE dans le bloc sans supprimer la ligne du bas. Le programme semble alors correct (3 2 1 Partez !)… mais la ligne oubliée s'exécute encore une fois APRÈS la boucle : n finit à -1 au lieu de 0. Invisible ici ; si du code plus bas réutilise n, bug différé. Corriger = déplacer la ligne, pas la dupliquer.

Variante plus difficile — Sans exécuter : que ferait le programme ORIGINAL si la condition était while n < 0: ? (Zéro tour : 3 < 0 est faux d'entrée — il afficherait directement Partez !. Une boucle cassée peut se taire selon la condition : deux bugs peuvent se masquer.)


Exercice moyen b — la note validée en boucle

Raisonnement — Le motif validation : on redemande TANT QUE la saisie est invalide. « Invalide » = hors bornes = grade < 0 or grade > 20 (le or de la leçon 02-2). L'amorçage doit rendre ce test vrai au premier passage pour forcer une première question : -1 est hors bornes, parfait.

Solution

# ex-02-3-e.py
grade = -1                              # amorçage : invalide exprès
while grade < 0 or grade > 20:
    grade = int(input("Note entre 0 et 20 ? "))
print(f"Note enregistree : {grade}")

Session avec les saisies 25, -3, 12 :

Note entre 0 et 20 ? 25
Note entre 0 et 20 ? -3
Note entre 0 et 20 ? 12
Note enregistree : 12

Pourquoi ça marche25 et -3 laissent la condition vraie (chacun déclenche UN des deux côtés du or — c'est pour ça qu'il faut les deux tests) ; 12 la rend fausse → sortie avec la valeur propre en main.

Erreur classique sur cet exercice — Écrire la condition avec and : grade < 0 and grade > 20 est impossible à satisfaire (aucun nombre n'est à la fois négatif et supérieur à 20) → condition toujours fausse → zéro tour → la note enregistrée est… -1, l'amorçage. Relis « invalide si trop petit OU trop grand » à voix haute. L'inversion and/or dans les bornes est un classique absolu.

Variante plus difficile — De Morgan (exercice difficile de 02-2) : la même boucle s'écrit while not (0 <= grade <= 20):. Vérifie l'équivalence, choisis ta préférée, note pourquoi. Puis durcis : compte les tentatives et abandonne après 3 échecs avec un message (condition composée dans le while — trouvé OU épuisé, comme l'extension du jeu de devinette).


Exercice moyen c — les grains de riz

Raisonnement — On ne sait PAS à l'avance combien de jours il faut : c'est le signal while (contre for). État : grains (accumulateur qui double) et days (compteur). La condition de POURSUITE est « pas encore dépassé 1000 » : grains < 1000. Attention au décalage jour/valeur : jour 1 = 1 grain AVANT la boucle ; chaque tour fait passer au jour suivant.

Solution

# ex-02-3-f.py
grains = 1                  # jour 1 : un grain
days = 1
while grains < 1000:
    grains = grains * 2     # le lendemain, le total double
    days += 1
print(f"Jour {days} : {grains} grains")

Sortie : Jour 11 : 1024 grains — le contrôle de l'énoncé.

Pourquoi ça marche — La boucle s'arrête à la première valeur >= 1000 : 512 (jour 10) relance un tour, 1024 (jour 11) le stoppe. Le compteur et l'accumulateur avancent ENSEMBLE dans le bloc, donc le couple (days, grains) reste cohérent à chaque instant — c'est ce qui rend le print final juste sans calcul supplémentaire.

Erreur classique sur cet exercice — Le décalage d'initialisation : days = 0 au départ donne Jour 10, doubler AVANT de compter ou l'inverse décale aussi. Un off-by-one de while se débogue en déroulant les DEUX premiers tours sur papier : jour 1 → 1 grain, tour 1 → jour 2, 2 grains. Si tes deux premières lignes de papier sont justes, la suite l'est.

Variante plus difficile — Version épargne : 100 € placés à 1 % par jour (money = money * 1.01) — au bout de combien de jours dépasses-tu 200 € ? (Réponse attendue : 70 jours. Les intérêts composés, en cinq lignes de while — et l'affichage propre exige le :.2f de la leçon 01-2.)


Exercice difficile a — l'algorithme d'Euclide

Raisonnement — L'énoncé DONNE l'algorithme, l'exercice est de le traduire : « tant que b n'est pas nul » → while b != 0: ; « remplacer la paire (a, b) par (b, a % b) » → l'assignation simultanée de la leçon 01-1 : a, b = b, a % b. Le point délicat est le « en même temps » : la droite est évaluée EN ENTIER avant toute assignation.

Solution

# gcd.py
a = int(input("Premier entier ? "))
b = int(input("Deuxieme entier ? "))

while b != 0:
    a, b = b, a % b

print(f"PGCD : {a}")

Contrôles : 48 et 36 → PGCD : 12 ; 17 et 5 → PGCD : 1 ; 100 et 10 → PGCD : 10.

Pourquoi ça marche — Déroulé de PGCD(48, 36) : (48, 36) → (36, 48 % 36 = 12) → (12, 36 % 12 = 0) → b == 0, stop, réponse dans a = 12. L'idée mathématique (le PGCD de a et b est aussi celui de b et du reste a % b) fait décroître b à chaque tour, et un reste finit toujours par tomber à 0 : la boucle TERMINE — c'est la question à se poser pour tout while : « pourquoi cette condition finira-t-elle par être fausse ? ».

Erreur classique sur cet exercice — La version en deux lignes : a = b puis b = a % b — au moment de la deuxième ligne, a a DÉJÀ été écrasé : on calcule b % b (toujours 0) et la boucle se termine trop tôt avec un résultat faux. C'est exactement le bug de l'échange sans variable temporaire de la leçon 01-1, en embuscade dans un algorithme. L'assignation simultanée existe pour ça.

Variante plus difficile — Affiche chaque étape (print(f"(a={a}, b={b})") en tête de tour) et compte les tours : combien pour PGCD(48, 36) ? Pour PGCD(1000000, 999999) ? Puis calcule le PPCM avec la formule a * b // pgcd (attention : garde les valeurs INITIALES de a et b dans des variables séparées avant la boucle — pourquoi ?).


Mini-projet — le jeu de devinette

Raisonnement — On suit les étapes de la fiche : d'abord la partie décision (un if/elif sur une proposition unique, secret en dur), puis le random.randint, puis la boucle autour, puis le compteur, puis le cas hors bornes. La condition de boucle est « pas encore trouvé » : guess != secret, avec un amorçage de guess à une valeur qui ne peut PAS être le secret — 0, hors bornes du tirage, est parfait.

Solution

# guess_game.py
import random

secret = random.randint(1, 100)     # bornes INCLUSES (≠ range, ⏩ 02-4)
attempts = 0
guess = 0                           # amorçage : jamais égal au secret (1-100)

print("J'ai choisi un nombre entre 1 et 100.")
while guess != secret:
    guess = int(input("Ta proposition : "))
    attempts += 1                   # compte TOUT essai, même hors bornes
    if guess < 1 or guess > 100:
        print("Hors bornes ! (entre 1 et 100)")
    elif guess > secret:
        print("Trop grand !")
    elif guess < secret:
        print("Trop petit !")
print(f"Gagne en {attempts} essais !")

Session type (le secret variait — ici 37) :

J'ai choisi un nombre entre 1 et 100.
Ta proposition : 50
Trop grand !
Ta proposition : 25
Trop petit !
Ta proposition : 37
Gagne en 3 essais !

Pourquoi ça marche — L'input() est DANS la boucle (l'erreur n°1 de la fiche évitée) : chaque tour lit une nouvelle proposition. La chaîne if/elif n'a pas de branche « gagné » : quand guess == secret, aucun elif n'est vrai, le tour se termine, et c'est la CONDITION du while qui détecte la victoire — une seule source de vérité. Le compteur incrémenté inconditionnellement en tête de tour rend le total exact, victoire au premier coup comprise (teste-le avec le print(secret) de triche de l'étape 2, puis retire-le).

Erreur classique sur cet exercice — Incrémenter attempts dans une seule branche du if (les mauvais essais seulement) : la victoire au premier coup affiche 0 essais. Ou amorcer guess = 1… qui PEUT être le secret : une partie sur cent s'achève sans qu'aucune question ne soit posée, Gagne en 0 essais ! — bug rarissime donc quasi introuvable : c'est l'amorçage HORS domaine (0) qui l'élimine par construction.

Variante plus difficile — Les extensions de la fiche, dans l'ordre : limite de 10 essais (condition composée guess != secret and attempts < 10, puis un if final pour distinguer victoire et défaite) ; proposer de rejouer (une boucle de menu AUTOUR du jeu — c'est le motif du mini-projet de la leçon 02-5) ; et l'inversion des rôles (l'ordinateur devine par dichotomie) — le meilleur exercice de logique du niveau, garde-le pour la fin du niveau 2.