1717 lines
50 KiB
Markdown
1717 lines
50 KiB
Markdown
# 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 "<stdin>", line 2, in <module>
|
|
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 "<stdin>", line 2, in <module>
|
|
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)) # → <class 'int'>
|
|
print(type(un_tuple)) # → <class '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 "<stdin>", line 2, in <module>
|
|
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 "<stdin>", line 4, in <module>
|
|
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 "<stdin>", line 3, in <module>
|
|
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 "<stdin>", line 2, in <module>
|
|
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 "<stdin>", line 2, in <module>
|
|
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
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 1</summary>
|
|
|
|
```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…).
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 2</summary>
|
|
|
|
```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.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 3</summary>
|
|
|
|
```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.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 4</summary>
|
|
|
|
```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`.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 5</summary>
|
|
|
|
```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.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 6</summary>
|
|
|
|
```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).
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 7</summary>
|
|
|
|
```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.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé de l'exercice 8</summary>
|
|
|
|
```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 ...)`.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Corrigé du mini-projet</summary>
|
|
|
|
```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.
|
|
|
|
</details>
|
|
|
|
---
|
|
|
|
## 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)
|