Solutions — série 03-1 (Listes : stocker en ordre)
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 — cinq notes
Raisonnement — Quatre informations à extraire d'une même liste : premier élément (index 0), dernier (index −1, exigé par l'énoncé), taille (len), moyenne (sum / len). Aucune boucle nécessaire : tout existe déjà comme fonction ou index.
Solution
# ex-03-1-a.py
grades = [12, 15, 9, 18, 14]
print(f"Premiere note : {grades[0]}")
print(f"Derniere note : {grades[-1]}")
print(f"Nombre de notes : {len(grades)}")
print(f"Moyenne : {sum(grades) / len(grades)}")
Sortie :
Premiere note : 12
Derniere note : 18
Nombre de notes : 5
Moyenne : 13.6
Pourquoi ça marche — grades[-1] compte depuis la fin : toujours le dernier, quelle que soit la taille. sum(grades) / len(grades) est la définition même de la moyenne — et comme / renvoie toujours un float (⏪ 01-2), pas de division entière surprise.
Erreur classique sur cet exercice — grades[5] pour le dernier (il y a 5 notes… aux index 0 à 4) : IndexError: list index out of range. L'index du dernier est len - 1 ; l'écriture [-1] supprime ce calcul et l'erreur avec.
Variante plus difficile — Affiche la moyenne arrondie à une décimale (round(x, 1) ou f-string {...:.1f}), puis la moyenne SANS la meilleure et la pire note (indice : sorted(grades) puis une slice — ⏩ 03-2).
Exercice facile b — prédire la liste
Raisonnement — On déroule ligne par ligne en dessinant la liste après chaque opération — c'est le réflexe à installer :
nums = [10, 20, 30] # [10, 20, 30]
nums.append(40) # [10, 20, 30, 40] au bout
nums.insert(1, 15) # [10, 15, 20, 30, 40] 15 s'insère À l'index 1,
# le reste se décale à droite
nums.pop() # [10, 15, 20, 30] pop() sans argument : retire
# le DERNIER (le 40)
Solution — La sortie est :
[10, 15, 20, 30] 4
Pourquoi ça marche — Trois méthodes « sur place » enchaînées : chacune modifie LA liste, il n'y a jamais de nouvelle liste. insert(1, 15) place la valeur à l'index demandé et pousse tout vers la droite ; pop() sans argument vise toujours la fin.
Erreur classique sur cet exercice — Deux pièges de prédiction : croire qu'insert(1, 15) remplace l'élément d'index 1 (il s'insère AVANT, rien n'est perdu — c'est nums[1] = 15 qui remplacerait) ; et oublier que pop() retire le 40 fraîchement ajouté, pas le 30.
Variante plus difficile — Même exercice avec nums.pop(0) au lieu de nums.pop() : prédis la liste ET la valeur renvoyée. (Réponse : [15, 20, 30, 40], et pop(0) a renvoyé 10.)
Exercice facile c — courses en quatre opérations
Raisonnement — L'énoncé impose l'outil de chaque étape : append pour ajouter au bout, un index pour remplacer ("lait" est à l'index 1), remove pour retirer par valeur. On suit la liste après chaque ligne pour vérifier qu'on atteint bien la sortie imposée.
Solution
# ex-03-1-c.py
shopping = ["pain", "lait", "oeufs"]
shopping.append("cafe") # ['pain', 'lait', 'oeufs', 'cafe']
shopping[1] = "yaourt" # ['pain', 'yaourt', 'oeufs', 'cafe']
shopping.remove("pain") # ['yaourt', 'oeufs', 'cafe']
print(shopping, len(shopping))
Sortie :
['yaourt', 'oeufs', 'cafe'] 3
Pourquoi ça marche — Remplacer = assigner à un index existant (shopping[1] = ...) : la taille ne change pas. remove("pain") cherche la valeur, peu importe sa position du moment.
Erreur classique sur cet exercice — Remplacer le lait avec shopping.remove("lait") puis shopping.append("yaourt") : le yaourt atterrit en FIN de liste, l'ordre final est faux. remove + append n'est pas un remplacement : l'assignation par index, si.
Variante plus difficile — Refais l'exercice sans connaître la position du lait à l'avance : trouve son index avec shopping.index("lait") avant de remplacer. Que se passe-t-il si le lait n'y est pas ? (Un ValueError — protège avec in.)
Exercice moyen a — les deux bugs du meilleur score
Raisonnement — Le programme veut le plus grand élément. On lit ligne par ligne en se demandant, pour chaque méthode : elle modifie, ou elle renvoie ?
scores = scores.sort():sort()trie sur place et renvoieNone; le=écrase donc la liste triée parNone. Bug sournois : aucun message ici, le crash arrivera à la ligne suivante.scores[len(scores)]: même si la ligne 2 était correcte, l'indexlen(scores)n'existe jamais (4 éléments → index 0 à 3). Off-by-one.
Tel quel, le programme plante d'abord sur le bug 1 : TypeError: object of type 'NoneType' has no len() — la victime est len(scores), le coupable est la ligne du dessus.
Solution
# ex-03-1-d.py — version corrigée
scores = [12, 7, 19, 3]
scores.sort() # bug 1 : sur place, SANS le "="
print(f"Meilleur score : {scores[-1]}") # bug 2 : le dernier, c'est [-1]
Sortie :
Meilleur score : 19
Autres corrections valables : scores = sorted(scores) (fonction qui renvoie), ou — plus direct — max(scores) sans trier du tout.
Pourquoi ça marche — Après le tri croissant, le plus grand est en dernière position, et [-1] désigne cette position sans calcul. La règle générale : ne JAMAIS garder le résultat d'une méthode « sur place » (sort, append, remove… renvoient None).
Erreur classique sur cet exercice — Corriger le sort mais garder scores[len(scores)] « parce que ça a l'air de désigner la fin ». Les deux bugs sont indépendants ; le programme énonce IndexError après la première correction — toujours re-exécuter après CHAQUE correction, un bug peut en cacher un autre.
Variante plus difficile — Affiche le meilleur score SANS trier ni max() : une boucle et une variable best (c'est un galop d'essai pour l'exercice difficile).
Exercice moyen b — les pairs de 1 à 20
Raisonnement — Motif « construire une liste en boucle » : liste vide, parcours de range(1, 21) (21 exclu — off-by-one du niveau 02), filtre n % 2 == 0, append. Les statistiques se lisent ensuite avec les fonctions toutes faites.
Solution
# ex-03-1-e.py
evens = []
for n in range(1, 21):
if n % 2 == 0:
evens.append(n)
print(evens)
print(f"Somme : {sum(evens)}")
print(f"Moyenne : {sum(evens) / len(evens)}")
print(f"Maximum : {max(evens)}")
Sortie :
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Somme : 110
Moyenne : 11.0
Maximum : 20
Pourquoi ça marche — Le trio liste vide / boucle / append transforme une source (le range) en collection filtrée. range(1, 21) s'arrête à 20 inclus car la borne haute est exclue.
Erreur classique sur cet exercice — range(1, 20) qui perd le 20 (le maximum affiché devient 18 — bug silencieux, vérifie ta sortie), ou evens = evens.append(n) qui écrase la liste par None dès le premier tour (même famille que le bug 9-a).
Variante plus difficile — Même résultat sans if : trouve le range à trois arguments qui produit directement les pairs. (Réponse : range(2, 21, 2).) Compare les deux versions : laquelle dit le mieux ce qu'elle fait ?
Exercice moyen c — retirer sans planter
Raisonnement — remove lève ValueError si la valeur manque : il faut donc tester avant d'agir — c'est exactement le métier de in, combiné au if/else du niveau 02.
Solution
# ex-03-1-f.py
stock = ["pomme", "poire", "banane"]
fruit = input("Quel fruit ? ")
if fruit in stock:
stock.remove(fruit)
print(f"{fruit} retire. Stock restant : {stock}")
else:
print(f"Pas en stock : {fruit}")
Exemples d'exécution :
Quel fruit ? poire
poire retire. Stock restant : ['pomme', 'banane']
Quel fruit ? kiwi
Pas en stock : kiwi
Pourquoi ça marche — in renvoie un booléen : la branche remove n'est atteinte que si la valeur existe, le ValueError devient impossible. C'est le motif général « vérifier avant d'agir » que tu réutiliseras avec les dicts (if k in d) et les fichiers (niveau 5).
Erreur classique sur cet exercice — Tester if fruit in stock: mais écrire stock.remove("fruit") avec des guillemets : on tente de retirer le texte littéral "fruit" → ValueError malgré le test. Guillemets = littéral, sans = variable (⏪ 01-1, toujours lui).
Variante plus difficile — Boucle la saisie : redemande tant que le stock n'est pas vide, et affiche Stock epuise ! quand la liste est vide (while
len(stock) > 0, ⏪ niveau 02).
Exercice difficile a — le deuxième plus grand, en un parcours
Raisonnement — Interdits sort/sorted : il faut donc retenir en avançant. Pour répondre « le deuxième plus grand » à tout instant, deux mémoires suffisent : best (le plus grand vu) et second (le plus grand vu qui n'est pas best). Initialisation propre : on classe les DEUX premiers éléments avec un if, puis on parcourt le reste avec range(2, len(nums)). À chaque nouvel élément, trois cas : il détrône best (l'ancien best glisse en second), il détrône seulement second, ou il ne détrône rien.
Solution
# ex-03-1-g.py
nums = [12, 7, 19, 3, 15]
if nums[0] > nums[1]: # initialisation : classer les deux premiers
best = nums[0]
second = nums[1]
else:
best = nums[1]
second = nums[0]
for i in range(2, len(nums)): # le reste de la liste
n = nums[i]
if n > best: # nouveau champion :
second = best # l'ancien devient vice-champion
best = n
elif n > second: # nouveau vice-champion seulement
second = n
print(best, second)
Sortie :
19 15
Pourquoi ça marche — Invariant de boucle : après chaque tour, best et second sont exactement les deux plus grands parmi les éléments déjà vus. L'ordre des deux lignes du premier cas est vital : on sauvegarde best dans second AVANT de l'écraser (le réflexe de l'échange de variables, ⏪ 01-1 exercice difficile).
Erreur classique sur cet exercice — Écrire best = n puis second = best : second reçoit la NOUVELLE valeur, l'ancien champion est perdu — les deux variables finissent égales. Autre piège : initialiser best = 0 et second = 0 : faux dès que la liste contient des négatifs ([-5, -2] devrait donner -2 -5, pas 0 0).
Variante plus difficile — (1) Gère les doublons du maximum : avec [19, 19, 7], ta version renvoie 19 19 — modifie-la pour renvoyer le deuxième plus grand distinct (19 7) : quel test change ? (2) Étends à third : trois mémoires, cascades de décalages — tu comprendras pourquoi, au-delà de 2 ou 3, on trie.
Mini-projet — « liste de courses interactive »
Raisonnement — Un menu en boucle est un while True + if/elif sur le choix (⏪ niveau 02) ; la nouveauté du niveau, c'est l'état : UNE liste qui vit pendant toute la session. Points de vigilance imposés par l'énoncé : in avant remove (pas de ValueError), affichage trié SANS détruire l'ordre d'ajout — donc sorted(items) (renvoie une copie triée) et surtout pas items.sort() (modifierait sur place).
Solution
# shopping_list.py
items = []
while True:
print("\n1. Ajouter 2. Retirer 3. Afficher trie q. Quitter")
choice = input("> ")
if choice == "1":
item = input("Article a ajouter : ")
items.append(item)
print(f"{item} ajoute ({len(items)} article(s)).")
elif choice == "2":
item = input("Article a retirer : ")
if item in items: # verifier AVANT d'agir
items.remove(item)
print(f"{item} retire.")
else:
print(f"{item} n'est pas dans la liste.")
elif choice == "3":
if len(items) == 0:
print("Liste vide.")
else:
for item in sorted(items): # copie triee : items intact
print(f"- {item}")
elif choice == "q":
print("Au revoir !")
break
else:
print("Choix invalide.")
Exemple de session :
1. Ajouter 2. Retirer 3. Afficher trie q. Quitter
> 1
Article a ajouter : cafe
cafe ajoute (1 article(s)).
1. Ajouter 2. Retirer 3. Afficher trie q. Quitter
> 1
Article a ajouter : beurre
beurre ajoute (2 article(s)).
1. Ajouter 2. Retirer 3. Afficher trie q. Quitter
> 3
- beurre
- cafe
1. Ajouter 2. Retirer 3. Afficher trie q. Quitter
> q
Au revoir !
Pourquoi ça marche — La liste items est créée UNE fois avant la boucle : chaque tour la retrouve dans l'état où le tour précédent l'a laissée (c'est la mutabilité qui travaille pour toi — ⏩ nommée et détaillée en 03-4). sorted(items) répond à la contrainte « afficher trié sans détruire » : c'est une fonction « qui renvoie », l'ordre d'ajout survit.
Erreur classique sur cet exercice — items.sort() dans le choix 3 : l'affichage est correct… mais l'ordre d'ajout est définitivement perdu, et personne ne s'en aperçoit avant d'en avoir besoin — bug silencieux type du niveau. Autre classique : créer items = [] DANS la boucle while : la liste est vidée à chaque tour et reste éternellement vide.
Variante plus difficile — (1) Refuse les doublons à l'ajout (in + message). (2) Ajoute un choix 4. Compter qui affiche le nombre d'articles. (3) À l'ajout, accepte plusieurs articles séparés par des virgules (input().split(",") — ⏩ split est présenté en 03-3, et pense à .strip() pour les espaces).