# Chapitre 04 — Les structures de données : listes, tuples, dictionnaires, ensembles > **Durée estimée :** 5 à 6 heures (cours, exercices et mini-projet) > > **Prérequis :** [Chapitre 03 — Les boucles](03-boucles.md) (`for`, `while`, `range`) > > **Objectifs — à la fin de ce chapitre, tu sauras…** > > - créer, lire, modifier et parcourir une **liste** ; > - comprendre la différence entre **copier** une liste et lui donner un **deuxième nom** ; > - utiliser un **tuple** et « déballer » plusieurs valeurs d'un coup ; > - ranger des informations par nom grâce aux **dictionnaires** ; > - éliminer des doublons et comparer des collections avec les **ensembles** ; > - choisir la structure adaptée à chaque problème. --- ## Cours ### 1. Pourquoi des structures de données ? Jusqu'ici, chaque variable contenait **une seule** valeur : un nombre, un texte, un booléen. Imagine que tu veuilles retenir les notes de toute une classe : ```python note1 = 12 note2 = 15 note3 = 9 note4 = 17 # ... et s'il y a 30 élèves ? 300 ? ``` C'est vite ingérable : impossible de faire une boucle sur `note1`, `note2`, `note3`… Il nous faut une variable capable de contenir **plusieurs valeurs** à la fois. C'est le rôle des **structures de données** (on dit aussi **collections**). Python en propose quatre, que tu utiliseras en permanence : | Structure | Exemple | Analogie | |---|---|---| | liste (`list`) | `[12, 15, 9]` | une étagère numérotée où l'on peut ajouter, retirer, réordonner | | tuple (`tuple`) | `(48.85, 2.35)` | une fiche plastifiée : on la lit, on ne la modifie pas | | dictionnaire (`dict`) | `{"Alice": 12, "Bob": 15}` | un répertoire : on cherche par **nom**, pas par numéro | | ensemble (`set`) | `{"rouge", "vert"}` | un sac de billes toutes différentes, sans ordre | --- ### 2. Les listes : créer et lire Une **liste** est une suite ordonnée d'éléments, écrite entre crochets `[ ]`, les éléments étant séparés par des virgules. ```python notes = [12, 15, 9, 17] prenoms = ["Alice", "Bob", "Chloé"] vide = [] melange = [42, "texte", 3.14, True] # possible, mais rarement une bonne idée print(notes) print(prenoms) print(len(notes)) # len() donne le nombre d'éléments print(vide) ``` ```text [12, 15, 9, 17] ['Alice', 'Bob', 'Chloé'] 4 [] ``` > Python affiche les chaînes d'une liste avec des apostrophes `'…'` : c'est normal, c'est sa façon de te montrer qu'il s'agit de textes. #### Les indices : chaque case a un numéro Chaque élément a une **position**, appelée **indice**. Attention : **on commence à compter à 0** ! ```text indice : 0 1 2 3 notes : [ 12, 15, 9, 17 ] indice : -4 -3 -2 -1 ``` ```python notes = [12, 15, 9, 17] print(notes[0]) # → 12 print(notes[2]) # → 9 print(notes[-1]) # → 17 print(notes[-2]) # → 9 ``` Les **indices négatifs** comptent depuis la fin : `-1` est le dernier élément, `-2` l'avant-dernier… Très pratique quand on ne connaît pas la longueur de la liste. Si tu demandes une case qui n'existe pas, Python se fâche : ```python notes = [12, 15, 9, 17] print(notes[4]) ``` ```text Traceback (most recent call last): File "", line 2, in IndexError: list index out of range ``` Une liste de 4 éléments a des indices de `0` à `3`. Le dernier indice valide est toujours `len(liste) - 1`. #### Modifier un élément Une liste est **modifiable** (on dit **mutable**) : on peut remplacer un élément en l'affectant par son indice. ```python notes = [12, 15, 9, 17] notes[2] = 11 # on corrige la 3e note print(notes) # → [12, 15, 11, 17] ``` --- ### 3. Ajouter et retirer des éléments Les listes viennent avec des **méthodes** : des actions qu'on appelle avec un point, `liste.methode(...)`. Voici les plus utiles pour faire grandir ou rétrécir une liste. #### Ajouter ```python courses = ["pain", "lait"] courses.append("oeufs") # ajoute À LA FIN print(courses) # → ['pain', 'lait', 'oeufs'] courses.insert(0, "café") # insère à l'indice 0 (au début) print(courses) # → ['café', 'pain', 'lait', 'oeufs'] courses.extend(["riz", "pâtes"]) # ajoute PLUSIEURS éléments à la fin print(courses) # → ['café', 'pain', 'lait', 'oeufs', 'riz', 'pâtes'] ``` On peut aussi **concaténer** deux listes avec `+` (cela crée une **nouvelle** liste) ou répéter une liste avec `*` : ```python a = [1, 2] b = [3, 4] print(a + b) # → [1, 2, 3, 4] print(a * 3) # → [1, 2, 1, 2, 1, 2] print(a) # → [1, 2] ``` #### Retirer ```python courses = ["café", "pain", "lait", "oeufs", "lait"] courses.remove("lait") # retire la PREMIÈRE occurrence de "lait" print(courses) # → ['café', 'pain', 'oeufs', 'lait'] dernier = courses.pop() # retire ET renvoie le dernier élément print(dernier) # → lait print(courses) # → ['café', 'pain', 'oeufs'] premier = courses.pop(0) # retire ET renvoie l'élément d'indice 0 print(premier) # → café del courses[0] # supprime l'élément d'indice 0 (sans le renvoyer) print(courses) # → ['oeufs'] courses.clear() # vide complètement la liste print(courses) # → [] ``` Résumé : | Méthode | Effet | |---|---| | `liste.append(x)` | ajoute `x` à la fin | | `liste.insert(i, x)` | insère `x` à l'indice `i` | | `liste.extend(autre)` | ajoute tous les éléments de `autre` à la fin | | `liste.remove(x)` | retire la première occurrence de `x` (erreur si absent) | | `liste.pop()` / `liste.pop(i)` | retire et **renvoie** le dernier élément / celui d'indice `i` | | `del liste[i]` | supprime l'élément d'indice `i` | | `liste.clear()` | vide la liste | --- ### 4. Rechercher dans une liste #### Le mot-clé `in` Pour savoir si un élément est présent, on utilise `in` (et `not in`), qui renvoie un booléen. Tu peux donc l'utiliser directement dans un `if` : ```python fruits = ["pomme", "banane", "kiwi"] print("kiwi" in fruits) # → True print("mangue" in fruits) # → False print("mangue" not in fruits) # → True if "banane" in fruits: print("Il reste des bananes !") # → Il reste des bananes ! ``` #### `index` et `count` ```python lettres = ["a", "b", "c", "b", "a", "b"] print(lettres.index("c")) # → 2 print(lettres.count("b")) # → 3 print(lettres.count("z")) # → 0 ``` `index()` provoque une erreur si l'élément est absent (`ValueError: 'z' is not in list`) : vérifie d'abord avec `in`. #### Les fonctions `min`, `max`, `sum` Ces fonctions intégrées travaillent sur une liste entière : ```python notes = [12, 15, 9, 17, 14] print(min(notes)) # → 9 print(max(notes)) # → 17 print(sum(notes)) # → 67 moyenne = sum(notes) / len(notes) print(f"Moyenne : {moyenne:.2f}") # → Moyenne : 13.40 ``` --- ### 5. Parcourir une liste #### Avec `for` : la façon naturelle Tu as vu `for` au chapitre 03 avec `range`. En réalité, `for` sait parcourir **n'importe quelle collection** : à chaque tour, la variable prend la valeur de l'élément suivant. ```python prenoms = ["Alice", "Bob", "Chloé"] for prenom in prenoms: print(f"Bonjour {prenom} !") ``` ```text Bonjour Alice ! Bonjour Bob ! Bonjour Chloé ! ``` Lis-le comme une phrase : « pour chaque prénom dans la liste des prénoms, affiche… ». Choisis un nom au singulier pour la variable de boucle et au pluriel pour la liste : le code devient lisible. #### Quand on a besoin de l'indice Parfois, tu veux aussi la position. La façon simple avec ce que tu connais : `range(len(liste))`. ```python podium = ["Alice", "Bob", "Chloé"] for i in range(len(podium)): print(f"{i + 1}. {podium[i]}") ``` ```text 1. Alice 2. Bob 3. Chloé ``` > Python propose une fonction plus élégante pour cela, `enumerate()`. On la verra au [chapitre 06](06-chaines-et-comprehensions.md). #### Construire une liste au fil d'une boucle Un schéma ultra-courant : partir d'une liste vide et la remplir avec `append`. ```python notes = [12, 7, 15, 9, 18, 10] notes_admises = [] for note in notes: if note >= 10: notes_admises.append(note) print(notes_admises) # → [12, 15, 18, 10] ``` Et pour un **accumulateur** (total, compteur), c'est le même principe qu'au chapitre 03 : ```python temperatures = [18.5, 21.0, 19.5, 23.0] total = 0 nb_chaudes = 0 for temperature in temperatures: total += temperature if temperature > 20: nb_chaudes += 1 print(f"Moyenne : {total / len(temperatures)}") # → Moyenne : 20.5 print(f"Jours chauds : {nb_chaudes}") # → Jours chauds : 2 ``` #### Remplir une liste avec `input()` ```python achats = [] saisie = input("Article (vide pour finir) : ") while saisie != "": achats.append(saisie) saisie = input("Article (vide pour finir) : ") print(f"Tu as noté {len(achats)} article(s) : {achats}") ``` ```text Article (vide pour finir) : tomates Article (vide pour finir) : basilic Article (vide pour finir) : Tu as noté 2 article(s) : ['tomates', 'basilic'] ``` --- ### 6. Trier et inverser #### `sort()` : trier la liste elle-même ```python nombres = [5, 2, 9, 1, 7] nombres.sort() print(nombres) # → [1, 2, 5, 7, 9] nombres.sort(reverse=True) # ordre décroissant print(nombres) # → [9, 7, 5, 2, 1] ``` `sort()` **modifie** la liste et ne renvoie rien (elle renvoie `None`, voir les pièges plus bas). #### `sorted()` : obtenir une copie triée ```python nombres = [5, 2, 9, 1, 7] tries = sorted(nombres) print(tries) # → [1, 2, 5, 7, 9] print(nombres) # → [5, 2, 9, 1, 7] ``` `sorted()` laisse l'original intact et renvoie une **nouvelle** liste. Les chaînes se trient par ordre alphabétique… avec une subtilité : les majuscules passent avant les minuscules. ```python mots = ["poire", "Banane", "abricot", "Cerise"] print(sorted(mots)) # → ['Banane', 'Cerise', 'abricot', 'poire'] print(sorted(mots, key=len)) # → ['poire', 'Banane', 'Cerise', 'abricot'] ``` Le paramètre `key=len` signifie « trie selon la longueur ». On approfondira `sorted` et `key` au [chapitre 06](06-chaines-et-comprehensions.md). #### `reverse()` : inverser l'ordre ```python etapes = ["départ", "milieu", "arrivée"] etapes.reverse() print(etapes) # → ['arrivée', 'milieu', 'départ'] ``` --- ### 7. Le découpage (slicing) : extraire une portion On peut extraire un **morceau** de liste avec la syntaxe `liste[debut:fin]`. L'élément d'indice `debut` est inclus, celui d'indice `fin` est **exclu** (comme avec `range`). ```python lettres = ["a", "b", "c", "d", "e", "f"] print(lettres[1:4]) # → ['b', 'c', 'd'] print(lettres[:3]) # → ['a', 'b', 'c'] print(lettres[3:]) # → ['d', 'e', 'f'] print(lettres[-2:]) # → ['e', 'f'] print(lettres[::2]) # → ['a', 'c', 'e'] print(lettres[::-1]) # → ['f', 'e', 'd', 'c', 'b', 'a'] ``` - Si on omet `debut`, on part du début ; si on omet `fin`, on va jusqu'au bout. - Un troisième nombre donne le **pas** : `[::2]` prend un élément sur deux, `[::-1]` parcourt à l'envers. Le slicing fonctionne aussi sur les chaînes de caractères ; on l'étudiera en détail au [chapitre 06](06-chaines-et-comprehensions.md). --- ### 8. Les listes de listes Une liste peut contenir… d'autres listes. C'est idéal pour représenter un **tableau** (une grille, une matrice, un plateau de jeu). ```python grille = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ] print(grille[0]) # → [1, 2, 3] print(grille[1][2]) # → 6 print(len(grille)) # → 3 ``` `grille[1][2]` se lit « dans la ligne d'indice 1, l'élément d'indice 2 ». Pour tout parcourir, on utilise deux boucles imbriquées (vues au chapitre 03) : ```python grille = [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ] for ligne in grille: for valeur in ligne: print(valeur, end=" ") print() # retour à la ligne après chaque ligne de la grille ``` ```text 1 2 3 4 5 6 7 8 9 ``` Exemple concret : un petit tableau d'élèves avec leurs notes. ```python classe = [ ["Alice", 14, 16], ["Bob", 9, 12], ["Chloé", 18, 15], ] for eleve in classe: nom = eleve[0] moyenne = (eleve[1] + eleve[2]) / 2 print(f"{nom} : {moyenne}") ``` ```text Alice : 15.0 Bob : 10.5 Chloé : 16.5 ``` --- ### 9. Références et copies : le point le plus important du chapitre Voici un comportement qui surprend **tous** les débutants : ```python a = [1, 2, 3] b = a b.append(4) print(a) # → [1, 2, 3, 4] print(b) # → [1, 2, 3, 4] ``` On a modifié `b`… et `a` a changé aussi ! Pourquoi ? **Analogie :** une variable n'est pas une boîte qui *contient* la liste, c'est une **étiquette** collée sur la liste. L'instruction `b = a` ne fabrique pas une deuxième liste : elle colle une deuxième étiquette, `b`, sur **la même** liste. ```text a ──┐ ├──► [1, 2, 3, 4] b ──┘ ``` On peut le vérifier avec l'opérateur `is`, qui teste si deux noms désignent **le même objet** : ```python a = [1, 2, 3] b = a print(b is a) # → True ``` #### Faire une vraie copie Si tu veux une liste **indépendante**, il faut la copier explicitement. Trois façons équivalentes : ```python a = [1, 2, 3] b = a.copy() # méthode copy() c = list(a) # construire une nouvelle liste à partir de a d = a[:] # slicing complet b.append(4) print(a) # → [1, 2, 3] print(b) # → [1, 2, 3, 4] print(c is a) # → False print(c == a) # → True ``` Note la différence : `==` compare le **contenu**, `is` compare l'**identité** (même objet ou non). #### Et pour les nombres ou les chaînes ? Avec un `int` ou une `str`, ce problème ne se pose jamais : ces types sont **immuables** (on ne peut pas les modifier sur place). `x += 1` ne modifie pas le nombre, il crée un nouveau nombre et déplace l'étiquette. ```python x = 5 y = x x += 1 print(x, y) # → 6 5 ``` #### Copie superficielle et listes imbriquées `copy()` ne copie que le **premier niveau**. Pour une liste de listes, les sous-listes restent partagées : ```python grille = [[1, 2], [3, 4]] copie = grille.copy() copie[0].append(99) print(grille) # → [[1, 2, 99], [3, 4]] ``` Pour copier aussi les sous-listes, on utilise `deepcopy` du module `copy` (les modules seront vus au [chapitre 09](09-modules-bibliotheque-standard.md)) : ```python import copy grille = [[1, 2], [3, 4]] copie = copy.deepcopy(grille) copie[0].append(99) print(grille) # → [[1, 2], [3, 4]] print(copie) # → [[1, 2, 99], [3, 4]] ``` --- ### 10. Les tuples : des listes figées Un **tuple** ressemble à une liste, mais il est **immuable** : une fois créé, on ne peut ni modifier, ni ajouter, ni retirer d'élément. On l'écrit entre parenthèses. ```python point = (3, 7) couleur_rgb = (255, 128, 0) print(point[0]) # → 3 print(len(couleur_rgb)) # → 3 print(128 in couleur_rgb) # → True ``` Tenter de le modifier provoque une erreur : ```python point = (3, 7) point[0] = 10 ``` ```text Traceback (most recent call last): File "", line 2, in TypeError: 'tuple' object does not support item assignment ``` **À quoi bon ?** Un tuple sert à regrouper des valeurs qui **vont ensemble** et ne doivent pas changer : des coordonnées, une date `(14, 7, 1789)`, une couleur RGB… L'immuabilité est une **garantie** : personne ne pourra modifier ces données par erreur. #### Le tuple à un seul élément Piège classique : `(5)` n'est pas un tuple, c'est juste le nombre 5 entre parenthèses. Il faut une **virgule** : ```python pas_un_tuple = (5) un_tuple = (5,) print(type(pas_un_tuple)) # → print(type(un_tuple)) # → ``` En fait, c'est la **virgule** qui fait le tuple, pas les parenthèses : `point = 3, 7` crée aussi un tuple. #### Le déballage (unpacking) On peut répartir les éléments d'un tuple (ou d'une liste) dans plusieurs variables d'un coup : ```python point = (3, 7) x, y = point print(x) # → 3 print(y) # → 7 jour, mois, annee = (14, 7, 1789) print(f"{jour}/{mois}/{annee}") # → 14/7/1789 ``` Il faut autant de variables que d'éléments, sinon : `ValueError: too many values to unpack`. L'astérisque `*` permet de récupérer « le reste » dans une liste : ```python premier, *autres = [10, 20, 30, 40] print(premier) # → 10 print(autres) # → [20, 30, 40] ``` Le déballage offre une astuce célèbre : **échanger deux variables** sans variable temporaire. ```python a = 1 b = 2 a, b = b, a print(a, b) # → 2 1 ``` Et il est très pratique dans une boucle sur une liste de tuples : ```python villes = [("Paris", 2_100_000), ("Lyon", 520_000), ("Lille", 236_000)] for nom, habitants in villes: print(f"{nom} compte {habitants} habitants") ``` ```text Paris compte 2100000 habitants Lyon compte 520000 habitants Lille compte 236000 habitants ``` > Rappel : `2_100_000` est simplement une écriture plus lisible de `2100000` ; les `_` sont ignorés par Python. --- ### 11. Les dictionnaires : chercher par clé Une liste range ses éléments par **numéro**. Mais souvent, on veut retrouver une information par un **nom** : le numéro de téléphone de Bob, le prix d'une baguette, la capitale de l'Italie… C'est le rôle du **dictionnaire**. **Analogie :** un vrai dictionnaire associe un **mot** (la clé) à sa **définition** (la valeur). On ne cherche pas « la définition n° 4127 », on cherche « le mot *chat* ». ```python prix = {"pain": 1.20, "lait": 0.95, "oeufs": 2.50} print(prix["lait"]) # → 0.95 print(len(prix)) # → 3 print(prix) # → {'pain': 1.2, 'lait': 0.95, 'oeufs': 2.5} ``` - On écrit un dictionnaire entre accolades `{ }`, avec des paires `cle: valeur` séparées par des virgules. - Les **clés** sont uniques et doivent être immuables (chaînes, nombres, tuples). En pratique, ce sont presque toujours des chaînes. - Les **valeurs** peuvent être de n'importe quel type, y compris des listes ou d'autres dictionnaires. - Depuis Python 3.7, un dictionnaire **conserve l'ordre d'insertion**. #### Ajouter, modifier, supprimer ```python stock = {"pommes": 10, "poires": 4} stock["kiwis"] = 7 # la clé n'existe pas : on l'AJOUTE stock["pommes"] = 12 # la clé existe : on la MODIFIE stock["poires"] += 3 # on peut utiliser += sur la valeur print(stock) # → {'pommes': 12, 'poires': 7, 'kiwis': 7} del stock["kiwis"] # suppression print(stock) # → {'pommes': 12, 'poires': 7} quantite = stock.pop("poires") # supprime ET renvoie la valeur print(quantite) # → 7 print(stock) # → {'pommes': 12} ``` #### Lire sans risque : `in` et `get` Accéder à une clé absente avec `[ ]` provoque une erreur `KeyError` : ```python stock = {"pommes": 10} print(stock["bananes"]) ``` ```text Traceback (most recent call last): File "", line 2, in KeyError: 'bananes' ``` Deux solutions : ```python stock = {"pommes": 10} # 1. Vérifier avec in (in teste les CLÉS) if "bananes" in stock: print(stock["bananes"]) else: print("Pas de bananes") # → Pas de bananes # 2. Utiliser get(cle, valeur_par_defaut) print(stock.get("bananes", 0)) # → 0 print(stock.get("pommes", 0)) # → 10 print(stock.get("bananes")) # → None ``` Sans valeur par défaut, `get` renvoie `None`, la valeur spéciale qui signifie « rien ». #### Parcourir un dictionnaire ```python capitales = {"France": "Paris", "Italie": "Rome", "Espagne": "Madrid"} for pays in capitales: # par défaut : on parcourt les CLÉS print(pays) for ville in capitales.values(): # les valeurs print(ville) for pays, ville in capitales.items(): # les paires (clé, valeur) print(f"La capitale de {pays} est {ville}.") ``` ```text France Italie Espagne Paris Rome Madrid La capitale de France est Paris. La capitale de Italie est Rome. La capitale de Espagne est Madrid. ``` `items()` renvoie des **tuples** `(cle, valeur)` que l'on déballe directement dans la boucle : c'est la forme que tu utiliseras le plus. Les méthodes `keys()`, `values()` et `items()` renvoient des « vues » ; si tu as besoin d'une vraie liste, convertis avec `list()` : ```python capitales = {"France": "Paris", "Italie": "Rome"} print(list(capitales.keys())) # → ['France', 'Italie'] print(list(capitales.values())) # → ['Paris', 'Rome'] ``` #### Un grand classique : compter avec un dictionnaire Compter combien de fois apparaît chaque élément est un usage incontournable des dictionnaires. ```python votes = ["chat", "chien", "chat", "lapin", "chat", "chien"] compteur = {} for animal in votes: if animal in compteur: compteur[animal] += 1 else: compteur[animal] = 1 print(compteur) # → {'chat': 3, 'chien': 2, 'lapin': 1} ``` Avec `get`, c'est encore plus court : ```python votes = ["chat", "chien", "chat", "lapin", "chat", "chien"] compteur = {} for animal in votes: compteur[animal] = compteur.get(animal, 0) + 1 print(compteur) # → {'chat': 3, 'chien': 2, 'lapin': 1} ``` > Le module `collections` propose un outil tout prêt pour ça, `Counter`. Tu le découvriras au [chapitre 09](09-modules-bibliotheque-standard.md). #### Dictionnaires imbriqués et listes de dictionnaires Les structures se combinent. Une **liste de dictionnaires** est la façon standard de représenter un tableau de données (chaque dictionnaire est une ligne) : ```python contacts = [ {"nom": "Alice", "tel": "06 11 22 33 44", "ville": "Lyon"}, {"nom": "Bob", "tel": "07 55 66 77 88", "ville": "Nantes"}, ] for contact in contacts: print(f"{contact['nom']} ({contact['ville']}) : {contact['tel']}") ``` ```text Alice (Lyon) : 06 11 22 33 44 Bob (Nantes) : 07 55 66 77 88 ``` > Dans une f-string délimitée par des guillemets `"…"`, utilise des apostrophes `'…'` pour les clés, afin de ne pas fermer la chaîne par erreur. Et un **dictionnaire de listes** permet de regrouper plusieurs valeurs par clé : ```python notes_par_eleve = { "Alice": [14, 16, 12], "Bob": [9, 12], } notes_par_eleve["Bob"].append(15) notes_par_eleve["Chloé"] = [18] for eleve, notes in notes_par_eleve.items(): print(f"{eleve} : {notes}, moyenne {sum(notes) / len(notes):.1f}") ``` ```text Alice : [14, 16, 12], moyenne 14.0 Bob : [9, 12, 15], moyenne 12.0 Chloé : [18], moyenne 18.0 ``` --- ### 12. Les ensembles (set) : des éléments uniques Un **ensemble** est une collection **sans ordre** et **sans doublon**. On l'écrit entre accolades, mais sans paires `cle: valeur`. ```python couleurs = {"rouge", "vert", "bleu", "rouge"} print(len(couleurs)) # → 3 print("vert" in couleurs) # → True ``` Le doublon `"rouge"` a disparu automatiquement. Comme il n'y a pas d'ordre, **pas d'indice** : `couleurs[0]` provoque une erreur `TypeError`. L'ordre d'affichage d'un ensemble de chaînes peut même changer d'une exécution à l'autre ; pour un affichage stable, utilise `sorted(ensemble)`, qui renvoie une liste triée. #### Créer, ajouter, retirer ```python vide = set() # ATTENTION : {} crée un dictionnaire vide, pas un ensemble ! vide.add(3) vide.add(1) vide.add(3) # déjà présent : ne fait rien print(sorted(vide)) # → [1, 3] vide.discard(3) # retire 3 (aucune erreur s'il est absent) vide.discard(42) # pas d'erreur print(vide) # → {1} ``` `remove(x)` existe aussi, mais provoque une `KeyError` si `x` est absent ; `discard(x)` est plus tolérant. #### Supprimer les doublons d'une liste C'est l'usage le plus courant : ```python emails = ["a@x.fr", "b@x.fr", "a@x.fr", "c@x.fr", "b@x.fr"] uniques = set(emails) print(len(uniques)) # → 3 print(sorted(uniques)) # → ['a@x.fr', 'b@x.fr', 'c@x.fr'] ``` #### Les opérations ensemblistes Comme en mathématiques, on peut combiner des ensembles : ```python club_foot = {"Alice", "Bob", "Chloé", "David"} club_theatre = {"Chloé", "Emma", "Bob", "Farid"} print(sorted(club_foot | club_theatre)) # → ['Alice', 'Bob', 'Chloé', 'David', 'Emma', 'Farid'] print(sorted(club_foot & club_theatre)) # → ['Bob', 'Chloé'] print(sorted(club_foot - club_theatre)) # → ['Alice', 'David'] print(sorted(club_foot ^ club_theatre)) # → ['Alice', 'David', 'Emma', 'Farid'] ``` | Opérateur | Nom | Résultat | |---|---|---| | `a \| b` | union | les éléments présents dans `a` **ou** `b` | | `a & b` | intersection | les éléments présents dans `a` **et** `b` | | `a - b` | différence | les éléments de `a` qui ne sont **pas** dans `b` | | `a ^ b` | différence symétrique | les éléments présents dans **un seul** des deux | **Bonus :** tester `x in ensemble` est extrêmement rapide, même avec des millions d'éléments, alors que `x in liste` doit parcourir toute la liste. Quand tu dois faire beaucoup de tests d'appartenance, pense aux ensembles. --- ### 13. Choisir la bonne structure | Besoin | Structure | |---|---| | Une suite d'éléments dans un ordre précis, qui va évoluer | **liste** | | Quelques valeurs liées qui ne doivent pas changer (coordonnées, date) | **tuple** | | Retrouver une valeur à partir d'un nom/identifiant | **dictionnaire** | | Des éléments uniques, tester l'appartenance, comparer deux groupes | **ensemble** | Et le tableau des propriétés : | | Ordonné | Modifiable | Doublons | Accès | |---|---|---|---|---| | `list` | oui | oui | autorisés | par indice `l[0]` | | `tuple` | oui | non | autorisés | par indice `t[0]` | | `dict` | oui (ordre d'insertion) | oui | clés uniques | par clé `d["cle"]` | | `set` | non | oui | interdits | aucun (seulement `in`) | Toutes ces structures fonctionnent avec `len()`, `in` et `for`. Et les conversions sont faciles : `list(...)`, `tuple(...)`, `set(...)`, `dict(...)`. ```python lettres = list("bonjour") print(lettres) # → ['b', 'o', 'n', 'j', 'o', 'u', 'r'] print(tuple([1, 2, 3])) # → (1, 2, 3) print(sorted(set("bonjour"))) # → ['b', 'j', 'n', 'o', 'r', 'u'] print(dict([("a", 1), ("b", 2)])) # → {'a': 1, 'b': 2} ``` --- ## Pièges fréquents ### Piège 1 — Croire que `b = a` copie une liste ```python originale = ["pain", "lait"] sauvegarde = originale # pas une copie : un deuxième nom ! originale.append("beurre") print(sauvegarde) # → ['pain', 'lait', 'beurre'] ``` Aucun message d'erreur : c'est ce qui rend ce bug sournois. **Correction :** ```python originale = ["pain", "lait"] sauvegarde = originale.copy() # vraie copie originale.append("beurre") print(sauvegarde) # → ['pain', 'lait'] ``` ### Piège 2 — Modifier une liste pendant qu'on la parcourt On veut retirer toutes les notes inférieures à 10 : ```python notes = [8, 5, 12, 9, 15] for note in notes: if note < 10: notes.remove(note) print(notes) # → [5, 12, 15] ``` Le 5 est resté ! En retirant le 8, tous les éléments ont « glissé » d'une case vers la gauche, et la boucle a sauté le 5. **Correction :** construire une nouvelle liste (ou parcourir une copie). ```python notes = [8, 5, 12, 9, 15] notes_gardees = [] for note in notes: if note >= 10: notes_gardees.append(note) notes = notes_gardees print(notes) # → [12, 15] ``` Pour un dictionnaire, c'est encore plus strict : Python refuse carrément. ```python stock = {"pommes": 0, "poires": 4} for fruit in stock: if stock[fruit] == 0: del stock[fruit] ``` ```text Traceback (most recent call last): File "", line 4, in RuntimeError: dictionary changed size during iteration ``` **Correction :** parcourir une copie des clés, `for fruit in list(stock):`. ```python stock = {"pommes": 0, "poires": 4} for fruit in list(stock): if stock[fruit] == 0: del stock[fruit] print(stock) # → {'poires': 4} ``` ### Piège 3 — `liste = liste.sort()` ```python nombres = [3, 1, 2] nombres = nombres.sort() print(nombres) # → None ``` `sort()` trie la liste **sur place** et renvoie `None`. En réaffectant, tu as perdu ta liste. **Correction :** `nombres.sort()` tout seul, ou `nombres = sorted(nombres)`. ### Piège 4 — Sortir des indices de la liste ```python nombres = [10, 20, 30] for i in range(len(nombres) + 1): print(nombres[i]) ``` ```text 10 20 30 Traceback (most recent call last): File "", line 3, in IndexError: list index out of range ``` Le dernier indice valide est `len(nombres) - 1`. **Correction :** `range(len(nombres))`, ou mieux, `for nombre in nombres:`. ### Piège 5 — Accéder à une clé absente d'un dictionnaire ```python age = {"Alice": 30} print(age["alice"]) # attention à la casse ! ``` ```text Traceback (most recent call last): File "", line 2, in KeyError: 'alice' ``` **Correction :** vérifier avec `if "alice" in age:` ou utiliser `age.get("alice", "inconnu")`. Et rappelle-toi que `"Alice"` et `"alice"` sont deux clés différentes. ### Piège 6 — `{}` n'est pas un ensemble vide ```python deja_vus = {} deja_vus.add("Paris") ``` ```text Traceback (most recent call last): File "", line 2, in AttributeError: 'dict' object has no attribute 'add' ``` **Correction :** `deja_vus = set()`. ### Piège 7 — `append` au lieu de `extend` ```python liste = [1, 2] liste.append([3, 4]) print(liste) # → [1, 2, [3, 4]] ``` `append` ajoute **un** élément (ici, une liste entière). **Correction :** `liste.extend([3, 4])` donne `[1, 2, 3, 4]`. ### Piège 8 — Créer une grille avec `*` ```python grille = [[0] * 3] * 3 grille[0][0] = 1 print(grille) # → [[1, 0, 0], [1, 0, 0], [1, 0, 0]] ``` Les trois lignes sont **la même** liste (trois étiquettes sur un seul objet, cf. section 9). **Correction :** créer chaque ligne séparément dans une boucle. ```python grille = [] for _ in range(3): grille.append([0] * 3) grille[0][0] = 1 print(grille) # → [[1, 0, 0], [0, 0, 0], [0, 0, 0]] ``` > Le nom de variable `_` est une convention qui signifie « je n'utilise pas cette variable ». --- ## Exercices ### Exercice 1 — Statistiques de notes ⭐ On donne `notes = [12, 8, 15, 19, 7, 11, 14]`. Affiche le nombre de notes, la plus petite, la plus grande, la moyenne arrondie à 2 décimales, et le nombre de notes supérieures ou égales à 10. Sortie attendue : ```text Nombre de notes : 7 Minimum : 7 Maximum : 19 Moyenne : 12.29 Notes >= 10 : 5 ``` ### Exercice 2 — Manipulations de liste ⭐ Pars de `animaux = ["chat", "chien", "lapin"]` et, dans l'ordre : 1. ajoute `"hamster"` à la fin ; 2. insère `"poisson"` en première position ; 3. retire `"chien"` ; 4. affiche la liste, son premier et son dernier élément ; 5. affiche la liste triée par ordre alphabétique, puis la liste à l'envers (sans la trier). Sortie attendue : ```text ['poisson', 'chat', 'lapin', 'hamster'] Premier : poisson Dernier : hamster Triée : ['chat', 'hamster', 'lapin', 'poisson'] À l'envers : ['hamster', 'lapin', 'chat', 'poisson'] ``` ### Exercice 3 — Compter les mots ⭐⭐ On donne la phrase `phrase = "le chat mange le poisson et le chien mange le chat"`. La méthode `phrase.split()` découpe une chaîne en liste de mots (on la reverra au chapitre 06). Construis un dictionnaire qui associe chaque mot à son nombre d'apparitions, puis affiche chaque mot avec son compte, et enfin le mot le plus fréquent. Sortie attendue : ```text le : 4 chat : 2 mange : 2 poisson : 1 et : 1 chien : 1 Mot le plus fréquent : 'le' (4 fois) ``` ### Exercice 4 — Petit annuaire ⭐⭐ On donne : ```python annuaire = {"Alice": "06 12 34 56 78", "Bob": "07 98 76 54 32", "Chloé": "06 55 44 33 22"} ``` Demande à l'utilisateur des prénoms en boucle (vide pour arrêter). Pour chaque prénom, affiche le numéro s'il existe, sinon propose de l'ajouter en demandant le numéro. À la fin, affiche le nombre de contacts. Exemple d'exécution : ```text Prénom (vide pour quitter) : Bob Bob : 07 98 76 54 32 Prénom (vide pour quitter) : David David est inconnu. Son numéro : 06 00 00 00 00 David ajouté. Prénom (vide pour quitter) : L'annuaire contient 4 contacts. ``` ### Exercice 5 — Amis en commun ⭐⭐ On donne les listes d'amis de deux personnes (avec des doublons dus à une erreur de saisie) : ```python amis_lea = ["Tom", "Inès", "Hugo", "Tom", "Sarah", "Yanis"] amis_noe = ["Sarah", "Lina", "Tom", "Hugo", "Lina"] ``` Affiche (toujours triés par ordre alphabétique) : les amis communs, les amis de Léa que Noé ne connaît pas, et tous les amis sans doublon, avec leur nombre. Sortie attendue : ```text Amis communs : ['Hugo', 'Sarah', 'Tom'] Seulement chez Léa : ['Inès', 'Yanis'] Tous (6) : ['Hugo', 'Inès', 'Lina', 'Sarah', 'Tom', 'Yanis'] ``` ### Exercice 6 — Transposer une grille ⭐⭐ On donne une grille de 2 lignes et 3 colonnes : ```python grille = [ [1, 2, 3], [4, 5, 6], ] ``` Construis sa **transposée** (les lignes deviennent les colonnes), de 3 lignes et 2 colonnes, avec des boucles, puis affiche-la ligne par ligne. Sortie attendue : ```text [1, 4] [2, 5] [3, 6] ``` ### Exercice 7 — Gestion de stock ⭐⭐⭐ Un magasin a ce stock et reçoit cette liste de commandes (tuples `(article, quantité)`) : ```python stock = {"stylo": 50, "cahier": 20, "gomme": 5, "règle": 0} commandes = [("stylo", 10), ("gomme", 8), ("cahier", 20), ("règle", 1), ("trousse", 2)] ``` Traite les commandes dans l'ordre : - si l'article n'existe pas, affiche `trousse : article inconnu` ; - si le stock est insuffisant, affiche `gomme : stock insuffisant (5 disponible(s))` et ne retire rien ; - sinon, retire la quantité et affiche `stylo : 10 expédié(s)`. Enfin, affiche le stock final, puis la liste triée des articles en rupture (quantité 0). Sortie attendue : ```text stylo : 10 expédié(s) gomme : stock insuffisant (5 disponible(s)) cahier : 20 expédié(s) règle : stock insuffisant (0 disponible(s)) trousse : article inconnu Stock final : {'stylo': 40, 'cahier': 0, 'gomme': 5, 'règle': 0} En rupture : ['cahier', 'règle'] ``` ### Exercice 8 — Classement des joueurs ⭐⭐⭐ On donne les scores d'une partie : ```python scores = {"Léo": 1200, "Maya": 1850, "Sam": 900, "Inès": 1850, "Noah": 1500} ``` Affiche le classement du meilleur au moins bon score. En cas d'égalité, classe par ordre alphabétique **et donne le même rang** aux ex æquo. Indice : construis une liste de tuples `(score, nom)` et trie-la. Python compare les tuples élément par élément : d'abord le score, puis le nom en cas d'égalité. Comme on veut les scores décroissants mais les noms croissants, une astuce consiste à stocker `-score`. Sortie attendue : ```text 1. Inès - 1850 pts 1. Maya - 1850 pts 3. Noah - 1500 pts 4. Léo - 1200 pts 5. Sam - 900 pts ``` --- ## Mini-projet — Le gestionnaire de liste de courses ### Cahier des charges Écris un programme interactif qui gère une liste de courses. Les articles sont stockés dans un **dictionnaire** `{article: quantite}`. Le programme affiche un menu en boucle : ```text === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter ``` - **Ajouter** : demander le nom et la quantité. Si l'article existe déjà, **additionner** les quantités. Les noms sont enregistrés en minuscules (`saisie.lower()`) et sans espaces autour (`saisie.strip()`) pour que « Pain » et « pain » soient le même article. - **Retirer** : demander le nom ; s'il existe, le supprimer, sinon afficher un message. - **Afficher** : lister les articles **par ordre alphabétique**, avec leur quantité, et le nombre total d'articles. Si la liste est vide, le dire. - **Vider** : tout supprimer. - **Quitter** : afficher un message d'au revoir et arrêter. - Un choix invalide affiche un message d'erreur sans planter. Exemple d'exécution : ```text Ton choix : 1 Article : Pain Quantité : 2 2 x pain ajouté(s). Ton choix : 1 Article : pain Quantité : 1 1 x pain ajouté(s). Ton choix : 3 - pain : 3 Total : 3 article(s) ``` ### Étapes suggérées 1. Crée le dictionnaire vide et une boucle `while True` qui affiche le menu et lit le choix. 2. Gère le choix `"5"` avec `break`, puis un `else` pour les choix invalides. 3. Implémente « Afficher » (avec `sorted(courses)` pour l'ordre alphabétique). 4. Implémente « Ajouter » avec `courses.get(article, 0) + quantite`. 5. Implémente « Retirer » et « Vider ». 6. Vérifie que la quantité saisie est bien un nombre avec `saisie.isdigit()` (renvoie `True` si la chaîne ne contient que des chiffres) avant d'appeler `int()`. ### Pistes d'amélioration - Permettre de retirer seulement **une partie** de la quantité (et supprimer l'article si on tombe à 0). - Ajouter un **prix unitaire** : dictionnaire de dictionnaires `{"pain": {"quantite": 2, "prix": 1.20}}` et afficher le total à payer. - Classer les articles par **rayon** (`{"boulangerie": {...}, "frais": {...}}`). - Utiliser `match/case` (chapitre 02) pour le menu. - Plus tard : découper le programme en fonctions ([chapitre 05](05-fonctions.md)) et sauvegarder la liste dans un fichier JSON ([chapitre 07](07-fichiers-csv-json.md)). --- ## Corrigés
Corrigé de l'exercice 1 ```python notes = [12, 8, 15, 19, 7, 11, 14] nb_admis = 0 for note in notes: if note >= 10: nb_admis += 1 moyenne = sum(notes) / len(notes) print(f"Nombre de notes : {len(notes)}") print(f"Minimum : {min(notes)}") print(f"Maximum : {max(notes)}") print(f"Moyenne : {moyenne:.2f}") print(f"Notes >= 10 : {nb_admis}") ``` Sortie : ```text Nombre de notes : 7 Minimum : 7 Maximum : 19 Moyenne : 12.29 Notes >= 10 : 5 ``` `min`, `max`, `sum` et `len` font l'essentiel du travail. Le comptage des notes admises utilise le schéma « compteur » : une variable à 0 incrémentée dans la boucle. La f-string `{moyenne:.2f}` arrondit l'affichage à 2 décimales (86 / 7 = 12.2857…).
Corrigé de l'exercice 2 ```python animaux = ["chat", "chien", "lapin"] animaux.append("hamster") animaux.insert(0, "poisson") animaux.remove("chien") print(animaux) print(f"Premier : {animaux[0]}") print(f"Dernier : {animaux[-1]}") print(f"Triée : {sorted(animaux)}") print(f"À l'envers : {animaux[::-1]}") ``` Sortie : ```text ['poisson', 'chat', 'lapin', 'hamster'] Premier : poisson Dernier : hamster Triée : ['chat', 'hamster', 'lapin', 'poisson'] À l'envers : ['hamster', 'lapin', 'chat', 'poisson'] ``` On utilise `sorted()` et `[::-1]` plutôt que `sort()` et `reverse()` : ainsi, la liste `animaux` n'est pas modifiée par les affichages. L'indice `-1` donne le dernier élément quelle que soit la longueur.
Corrigé de l'exercice 3 ```python phrase = "le chat mange le poisson et le chien mange le chat" compteur = {} for mot in phrase.split(): compteur[mot] = compteur.get(mot, 0) + 1 for mot, nombre in compteur.items(): print(f"{mot} : {nombre}") # Recherche du maximum « à la main » mot_max = "" nombre_max = 0 for mot, nombre in compteur.items(): if nombre > nombre_max: mot_max = mot nombre_max = nombre print(f"Mot le plus fréquent : '{mot_max}' ({nombre_max} fois)") ``` Sortie : ```text le : 4 chat : 2 mange : 2 poisson : 1 et : 1 chien : 1 Mot le plus fréquent : 'le' (4 fois) ``` `compteur.get(mot, 0)` renvoie le compte actuel, ou 0 si le mot n'a jamais été vu. Le dictionnaire conserve l'ordre d'insertion, d'où l'ordre d'affichage. Pour le maximum, on garde en mémoire le meilleur trouvé jusqu'ici et on le remplace dès qu'on trouve mieux.
Corrigé de l'exercice 4 ```python annuaire = {"Alice": "06 12 34 56 78", "Bob": "07 98 76 54 32", "Chloé": "06 55 44 33 22"} prenom = input("Prénom (vide pour quitter) : ") while prenom != "": if prenom in annuaire: print(f"{prenom} : {annuaire[prenom]}") else: numero = input(f"{prenom} est inconnu. Son numéro : ") annuaire[prenom] = numero print(f"{prenom} ajouté.") prenom = input("Prénom (vide pour quitter) : ") print(f"L'annuaire contient {len(annuaire)} contacts.") ``` Avec les saisies `Bob`, `David`, `06 00 00 00 00` puis une ligne vide : ```text Prénom (vide pour quitter) : Bob Bob : 07 98 76 54 32 Prénom (vide pour quitter) : David David est inconnu. Son numéro : 06 00 00 00 00 David ajouté. Prénom (vide pour quitter) : L'annuaire contient 4 contacts. ``` On teste d'abord `prenom in annuaire` pour éviter une `KeyError`. Ajouter un contact, c'est simplement affecter une nouvelle clé : `annuaire[prenom] = numero`.
Corrigé de l'exercice 5 ```python amis_lea = ["Tom", "Inès", "Hugo", "Tom", "Sarah", "Yanis"] amis_noe = ["Sarah", "Lina", "Tom", "Hugo", "Lina"] ens_lea = set(amis_lea) ens_noe = set(amis_noe) communs = ens_lea & ens_noe seulement_lea = ens_lea - ens_noe tous = ens_lea | ens_noe print(f"Amis communs : {sorted(communs)}") print(f"Seulement chez Léa : {sorted(seulement_lea)}") print(f"Tous ({len(tous)}) : {sorted(tous)}") ``` Sortie : ```text Amis communs : ['Hugo', 'Sarah', 'Tom'] Seulement chez Léa : ['Inès', 'Yanis'] Tous (6) : ['Hugo', 'Inès', 'Lina', 'Sarah', 'Tom', 'Yanis'] ``` Convertir en `set` élimine les doublons d'un coup, puis les opérateurs `&`, `-` et `|` répondent directement aux questions. `sorted()` transforme l'ensemble en liste triée pour un affichage stable.
Corrigé de l'exercice 6 ```python grille = [ [1, 2, 3], [4, 5, 6], ] nb_lignes = len(grille) nb_colonnes = len(grille[0]) transposee = [] for j in range(nb_colonnes): # chaque colonne de la grille... nouvelle_ligne = [] for i in range(nb_lignes): # ...devient une ligne de la transposée nouvelle_ligne.append(grille[i][j]) transposee.append(nouvelle_ligne) for ligne in transposee: print(ligne) ``` Sortie : ```text [1, 4] [2, 5] [3, 6] ``` L'élément en ligne `i`, colonne `j` de la grille se retrouve en ligne `j`, colonne `i` de la transposée. La boucle extérieure parcourt donc les colonnes, et on crée une **nouvelle** liste à chaque tour (pas de piège 8 ici).
Corrigé de l'exercice 7 ```python stock = {"stylo": 50, "cahier": 20, "gomme": 5, "règle": 0} commandes = [("stylo", 10), ("gomme", 8), ("cahier", 20), ("règle", 1), ("trousse", 2)] for article, quantite in commandes: if article not in stock: print(f"{article} : article inconnu") elif stock[article] < quantite: print(f"{article} : stock insuffisant ({stock[article]} disponible(s))") else: stock[article] -= quantite print(f"{article} : {quantite} expédié(s)") print(f"Stock final : {stock}") ruptures = [] for article, quantite in stock.items(): if quantite == 0: ruptures.append(article) ruptures.sort() print(f"En rupture : {ruptures}") ``` Sortie : ```text stylo : 10 expédié(s) gomme : stock insuffisant (5 disponible(s)) cahier : 20 expédié(s) règle : stock insuffisant (0 disponible(s)) trousse : article inconnu Stock final : {'stylo': 40, 'cahier': 0, 'gomme': 5, 'règle': 0} En rupture : ['cahier', 'règle'] ``` Le déballage `for article, quantite in commandes` rend la boucle très lisible. L'ordre des tests compte : on vérifie d'abord que l'article existe (sinon `stock[article]` provoquerait une `KeyError`), puis que la quantité suffit.
Corrigé de l'exercice 8 ```python scores = {"Léo": 1200, "Maya": 1850, "Sam": 900, "Inès": 1850, "Noah": 1500} # 1. Liste de tuples (-score, nom) : le tri croissant donnera # les scores décroissants, puis les noms par ordre alphabétique. classement = [] for nom, score in scores.items(): classement.append((-score, nom)) classement.sort() # 2. Affichage avec gestion des ex æquo rang = 0 score_precedent = None for position in range(len(classement)): score_negatif, nom = classement[position] score = -score_negatif if score != score_precedent: rang = position + 1 # nouveau score : le rang = la position réelle print(f"{rang}. {nom} - {score} pts") score_precedent = score ``` Sortie : ```text 1. Inès - 1850 pts 1. Maya - 1850 pts 3. Noah - 1500 pts 4. Léo - 1200 pts 5. Sam - 900 pts ``` Les tuples se comparent comme des mots dans le dictionnaire : premier élément d'abord, puis le suivant en cas d'égalité. En stockant `-1850`, `-1500`, etc., le plus grand score devient le plus petit nombre et arrive en tête du tri croissant. Pour les ex æquo, le rang ne change que lorsque le score change, et il saute alors à la position réelle (d'où le « 3 » après deux « 1 », comme dans un vrai classement sportif). Au chapitre 06, tu verras une écriture plus directe avec `sorted(..., key=lambda ...)`.
Corrigé du mini-projet ```python courses = {} while True: print() print("=== LISTE DE COURSES ===") print("1. Ajouter un article") print("2. Retirer un article") print("3. Afficher la liste") print("4. Vider la liste") print("5. Quitter") choix = input("Ton choix : ").strip() if choix == "1": article = input("Article : ").strip().lower() saisie = input("Quantité : ").strip() if article == "": print("Nom d'article vide, rien n'a été ajouté.") elif not saisie.isdigit() or int(saisie) == 0: print("La quantité doit être un nombre entier positif.") else: quantite = int(saisie) courses[article] = courses.get(article, 0) + quantite print(f"{quantite} x {article} ajouté(s).") elif choix == "2": article = input("Article à retirer : ").strip().lower() if article in courses: del courses[article] print(f"{article} retiré de la liste.") else: print(f"{article} n'est pas dans la liste.") elif choix == "3": if len(courses) == 0: print("La liste est vide.") else: total = 0 for article in sorted(courses): print(f"- {article} : {courses[article]}") total += courses[article] print(f"Total : {total} article(s)") elif choix == "4": courses.clear() print("Liste vidée.") elif choix == "5": print("Bonnes courses, à bientôt !") break else: print("Choix invalide, tape un nombre entre 1 et 5.") ``` Exemple de session (saisies : `1`, `Pain`, `2`, `1`, `pain`, `1`, `1`, `Tomates`, `6`, `3`, `2`, `beurre`, `7`, `5`) : ```text === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 1 Article : Pain Quantité : 2 2 x pain ajouté(s). === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 1 Article : pain Quantité : 1 1 x pain ajouté(s). === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 1 Article : Tomates Quantité : 6 6 x tomates ajouté(s). === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 3 - pain : 3 - tomates : 6 Total : 9 article(s) === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 2 Article à retirer : beurre beurre n'est pas dans la liste. === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 7 Choix invalide, tape un nombre entre 1 et 5. === LISTE DE COURSES === 1. Ajouter un article 2. Retirer un article 3. Afficher la liste 4. Vider la liste 5. Quitter Ton choix : 5 Bonnes courses, à bientôt ! ``` **Points clés de la solution :** - `while True` + `break` est le schéma classique d'un menu : on boucle « pour toujours » jusqu'à ce que l'utilisateur choisisse de quitter. - `.strip().lower()` normalise les noms : « Pain », « pain » et « PAIN » désignent le même article. - `courses.get(article, 0) + quantite` gère d'un coup le cas « nouvel article » et le cas « article déjà présent ». - `saisie.isdigit()` est vérifié **avant** `int(saisie)` : si la saisie contient des lettres, `int()` provoquerait une erreur. Grâce à l'évaluation paresseuse de `or` (chapitre 02), `int(saisie)` n'est jamais appelé si `isdigit()` est faux. Au [chapitre 08](08-erreurs-et-debogage.md), tu verras une autre approche avec `try/except`. - `sorted(courses)` renvoie les clés triées sans modifier le dictionnaire.
--- ## Récapitulatif - Une **liste** `[a, b, c]` est ordonnée et modifiable ; on accède aux éléments par **indice** à partir de 0 (`-1` pour le dernier). - Méthodes essentielles : `append`, `insert`, `extend`, `remove`, `pop`, `sort`, `reverse`, `index`, `count`, `copy`, `clear`. - Fonctions utiles : `len`, `min`, `max`, `sum`, `sorted`, et le test `in`. - Le **slicing** `liste[debut:fin:pas]` extrait une portion (fin exclue). - `b = a` ne copie pas : les deux noms désignent **la même** liste. Pour copier : `a.copy()`, `list(a)` ou `a[:]` (et `copy.deepcopy` pour les listes imbriquées). - Ne modifie jamais une collection pendant que tu la parcours : construis-en une nouvelle. - Un **tuple** `(a, b)` est immuable ; on le **déballe** avec `x, y = point`. Un tuple à un élément s'écrit `(x,)`. - Un **dictionnaire** `{cle: valeur}` permet de retrouver une valeur par sa clé ; `get(cle, defaut)` évite les `KeyError`, `items()` permet de parcourir les paires. - Un **ensemble** `{a, b}` ne contient pas de doublons ; il se crée vide avec `set()` et supporte `|`, `&`, `-`, `^`. ### Checklist de validation - [ ] Je sais créer une liste, lire `liste[0]` et `liste[-1]`, et la modifier. - [ ] Je connais la différence entre `append` et `extend`, entre `sort()` et `sorted()`. - [ ] Je sais expliquer pourquoi `b = a` ne copie pas une liste, et comment faire une vraie copie. - [ ] Je sais déballer un tuple et échanger deux variables en une ligne. - [ ] Je sais compter des occurrences avec un dictionnaire et `get`. - [ ] Je sais parcourir un dictionnaire avec `items()`. - [ ] Je sais supprimer les doublons d'une liste et trouver les éléments communs à deux listes. - [ ] J'ai terminé le mini-projet « liste de courses ». --- [← Chapitre précédent](03-boucles.md) | [Sommaire](README.md) | [Chapitre suivant →](05-fonctions.md)