Solutions — série 02-4 (for et range)
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 — prédire les range
Raisonnement — Pour chacun, la routine : première valeur (= start, ou 0), pas, et dernière valeur (la dernière ATTEINTE avant stop, jamais stop lui-même). Le sens du pas doit permettre d'aller de start vers stop, sinon le range est vide.
Solution — vérifiée avec for i in range(...): print(i, end=" ") :
range(4) → 0 1 2 3 (4 valeurs, départ 0)
range(2, 7) → 2 3 4 5 6 (7 exclu ; 7-2 = 5 valeurs)
range(10, 0, -3) → 10 7 4 1 (on descend de 3, on s'arrête AVANT 0)
range(3, 3) → (rien) (start == stop : vide, zéro tour)
Pourquoi ça marche — La règle unique start inclus / stop exclu, plus la marche du pas : 10, 7, 4, 1 puis -2, qui dépasserait 0 → stop. range(3, 3) n'a aucun terrain entre start et stop : vide, sans erreur.
Erreur classique sur cet exercice — Inclure le stop (range(2, 7) → « 2 à 7 ») : LE réflexe à désapprendre. Et pour le pas négatif, s'arrêter à 0 : le stop est exclu dans les DEUX sens.
Variante plus difficile — Prédis range(0, 10, 4) et range(7, 8). (Réponses : 0 4 8 — le prochain pas, 12, dépasse ; et 7 tout seul — un range d'une seule valeur est parfaitement légal.)
Exercice facile b — les multiples de 3, deux écritures
Raisonnement — Version 1 : le pas de range fait tout — départ 3, pas 3, et stop… 31 (PAS 30 : stop exclu, il faut le dépasser pour inclure 30). Version 2 : on parcourt tout et on SAUTE les non-multiples avec continue — i % 3 != 0 (le modulo de 01-2).
Solution
# ex-02-4-b.py — version 1 : le pas
for i in range(3, 31, 3):
print(i, end=" ")
print()
# version 2 : continue
for i in range(3, 31):
if i % 3 != 0:
continue
print(i, end=" ")
print()
Sortie (deux fois la même ligne) :
3 6 9 12 15 18 21 24 27 30
3 6 9 12 15 18 21 24 27 30
Pourquoi ça marche — Version 1 : range(3, 31, 3) produit exactement les multiples, dernier = 30 car 33 dépasserait le stop 31. Version 2 : le continue inverse la logique (« si PAS multiple, passer ») — note que la condition est la NÉGATION de celle qu'on afficherait avec un if direct.
Erreur classique sur cet exercice — range(3, 30, 3) : s'arrête à 27, le 30 manque — l'off-by-one emblématique de la leçon, d'autant plus piégeux que « 30 » apparaît dans l'énoncé et INVITE à l'écrire tel quel en stop. Règle : stop = dernière valeur voulue + 1 (ou + le pas).
Variante plus difficile — Troisième version : for i in range(1, 11): et afficher i * 3. Laquelle des trois exprime le plus directement « les dix premiers multiples de 3 » ? Il n'y a pas de réponse unique — note ton choix et son pourquoi dans le journal.
Exercice facile c — compter les e
Raisonnement — Le trio de l'exemple 3 de la leçon : for sur la chaîne, if sur le caractère, compteur. Rien de plus.
Solution
# ex-02-4-c.py
sentence = input("Une phrase ? ")
count = 0
for letter in sentence:
if letter == "e":
count += 1
print(f"Nombre de 'e' : {count}")
Contrôle : elle prefere le the vert → Nombre de 'e' : 8.
Pourquoi ça marche — for letter in sentence: donne chaque caractère, espaces compris (ils échouent au test, sans dommage). Le compteur, initialisé avant la boucle, n'avance que sur les e exacts.
Erreur classique sur cet exercice — Initialiser count = 0 dans la boucle (compteur éternellement à 0 ou 1) — le piège d'accumulateur récurrent depuis 02-3. Ou compter aussi les E majuscules… ou pas : l'énoncé dit e, ta solution fait ce qu'elle fait — l'important est de SAVOIR lequel.
Variante plus difficile — Compte les voyelles (toutes) avec letter in "aeiou", puis affiche AUSSI le nombre de consonnes. Piège : consonnes ≠ « pas voyelle » (les espaces !). Il te faut un troisième cas — ou l'astuce letter != " " combinée en and.
Exercice moyen a — la table de 7 amputée
Raisonnement — range(1, 10) produit 1 à 9 : la ligne 7 x 10 = 70 manque. Le programme est SAIN pour Python (aucune erreur) : c'est le contrat avec le client (« de 1 à 10 ») qui est violé. Off-by-one d'énoncé, le plus courant de tous.
Solution
# ex-02-4-d.py — version corrigée
for i in range(1, 11):
print(f"7 x {i} = {7 * i}")
Sortie : dix lignes, de 7 x 1 = 7 à 7 x 10 = 70.
Pourquoi ça marche — « De 1 à 10 inclus » = range(1, 11) : stop = dernière valeur + 1. Le contrôle systématique — première ligne, dernière ligne, combien — aurait attrapé le bug avant le client.
Erreur classique sur cet exercice — « Corriger » en range(1, 10 + 1) … qui est juste, mais tape range(0, 11) par excès de prudence : la table commence alors à 7 x 0 = 0. L'off-by-one se corrige par les BORNES exactes, pas en élargissant au hasard.
Variante plus difficile — Généralise : demande la table ET la longueur (table, up_to) à l'utilisateur, affiche table x i = ... jusqu'à up_to inclus. Deux int(input(...)) et un range paramétré — plus AUCUN nombre en dur dans la boucle.
Exercice moyen b — la somme des pairs, trois fois
Raisonnement — Même résultat par trois chemins : (1) ne GÉNÉRER que les pairs (pas de test) ; (2) tout générer, additionner sous condition ; (3) tout générer, écarter les impairs par continue. Contrôle commun : 2550.
Solution
# ex-02-4-e.py
# v1 : le pas de range
total = 0
for n in range(2, 101, 2):
total += n
print(total)
# v2 : if
total = 0
for n in range(1, 101):
if n % 2 == 0:
total += n
print(total)
# v3 : continue
total = 0
for n in range(1, 101):
if n % 2 != 0:
continue
total += n
print(total)
Sortie : 2550 trois fois.
Pourquoi ça marche — v1 : range(2, 101, 2) → 2, 4, …, 100 (101 en stop pour inclure 100). v2 et v3 : les deux faces du même test — « ajouter si pair » contre « sauter si impair ». Remise à zéro de total entre les versions : trois mesures indépendantes.
Erreur classique sur cet exercice — Oublier de RÉINITIALISER total entre les versions : v2 affiche 5100, v3 7650 — et on soupçonne à tort les boucles. Quand un accumulateur sert plusieurs fois dans un fichier, chaque usage repart de zéro. (Sur la lisibilité : la plupart des gens trouvent v1 la plus claire ICI, et v2 plus adaptable quand la condition se complique — ton avis vaut le leur s'il est argumenté.)
Variante plus difficile — Ajoute la somme des IMPAIRS et vérifie que pairs + impairs = 5050 (la somme totale de l'exercice 02-3). Deux accumulateurs dans UNE seule boucle avec un if/else : plus efficace que deux boucles — et c'est ta première « double comptabilité », motif omniprésent en analyse de données.
Exercice moyen c — la pyramide
Raisonnement — Ligne n = n étoiles = "*" * n (multiplication de chaîne, vue avec la bordure de la carte en 01-1). Montée : n va de 1 à 5. Descente : de 5 à 1 → pas négatif, et le contrôle première/dernière valeur AVANT de taper : première 5, dernière 1, donc range(5, 0, -1).
Solution
# ex-02-4-f.py
for n in range(1, 6):
print("*" * n)
for n in range(5, 0, -1):
print("*" * n)
Sortie :
*
**
***
****
*****
*****
****
***
**
*
Pourquoi ça marche — La variable de boucle sert directement d'opérande : à chaque tour, une chaîne de la bonne longueur est CONSTRUITE (pas de if en cascade, pas de dix print). La descente s'arrête avant 0 — une ligne de zéro étoile serait une ligne vide, presque invisible : off-by-one typique du pas négatif.
Erreur classique sur cet exercice — range(5, 1, -1) pour la descente : s'arrête à 2 étoiles, la dernière ligne * manque — le stop est exclu AUSSI en descendant. Et range(5, 0) (pas oublié) : vide, rien ne s'affiche du tout — un range qui ne peut pas marcher vers son stop est vide, sans erreur.
Variante plus difficile — La pyramide centrée :
*
***
*****
Chaque ligne = des espaces PUIS des étoiles : deux multiplications de chaînes concaténées avec +. Trouve les deux formules en fonction du numéro de ligne (fais un tableau sur papier : ligne 1 → 4 espaces, 1 étoile…).
Exercice difficile a — les nombres premiers
Raisonnement — Deux étages : POUR chaque candidat n (2 à 30), décider s'il est premier. La décision : chercher un diviseur parmi 2 … n-1 ; si on en trouve UN, verdict rendu, inutile de continuer → break. Mais le break seul ne dit pas au monde extérieur POURQUOI on est sorti : le drapeau is_prime, levé avant la recherche, baissé à la première trouvaille, porte le verdict au-delà de la boucle intérieure. Il doit être RELEVÉ pour chaque nouveau candidat → à l'intérieur de la boucle extérieure.
Solution
# ex-02-4-g.py
for n in range(2, 31):
is_prime = True # relevé pour CHAQUE candidat
for d in range(2, n):
if n % d == 0: # un diviseur : n n'est pas premier
is_prime = False
break # verdict rendu, on arrête de chercher
if is_prime:
print(n, end=" ")
print()
Sortie :
2 3 5 7 11 13 17 19 23 29
Pourquoi ça marche — Pour n = 2 : range(2, 2) est vide, zéro tour, le drapeau reste levé → 2 est premier (élégant cas limite gratuit). Pour n = 9 : d = 3 divise, drapeau baissé, break — le if is_prime: situé APRÈS la boucle intérieure (même indentation qu'elle) lit le verdict. Le break ne sort que de la boucle d : la chasse continue avec le candidat suivant, comme voulu.
Erreur classique sur cet exercice — is_prime = True placé AVANT la boucle extérieure : baissé au premier non-premier (4), il ne se relève jamais — sortie 2 3 puis plus rien. Symptôme discret, cause à un niveau d'indentation près : relis toujours OÙ un drapeau se réinitialise. Autre classique : indenter if is_prime: DANS la boucle intérieure — verdict lu pendant l'enquête, résultats incohérents.
Variante plus difficile — Optimise : il suffit de chercher les diviseurs jusqu'à n // 2 (pourquoi ?), et même jusqu'à la racine carrée (⏩ niveau 4, avec les fonctions). Puis inverse le problème : affiche la DÉCOMPOSITION de 360 en facteurs premiers (2 2 2 3 3 5) — un while DANS un for, le niveau 2 au complet.
Mini-projet — « table de multiplication à trous »
Raisonnement — Un quiz = une boucle for (nombre de questions CONNU → for, pas while), et par tour : générer, demander, comparer, compter. Le nombre de questions vit dans UNE variable (règle DRY de 01-1) utilisée par le range ET l'affichage final. Le tirage : random.randint(1, 10) deux fois (leçon 02-3).
Solution
# quiz_tables.py
import random
question_count = 5
score = 0
print("=== Quiz des tables ===")
for q in range(1, question_count + 1):
a = random.randint(1, 10)
b = random.randint(1, 10)
answer = int(input(f"Question {q} : {a} x {b} ? "))
if answer == a * b:
print("Correct !")
score += 1
else:
print(f"Faux : {a} x {b} = {a * b}")
print(f"Score final : {score}/{question_count}")
Session type (les facteurs varient à chaque lancement) :
=== Quiz des tables ===
Question 1 : 6 x 3 ? 18
Correct !
Question 2 : 7 x 1 ? 7
Correct !
Question 3 : 2 x 9 ? 19
Faux : 2 x 9 = 18
Question 4 : 2 x 6 ? 12
Correct !
Question 5 : 10 x 1 ? 10
Correct !
Score final : 4/5
Pourquoi ça marche — range(1, question_count + 1) numérote les questions de 1 à 5 (plus humain que 0 à 4 — et le + 1 est le réflexe anti-off-by-one). Les facteurs sont tirés à CHAQUE tour, dans la boucle — placés avant, les cinq questions seraient identiques. Le score, motif compteur de 02-3, ne s'incrémente que dans la branche Correct.
Erreur classique sur cet exercice — Tirer a et b AVANT la boucle (cinq fois la même question — relis « erreur classique » ci-dessus) ; ou afficher Score final : {score}/5 avec le 5 en dur : passe question_count à 10 et l'affichage ment. Chaque nombre écrit deux fois est un mensonge en préparation.
Variante plus difficile — L'extension de la leçon : reposer la MÊME question tant que la réponse est fausse (un while answer != a * b: DANS le for — attention à demander la réponse une première fois avant, ou à amorcer). Puis : ne compter le point QUE si la bonne réponse vient du premier coup — il te faut distinguer « premier essai » des suivants… un drapeau, comme pour les nombres premiers.