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 marchegrades[-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 exercicegrades[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 ?

  1. scores = scores.sort() : sort() trie sur place et renvoie None ; le = écrase donc la liste triée par None. Bug sournois : aucun message ici, le crash arrivera à la ligne suivante.
  2. scores[len(scores)] : même si la ligne 2 était correcte, l'index len(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 exercicerange(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

Raisonnementremove 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 marchein 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


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 exerciceitems.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).