1598 lines
51 KiB
Markdown
1598 lines
51 KiB
Markdown
# Chapitre 06 — Chaînes de caractères et compréhensions
|
||
|
||
> **Durée estimée :** 5 à 6 heures (cours, exercices et mini-projet)
|
||
>
|
||
> **Prérequis :** [Chapitre 05 — Les fonctions](05-fonctions.md) (`def`, paramètres, `return`)
|
||
>
|
||
> **Objectifs — à la fin de ce chapitre, tu sauras…**
|
||
>
|
||
> - manipuler du texte avec les principales **méthodes de `str`** (`split`, `join`, `strip`, `replace`, `find`…) ;
|
||
> - extraire des morceaux de chaîne avec le **slicing** ;
|
||
> - écrire des **compréhensions** de listes, de dictionnaires et d'ensembles ;
|
||
> - parcourir plus élégamment avec **`enumerate`** et **`zip`** ;
|
||
> - trier finement avec **`sorted(..., key=...)`** et des fonctions **`lambda`** ;
|
||
> - nettoyer et analyser un texte complet.
|
||
|
||
---
|
||
|
||
## Cours
|
||
|
||
### 1. Rappels : une chaîne est une séquence
|
||
|
||
Tu manipules des chaînes (`str`) depuis le [chapitre 01](01-variables-types-operateurs.md). Il est temps de voir qu'une chaîne se comporte **comme une liste de caractères** (mais une liste figée, comme un tuple).
|
||
|
||
```python
|
||
mot = "Python"
|
||
print(len(mot)) # → 6
|
||
print(mot[0]) # → P
|
||
print(mot[-1]) # → n
|
||
print("y" in mot) # → True
|
||
print("thon" in mot) # → True
|
||
|
||
for lettre in mot:
|
||
print(lettre, end=" ")
|
||
print()
|
||
```
|
||
|
||
```text
|
||
6
|
||
P
|
||
n
|
||
True
|
||
True
|
||
P y t h o n
|
||
```
|
||
|
||
`in` sur une chaîne cherche une **sous-chaîne** entière : `"thon" in "Python"` est vrai.
|
||
|
||
#### Les chaînes sont immuables
|
||
|
||
Comme les tuples, on ne peut pas modifier une chaîne « sur place » :
|
||
|
||
```python
|
||
mot = "Python"
|
||
mot[0] = "J"
|
||
```
|
||
|
||
```text
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 2, in <module>
|
||
TypeError: 'str' object does not support item assignment
|
||
```
|
||
|
||
Toutes les « modifications » de chaîne créent en fait une **nouvelle** chaîne :
|
||
|
||
```python
|
||
mot = "Python"
|
||
mot = "J" + mot[1:]
|
||
print(mot) # → Jython
|
||
```
|
||
|
||
C'est fondamental pour comprendre les méthodes qui suivent : **aucune ne modifie la chaîne d'origine**, elles **renvoient** une nouvelle chaîne. Il faut donc récupérer le résultat.
|
||
|
||
```python
|
||
nom = "alice"
|
||
nom.upper() # le résultat est perdu !
|
||
print(nom) # → alice
|
||
nom = nom.upper()
|
||
print(nom) # → ALICE
|
||
```
|
||
|
||
#### Caractères spéciaux et chaînes sur plusieurs lignes
|
||
|
||
```python
|
||
print("Ligne 1\nLigne 2") # \n = retour à la ligne
|
||
print("Col1\tCol2") # \t = tabulation
|
||
print("Il a dit \"bonjour\"") # \" = guillemet dans la chaîne
|
||
print('C\'est facile') # \' = apostrophe
|
||
print("C:\\Users\\alice") # \\ = un antislash
|
||
```
|
||
|
||
```text
|
||
Ligne 1
|
||
Ligne 2
|
||
Col1 Col2
|
||
Il a dit "bonjour"
|
||
C'est facile
|
||
C:\Users\alice
|
||
```
|
||
|
||
Les triples guillemets permettent d'écrire du texte sur plusieurs lignes :
|
||
|
||
```python
|
||
poeme = """Mignonne, allons voir si la rose
|
||
Qui ce matin avait déclose
|
||
Sa robe de pourpre au soleil"""
|
||
print(len(poeme.splitlines())) # → 3
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Le slicing en détail
|
||
|
||
Tu as découvert le slicing sur les listes au [chapitre 04](04-structures-de-donnees.md). Il fonctionne exactement pareil sur les chaînes : `chaine[debut:fin:pas]`, avec `fin` **exclu**.
|
||
|
||
```text
|
||
indice positif : 0 1 2 3 4 5 6 7 8 9
|
||
caractère : B o n j o u r ! ! !
|
||
indice négatif : -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
|
||
```
|
||
|
||
```python
|
||
texte = "Bonjour!!!"
|
||
print(texte[0:3]) # → Bon
|
||
print(texte[3:7]) # → jour
|
||
print(texte[:7]) # → Bonjour
|
||
print(texte[7:]) # → !!!
|
||
print(texte[-3:]) # → !!!
|
||
print(texte[:-3]) # → Bonjour
|
||
print(texte[::2]) # → Bnor!
|
||
print(texte[::-1]) # → !!!ruojnoB
|
||
```
|
||
|
||
Astuce pour retenir : imagine que les indices désignent les **séparations entre les caractères**, et non les caractères eux-mêmes. `texte[3:7]` prend ce qui se trouve entre la séparation 3 et la séparation 7.
|
||
|
||
```text
|
||
| B | o | n | j | o | u | r | ! | ! | ! |
|
||
0 1 2 3 4 5 6 7 8 9 10
|
||
```
|
||
|
||
Contrairement à l'accès par indice, le slicing ne provoque **jamais** d'erreur si on dépasse :
|
||
|
||
```python
|
||
mot = "chat"
|
||
print(mot[2:100]) # → at
|
||
print(mot[10:] == "") # → True (on obtient une chaîne vide)
|
||
```
|
||
|
||
Quelques usages typiques :
|
||
|
||
```python
|
||
fichier = "rapport_2024.pdf"
|
||
print(fichier[-4:]) # → .pdf (l'extension)
|
||
print(fichier[:-4]) # → rapport_2024 (sans l'extension)
|
||
|
||
code_postal = "75011"
|
||
print(code_postal[:2]) # → 75 (le département)
|
||
|
||
mot = "kayak"
|
||
print(mot == mot[::-1]) # → True (palindrome en une ligne !)
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Les méthodes de chaîne essentielles
|
||
|
||
Une méthode s'appelle avec un point : `chaine.methode(...)`. Voici celles que tu utiliseras tout le temps, regroupées par usage.
|
||
|
||
#### Changer la casse
|
||
|
||
```python
|
||
titre = "le Petit PRINCE"
|
||
print(titre.upper()) # → LE PETIT PRINCE
|
||
print(titre.lower()) # → le petit prince
|
||
print(titre.capitalize()) # → Le petit prince
|
||
print(titre.title()) # → Le Petit Prince
|
||
print(titre.swapcase()) # → LE pETIT prince
|
||
```
|
||
|
||
Pour comparer deux textes sans tenir compte des majuscules, on les met tous les deux en minuscules :
|
||
|
||
```python
|
||
reponse = "OUI"
|
||
if reponse.lower() == "oui":
|
||
print("D'accord !") # → D'accord !
|
||
```
|
||
|
||
#### Nettoyer les espaces
|
||
|
||
`strip()` retire les espaces (et retours à la ligne, tabulations) **au début et à la fin**. C'est indispensable pour nettoyer une saisie utilisateur.
|
||
|
||
```python
|
||
saisie = " alice@mail.fr \n"
|
||
print(f"[{saisie.strip()}]") # → [alice@mail.fr]
|
||
```
|
||
|
||
`lstrip()` et `rstrip()` ne nettoient qu'un seul côté (gauche ou droite) :
|
||
|
||
```python
|
||
saisie = " bonjour "
|
||
print(f"[{saisie.strip()}]") # → [bonjour]
|
||
print(f"[{saisie.lstrip()}]") # → [bonjour ]
|
||
print(f"[{saisie.rstrip()}]") # → [ bonjour]
|
||
print("---titre---".strip("-")) # → titre
|
||
```
|
||
|
||
On peut passer à `strip` les caractères à retirer, comme dans la dernière ligne.
|
||
|
||
#### Chercher
|
||
|
||
```python
|
||
phrase = "Le chat dort. Le chat mange."
|
||
print(phrase.find("chat")) # → 3 (indice de la 1re occurrence)
|
||
print(phrase.find("chien")) # → -1 (pas trouvé)
|
||
print(phrase.count("chat")) # → 2
|
||
print(phrase.startswith("Le")) # → True
|
||
print(phrase.endswith(".")) # → True
|
||
print(phrase.index("dort")) # → 8
|
||
```
|
||
|
||
`find` renvoie `-1` si rien n'est trouvé, alors que `index` provoque une erreur `ValueError: substring not found`. Pour un simple test de présence, `in` reste le plus lisible.
|
||
|
||
#### Remplacer
|
||
|
||
```python
|
||
phrase = "J'aime le thé. Le thé c'est bon."
|
||
print(phrase.replace("thé", "café")) # → J'aime le café. Le café c'est bon.
|
||
print(phrase.replace("thé", "café", 1)) # → J'aime le café. Le thé c'est bon.
|
||
print(phrase.replace(".", "")) # → J'aime le thé Le thé c'est bon
|
||
```
|
||
|
||
Le troisième argument limite le nombre de remplacements. Remplacer par `""` revient à **supprimer**.
|
||
|
||
#### Tester le contenu
|
||
|
||
Ces méthodes renvoient un booléen :
|
||
|
||
```python
|
||
print("2024".isdigit()) # → True (que des chiffres)
|
||
print("12.5".isdigit()) # → False (le point n'est pas un chiffre)
|
||
print("Bonjour".isalpha()) # → True (que des lettres)
|
||
print("Été".isalpha()) # → True (les accents sont des lettres)
|
||
print("abc123".isalnum()) # → True (lettres ou chiffres)
|
||
print(" ".isspace()) # → True
|
||
print("ALICE".isupper()) # → True
|
||
print("alice".islower()) # → True
|
||
```
|
||
|
||
#### Découper avec `split` et assembler avec `join`
|
||
|
||
Ce sont les deux méthodes les plus importantes du chapitre.
|
||
|
||
`split()` découpe une chaîne en **liste** :
|
||
|
||
```python
|
||
phrase = "le chat dort"
|
||
print(phrase.split()) # → ['le', 'chat', 'dort']
|
||
|
||
date = "14/07/1789"
|
||
print(date.split("/")) # → ['14', '07', '1789']
|
||
|
||
ligne_csv = "Alice;30;Lyon"
|
||
nom, age, ville = ligne_csv.split(";")
|
||
print(nom, age, ville) # → Alice 30 Lyon
|
||
|
||
print("a,b,,c".split(",")) # → ['a', 'b', '', 'c']
|
||
```
|
||
|
||
Sans argument, `split()` coupe sur **n'importe quel blanc** (espaces multiples, tabulations, retours à la ligne) et ignore les blancs en trop. Avec un séparateur, il coupe exactement sur ce séparateur, d'où la chaîne vide `''` dans le dernier exemple.
|
||
|
||
`join()` fait l'inverse : il **assemble** une liste de chaînes en une seule, en insérant un séparateur. Attention à la syntaxe, c'est le **séparateur** qui appelle la méthode :
|
||
|
||
```python
|
||
mots = ["le", "chat", "dort"]
|
||
print(" ".join(mots)) # → le chat dort
|
||
print("-".join(mots)) # → le-chat-dort
|
||
print("".join(mots)) # → lechatdort
|
||
print(", ".join(mots)) # → le, chat, dort
|
||
print("\n".join(mots))
|
||
```
|
||
|
||
```text
|
||
le chat dort
|
||
le-chat-dort
|
||
lechatdort
|
||
le, chat, dort
|
||
le
|
||
chat
|
||
dort
|
||
```
|
||
|
||
`join` n'accepte **que des chaînes** :
|
||
|
||
```python
|
||
nombres = [1, 2, 3]
|
||
print("-".join(nombres))
|
||
```
|
||
|
||
```text
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 2, in <module>
|
||
TypeError: sequence item 0: expected str instance, int found
|
||
```
|
||
|
||
Il faut d'abord convertir chaque nombre en chaîne ; on verra la façon élégante de le faire avec les compréhensions (section 6).
|
||
|
||
Combinés, `split` et `join` permettent de transformer un texte en quelques lignes :
|
||
|
||
```python
|
||
phrase = " python est super "
|
||
propre = " ".join(phrase.split())
|
||
print(f"[{propre}]") # → [python est super]
|
||
```
|
||
|
||
#### Aligner et remplir
|
||
|
||
```python
|
||
print("chat".center(12, "*")) # → ****chat****
|
||
print("chat".ljust(8) + "|") # → chat |
|
||
print("chat".rjust(8) + "|") # → chat|
|
||
print("7".zfill(3)) # → 007
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Les f-strings, en plus fin
|
||
|
||
Tu connais les f-strings depuis le chapitre 01. Après les deux-points, on peut ajouter un **format** très précis :
|
||
|
||
```python
|
||
prix = 1234.5678
|
||
print(f"{prix:.2f}") # → 1234.57 (2 décimales)
|
||
print(f"{prix:,.2f}") # → 1,234.57 (séparateur de milliers)
|
||
print(f"{0.256:.1%}") # → 25.6% (pourcentage)
|
||
print(f"{42:05d}") # → 00042 (5 chiffres, complété par des 0)
|
||
|
||
nom = "Alice"
|
||
print(f"[{nom:<10}]") # → [Alice ] (aligné à gauche sur 10)
|
||
print(f"[{nom:>10}]") # → [ Alice] (aligné à droite)
|
||
print(f"[{nom:^10}]") # → [ Alice ] (centré)
|
||
print(f"[{nom:*^11}]") # → [***Alice***] (centré, rempli de *)
|
||
```
|
||
|
||
C'est idéal pour afficher des tableaux bien alignés :
|
||
|
||
```python
|
||
produits = [("Pomme", 0.5, 12), ("Ananas", 2.95, 2), ("Kiwi", 0.35, 30)]
|
||
print(f"{'Produit':<10}{'Prix':>8}{'Qté':>6}")
|
||
print("-" * 24)
|
||
for nom, prix, quantite in produits:
|
||
print(f"{nom:<10}{prix:>8.2f}{quantite:>6}")
|
||
```
|
||
|
||
```text
|
||
Produit Prix Qté
|
||
------------------------
|
||
Pomme 0.50 12
|
||
Ananas 2.95 2
|
||
Kiwi 0.35 30
|
||
```
|
||
|
||
Une autre astuce pratique pour déboguer : `=` après le nom de variable affiche le nom **et** la valeur.
|
||
|
||
```python
|
||
total = 42
|
||
print(f"{total=}") # → total=42
|
||
```
|
||
|
||
---
|
||
|
||
### 5. `enumerate`, `zip` et `sorted`
|
||
|
||
Ces trois fonctions intégrées rendent les boucles beaucoup plus élégantes.
|
||
|
||
#### `enumerate` : l'indice et la valeur
|
||
|
||
Au chapitre 04, pour avoir l'indice, on écrivait `for i in range(len(liste))`. `enumerate` fait mieux : il fournit à chaque tour un tuple `(indice, valeur)`, qu'on déballe.
|
||
|
||
```python
|
||
podium = ["Alice", "Bob", "Chloé"]
|
||
for i, nom in enumerate(podium):
|
||
print(i, nom)
|
||
```
|
||
|
||
```text
|
||
0 Alice
|
||
1 Bob
|
||
2 Chloé
|
||
```
|
||
|
||
Le paramètre `start` permet de commencer à compter ailleurs qu'à 0 (pratique pour l'affichage humain) :
|
||
|
||
```python
|
||
podium = ["Alice", "Bob", "Chloé"]
|
||
for rang, nom in enumerate(podium, start=1):
|
||
print(f"{rang}. {nom}")
|
||
```
|
||
|
||
```text
|
||
1. Alice
|
||
2. Bob
|
||
3. Chloé
|
||
```
|
||
|
||
Ça marche aussi sur une chaîne :
|
||
|
||
```python
|
||
for position, lettre in enumerate("abc"):
|
||
print(f"La lettre {lettre} est en position {position}")
|
||
```
|
||
|
||
```text
|
||
La lettre a est en position 0
|
||
La lettre b est en position 1
|
||
La lettre c est en position 2
|
||
```
|
||
|
||
#### `zip` : parcourir plusieurs listes en parallèle
|
||
|
||
`zip` associe les éléments de plusieurs collections **deux à deux** (comme une fermeture éclair, *zip* en anglais).
|
||
|
||
```python
|
||
prenoms = ["Alice", "Bob", "Chloé"]
|
||
ages = [30, 25, 35]
|
||
villes = ["Lyon", "Paris", "Nice"]
|
||
|
||
for prenom, age, ville in zip(prenoms, ages, villes):
|
||
print(f"{prenom}, {age} ans, habite à {ville}")
|
||
```
|
||
|
||
```text
|
||
Alice, 30 ans, habite à Lyon
|
||
Bob, 25 ans, habite à Paris
|
||
Chloé, 35 ans, habite à Nice
|
||
```
|
||
|
||
Si les listes n'ont pas la même longueur, `zip` s'arrête à la plus courte. Pour transformer deux listes en dictionnaire, `zip` est parfait :
|
||
|
||
```python
|
||
cles = ["nom", "age", "ville"]
|
||
valeurs = ["Alice", 30, "Lyon"]
|
||
fiche = dict(zip(cles, valeurs))
|
||
print(fiche) # → {'nom': 'Alice', 'age': 30, 'ville': 'Lyon'}
|
||
```
|
||
|
||
Et pour comparer des éléments voisins d'une liste, on peut « zipper » la liste avec elle-même décalée :
|
||
|
||
```python
|
||
temperatures = [12, 15, 14, 18, 21]
|
||
for avant, apres in zip(temperatures, temperatures[1:]):
|
||
print(f"{avant} → {apres} : {apres - avant:+d}")
|
||
```
|
||
|
||
```text
|
||
12 → 15 : +3
|
||
15 → 14 : -1
|
||
14 → 18 : +4
|
||
18 → 21 : +3
|
||
```
|
||
|
||
(Le format `+d` affiche toujours le signe.)
|
||
|
||
#### `sorted` avec `key`
|
||
|
||
Tu sais déjà que `sorted(liste)` renvoie une copie triée et que `reverse=True` inverse l'ordre. Le paramètre `key` permet de dire **selon quel critère** trier : on lui passe une **fonction**, que `sorted` appelle sur chaque élément ; le tri se fait sur les résultats.
|
||
|
||
```python
|
||
mots = ["kiwi", "Banane", "abricot", "Cerise", "fraise"]
|
||
|
||
print(sorted(mots)) # → ['Banane', 'Cerise', 'abricot', 'fraise', 'kiwi']
|
||
print(sorted(mots, key=str.lower)) # → ['abricot', 'Banane', 'Cerise', 'fraise', 'kiwi']
|
||
print(sorted(mots, key=len)) # → ['kiwi', 'Banane', 'Cerise', 'fraise', 'abricot']
|
||
```
|
||
|
||
- Par défaut, les majuscules passent avant les minuscules (`"B" < "a"`).
|
||
- `key=str.lower` compare les versions en minuscules : ordre alphabétique « naturel ».
|
||
- `key=len` trie par longueur ; à longueur égale, l'ordre d'origine est conservé (le tri est dit **stable**).
|
||
|
||
On peut passer n'importe quelle fonction, y compris une des tiennes :
|
||
|
||
```python
|
||
def derniere_lettre(mot):
|
||
return mot[-1]
|
||
|
||
|
||
mots = ["chat", "souris", "lapin", "zebre"]
|
||
print(sorted(mots, key=derniere_lettre)) # → ['zebre', 'lapin', 'souris', 'chat']
|
||
```
|
||
|
||
Les fonctions `min` et `max` acceptent aussi `key` :
|
||
|
||
```python
|
||
mots = ["chat", "hippopotame", "ours"]
|
||
print(max(mots, key=len)) # → hippopotame
|
||
```
|
||
|
||
---
|
||
|
||
### 6. Les fonctions `lambda`
|
||
|
||
Écrire une fonction complète avec `def` juste pour la passer à `sorted` est un peu lourd. Une **lambda** est une petite fonction **anonyme**, écrite en une seule expression :
|
||
|
||
```python
|
||
# Ces deux écritures sont équivalentes :
|
||
def double(x):
|
||
return x * 2
|
||
|
||
|
||
double_lambda = lambda x: x * 2
|
||
|
||
print(double(5)) # → 10
|
||
print(double_lambda(5)) # → 10
|
||
```
|
||
|
||
La syntaxe est `lambda parametres: expression`. Pas de `return` : la valeur de l'expression est automatiquement renvoyée. Une lambda ne peut contenir qu'**une seule expression** (pas de `if` sur plusieurs lignes, pas de boucle).
|
||
|
||
> On ne range pas normalement une lambda dans une variable comme ci-dessus (autant utiliser `def`). Une lambda s'utilise **directement là où on a besoin d'une fonction**, typiquement comme argument `key`.
|
||
|
||
```python
|
||
eleves = [("Alice", 15), ("Bob", 12), ("Chloé", 18), ("David", 12)]
|
||
|
||
# Trier par note (le 2e élément du tuple)
|
||
print(sorted(eleves, key=lambda eleve: eleve[1]))
|
||
# Trier par note décroissante
|
||
print(sorted(eleves, key=lambda eleve: eleve[1], reverse=True))
|
||
# Meilleur élève
|
||
print(max(eleves, key=lambda eleve: eleve[1]))
|
||
```
|
||
|
||
```text
|
||
[('Bob', 12), ('David', 12), ('Alice', 15), ('Chloé', 18)]
|
||
[('Chloé', 18), ('Alice', 15), ('Bob', 12), ('David', 12)]
|
||
('Chloé', 18)
|
||
```
|
||
|
||
Avec une liste de dictionnaires :
|
||
|
||
```python
|
||
contacts = [
|
||
{"nom": "Durand", "age": 42},
|
||
{"nom": "Martin", "age": 25},
|
||
{"nom": "Bernard", "age": 33},
|
||
]
|
||
for contact in sorted(contacts, key=lambda c: c["age"]):
|
||
print(contact["nom"], contact["age"])
|
||
```
|
||
|
||
```text
|
||
Martin 25
|
||
Bernard 33
|
||
Durand 42
|
||
```
|
||
|
||
#### Trier sur plusieurs critères
|
||
|
||
La lambda peut renvoyer un **tuple** : Python trie d'abord selon le premier élément, puis le deuxième en cas d'égalité. Pour inverser un critère numérique, on utilise le signe moins :
|
||
|
||
```python
|
||
eleves = [("Alice", 15), ("Bob", 12), ("Chloé", 18), ("David", 12), ("Emma", 15)]
|
||
# Note décroissante, puis nom alphabétique
|
||
classement = sorted(eleves, key=lambda e: (-e[1], e[0]))
|
||
print(classement)
|
||
```
|
||
|
||
```text
|
||
[('Chloé', 18), ('Alice', 15), ('Emma', 15), ('Bob', 12), ('David', 12)]
|
||
```
|
||
|
||
Compare avec l'exercice 8 du chapitre 04 : c'est la même idée, en une ligne.
|
||
|
||
#### Trier un dictionnaire par valeur
|
||
|
||
```python
|
||
scores = {"Léo": 1200, "Maya": 1850, "Sam": 900}
|
||
for nom, score in sorted(scores.items(), key=lambda paire: paire[1], reverse=True):
|
||
print(nom, score)
|
||
```
|
||
|
||
```text
|
||
Maya 1850
|
||
Léo 1200
|
||
Sam 900
|
||
```
|
||
|
||
`scores.items()` fournit des tuples `(nom, score)` ; on trie selon `paire[1]`, le score.
|
||
|
||
---
|
||
|
||
### 7. Les compréhensions de liste
|
||
|
||
Tu as souvent écrit ce schéma :
|
||
|
||
```python
|
||
nombres = [1, 2, 3, 4, 5]
|
||
carres = []
|
||
for n in nombres:
|
||
carres.append(n * n)
|
||
print(carres) # → [1, 4, 9, 16, 25]
|
||
```
|
||
|
||
Python propose une syntaxe compacte pour exactement ça, la **compréhension de liste** :
|
||
|
||
```python
|
||
nombres = [1, 2, 3, 4, 5]
|
||
carres = [n * n for n in nombres]
|
||
print(carres) # → [1, 4, 9, 16, 25]
|
||
```
|
||
|
||
Lis-la de gauche à droite : « la liste des `n * n` **pour** chaque `n` **dans** `nombres` ». C'est très proche de la notation mathématique { n² | n ∈ nombres }.
|
||
|
||
```text
|
||
[ n * n for n in nombres ]
|
||
───┬── ────────┬───────
|
||
│ └─ la boucle : d'où viennent les éléments
|
||
└─ l'expression : ce qu'on met dans la nouvelle liste
|
||
```
|
||
|
||
Exemples :
|
||
|
||
```python
|
||
prenoms = ["alice", "bob", "chloé"]
|
||
print([p.capitalize() for p in prenoms]) # → ['Alice', 'Bob', 'Chloé']
|
||
print([len(p) for p in prenoms]) # → [5, 3, 5]
|
||
|
||
print([i * 10 for i in range(5)]) # → [0, 10, 20, 30, 40]
|
||
|
||
nombres = [1, 2, 3]
|
||
print("-".join([str(n) for n in nombres])) # → 1-2-3
|
||
|
||
prix_ht = [10, 25.5, 100]
|
||
print([round(p * 1.2, 2) for p in prix_ht]) # → [12.0, 30.6, 120.0]
|
||
```
|
||
|
||
Note l'exemple avec `join` : la compréhension convertit chaque nombre en chaîne, ce qui résout l'erreur vue en section 3.
|
||
|
||
#### Filtrer avec `if`
|
||
|
||
On peut ajouter une condition à la fin pour ne garder que certains éléments :
|
||
|
||
```python
|
||
notes = [8, 15, 12, 6, 19, 10]
|
||
print([n for n in notes if n >= 10]) # → [15, 12, 19, 10]
|
||
|
||
mots = ["Python", "est", "un", "langage", "génial"]
|
||
print([m for m in mots if len(m) > 3]) # → ['Python', 'langage', 'génial']
|
||
print([m.upper() for m in mots if "a" in m]) # → ['LANGAGE', 'GÉNIAL']
|
||
```
|
||
|
||
Correspondance avec la boucle classique :
|
||
|
||
```python
|
||
# Compréhension :
|
||
resultat = [m.upper() for m in ["chat", "lion", "rat"] if "a" in m]
|
||
|
||
# Équivaut exactement à :
|
||
resultat2 = []
|
||
for m in ["chat", "lion", "rat"]:
|
||
if "a" in m:
|
||
resultat2.append(m.upper())
|
||
|
||
print(resultat == resultat2) # → True
|
||
```
|
||
|
||
#### Transformer avec `if/else`
|
||
|
||
Attention, différence importante : si tu veux **transformer** chaque élément selon une condition (et non filtrer), le `if ... else` se place **avant** le `for`. C'est une expression conditionnelle (vue au chapitre 05).
|
||
|
||
```python
|
||
notes = [8, 15, 12, 6]
|
||
print(["admis" if n >= 10 else "recalé" for n in notes]) # → ['recalé', 'admis', 'admis', 'recalé']
|
||
```
|
||
|
||
Pour bien retenir la différence entre les deux formes, voici leur schéma.
|
||
|
||
```text
|
||
[ expression for x in liste if condition ] ← FILTRE : moins d'éléments
|
||
[ valeur_si_vrai if condition else valeur_si_faux for x in liste ] ← TRANSFORME : autant d'éléments
|
||
```
|
||
|
||
#### Compréhensions imbriquées
|
||
|
||
On peut enchaîner plusieurs `for`, dans le même ordre que des boucles imbriquées :
|
||
|
||
```python
|
||
couleurs = ["rouge", "bleu"]
|
||
tailles = ["S", "M"]
|
||
print([f"{c}-{t}" for c in couleurs for t in tailles])
|
||
# → ['rouge-S', 'rouge-M', 'bleu-S', 'bleu-M']
|
||
|
||
grille = [[1, 2, 3], [4, 5, 6]]
|
||
print([valeur for ligne in grille for valeur in ligne]) # aplatir
|
||
# → [1, 2, 3, 4, 5, 6]
|
||
|
||
print([[0] * 3 for _ in range(2)]) # grille 2x3 correcte (cf. piège 8 du chapitre 04)
|
||
# → [[0, 0, 0], [0, 0, 0]]
|
||
```
|
||
|
||
```text
|
||
['rouge-S', 'rouge-M', 'bleu-S', 'bleu-M']
|
||
[1, 2, 3, 4, 5, 6]
|
||
[[0, 0, 0], [0, 0, 0]]
|
||
```
|
||
|
||
**Conseil :** une compréhension doit rester **lisible en un coup d'œil**. Si elle dépasse une ligne ou contient plus de deux `for`, une boucle classique est souvent plus claire.
|
||
|
||
---
|
||
|
||
### 8. Compréhensions de dictionnaire et d'ensemble
|
||
|
||
Même principe, avec des accolades.
|
||
|
||
#### Compréhension de dictionnaire
|
||
|
||
On écrit `{cle: valeur for ...}` :
|
||
|
||
```python
|
||
mots = ["chat", "éléphant", "rat"]
|
||
longueurs = {mot: len(mot) for mot in mots}
|
||
print(longueurs) # → {'chat': 4, 'éléphant': 8, 'rat': 3}
|
||
|
||
prix = {"pain": 1.2, "lait": 0.95, "caviar": 89.0}
|
||
print({article: p for article, p in prix.items() if p < 10})
|
||
# → {'pain': 1.2, 'lait': 0.95}
|
||
|
||
print({article: round(p * 2, 2) for article, p in prix.items()})
|
||
# → {'pain': 2.4, 'lait': 1.9, 'caviar': 178.0}
|
||
|
||
# Inverser un dictionnaire (clés ↔ valeurs)
|
||
codes = {"FR": "France", "IT": "Italie"}
|
||
print({pays: code for code, pays in codes.items()})
|
||
# → {'France': 'FR', 'Italie': 'IT'}
|
||
```
|
||
|
||
```text
|
||
{'chat': 4, 'éléphant': 8, 'rat': 3}
|
||
{'pain': 1.2, 'lait': 0.95}
|
||
{'pain': 2.4, 'lait': 1.9, 'caviar': 178.0}
|
||
{'France': 'FR', 'Italie': 'IT'}
|
||
```
|
||
|
||
#### Compréhension d'ensemble
|
||
|
||
`{expression for ...}` sans deux-points crée un ensemble (donc sans doublons) :
|
||
|
||
```python
|
||
mots = ["Chat", "chat", "CHIEN", "chien", "Lapin"]
|
||
print(sorted({m.lower() for m in mots})) # → ['chat', 'chien', 'lapin']
|
||
|
||
phrase = "le chat et le rat"
|
||
print(sorted({len(mot) for mot in phrase.split()})) # → [2, 3, 4]
|
||
```
|
||
|
||
#### Et les parenthèses ?
|
||
|
||
`(x for x in ...)` ne crée **pas** un tuple, mais un **générateur** : un objet qui produit les valeurs une par une, sans tout stocker en mémoire. On l'utilise surtout directement dans une fonction comme `sum`, `max`, `any` ou `all` (les parenthèses de la fonction suffisent) :
|
||
|
||
```python
|
||
nombres = [3, 8, 12, 5]
|
||
print(sum(n * n for n in nombres)) # → 242
|
||
print(any(n > 10 for n in nombres)) # → True (au moins un ?)
|
||
print(all(n > 0 for n in nombres)) # → True (tous ?)
|
||
print(max(len(m) for m in ["a", "abc", "ab"])) # → 3
|
||
```
|
||
|
||
`any` renvoie `True` si **au moins un** élément est vrai, `all` si **tous** le sont. Ils remplacent élégamment les boucles avec drapeau du chapitre 05.
|
||
|
||
---
|
||
|
||
### 9. Mettre tout ensemble : nettoyer un texte
|
||
|
||
Voici un exemple réaliste qui combine tout le chapitre : extraire les mots d'un texte, sans ponctuation ni majuscules, et trouver les plus fréquents.
|
||
|
||
```python
|
||
def nettoyer(texte):
|
||
"""Renvoie le texte en minuscules, la ponctuation remplacée par des espaces."""
|
||
texte = texte.lower()
|
||
for signe in ".,;:!?'\"()-":
|
||
texte = texte.replace(signe, " ")
|
||
return texte
|
||
|
||
|
||
def compter_mots(texte):
|
||
"""Renvoie un dictionnaire {mot: nombre d'apparitions}."""
|
||
compteur = {}
|
||
for mot in nettoyer(texte).split():
|
||
compteur[mot] = compteur.get(mot, 0) + 1
|
||
return compteur
|
||
|
||
|
||
texte = """Le chat dort. Le chien aboie ! Le chat se réveille,
|
||
et le chien, lui, s'endort. C'est la vie du chat."""
|
||
|
||
compteur = compter_mots(texte)
|
||
plus_frequents = sorted(compteur.items(), key=lambda paire: (-paire[1], paire[0]))
|
||
for rang, (mot, nombre) in enumerate(plus_frequents[:3], start=1):
|
||
print(f"{rang}. {mot:<8} {nombre}")
|
||
```
|
||
|
||
```text
|
||
1. le 4
|
||
2. chat 3
|
||
3. chien 2
|
||
```
|
||
|
||
Remarque le `for rang, (mot, nombre) in enumerate(...)` : `enumerate` fournit `(rang, (mot, nombre))`, et on déballe les deux niveaux d'un coup grâce aux parenthèses.
|
||
|
||
---
|
||
|
||
## Pièges fréquents
|
||
|
||
### Piège 1 — Oublier que les chaînes sont immuables
|
||
|
||
```python
|
||
nom = " alice "
|
||
nom.strip()
|
||
nom.capitalize()
|
||
print(f"[{nom}]") # → [ alice ]
|
||
```
|
||
|
||
Les méthodes ont renvoyé de nouvelles chaînes… qui ont été perdues. **Correction :** récupérer le résultat, éventuellement en enchaînant les méthodes.
|
||
|
||
```python
|
||
nom = " alice "
|
||
nom = nom.strip().capitalize()
|
||
print(f"[{nom}]") # → [Alice]
|
||
```
|
||
|
||
### Piège 2 — Écrire `join` à l'envers
|
||
|
||
```python
|
||
mots = ["a", "b", "c"]
|
||
print(mots.join("-"))
|
||
```
|
||
|
||
```text
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 2, in <module>
|
||
AttributeError: 'list' object has no attribute 'join'
|
||
```
|
||
|
||
**Correction :** c'est le séparateur qui appelle `join` : `"-".join(mots)`.
|
||
|
||
### Piège 3 — `join` sur des nombres
|
||
|
||
```python
|
||
print(", ".join([1, 2, 3]))
|
||
```
|
||
|
||
```text
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 1, in <module>
|
||
TypeError: sequence item 0: expected str instance, int found
|
||
```
|
||
|
||
**Correction :** convertir d'abord : `", ".join(str(n) for n in [1, 2, 3])`.
|
||
|
||
### Piège 4 — `split()` contre `split(" ")`
|
||
|
||
```python
|
||
texte = "un deux trois"
|
||
print(texte.split(" ")) # → ['un', '', 'deux', '', '', 'trois']
|
||
print(texte.split()) # → ['un', 'deux', 'trois']
|
||
```
|
||
|
||
Avec `" "`, chaque espace est un séparateur, ce qui crée des chaînes vides entre deux espaces consécutifs. **Correction :** pour découper en mots, utilise `split()` sans argument.
|
||
|
||
### Piège 5 — Confondre filtre et transformation dans une compréhension
|
||
|
||
```python
|
||
notes = [8, 15, 12]
|
||
print([n for n in notes if n >= 10 else 0])
|
||
```
|
||
|
||
```text
|
||
File "<stdin>", line 2
|
||
print([n for n in notes if n >= 10 else 0])
|
||
^^^^
|
||
SyntaxError: invalid syntax
|
||
```
|
||
|
||
Un `if` placé **après** le `for` est un filtre et n'accepte pas de `else`. **Correction :** pour transformer, place le `if/else` **avant** le `for`.
|
||
|
||
```python
|
||
notes = [8, 15, 12]
|
||
print([n if n >= 10 else 0 for n in notes]) # → [0, 15, 12]
|
||
```
|
||
|
||
### Piège 6 — Comparer des textes sans normaliser
|
||
|
||
```python
|
||
reponse = "Oui "
|
||
if reponse == "oui":
|
||
print("Validé")
|
||
else:
|
||
print("Refusé") # → Refusé
|
||
```
|
||
|
||
L'utilisateur a tapé une majuscule et un espace. **Correction :** `if reponse.strip().lower() == "oui":`.
|
||
|
||
### Piège 7 — Lambda avec un `return` ou plusieurs instructions
|
||
|
||
```python
|
||
carre = lambda x: return x * x
|
||
```
|
||
|
||
```text
|
||
File "<stdin>", line 1
|
||
carre = lambda x: return x * x
|
||
^^^^^^
|
||
SyntaxError: invalid syntax
|
||
```
|
||
|
||
Une lambda contient une **expression**, pas d'instruction : pas de `return`, pas d'affectation, pas de boucle. **Correction :** `lambda x: x * x`, ou une vraie fonction `def` si la logique est plus complexe.
|
||
|
||
### Piège 8 — Utiliser une variable de boucle après une compréhension
|
||
|
||
```python
|
||
carres = [n * n for n in range(5)]
|
||
print(n)
|
||
```
|
||
|
||
```text
|
||
Traceback (most recent call last):
|
||
File "<stdin>", line 2, in <module>
|
||
NameError: name 'n' is not defined
|
||
```
|
||
|
||
La variable d'une compréhension est **locale** à la compréhension, contrairement à celle d'une boucle `for` classique. C'est plutôt une bonne chose : elle n'écrase pas une variable existante.
|
||
|
||
---
|
||
|
||
## Exercices
|
||
|
||
### Exercice 1 — Manipulations de base ⭐
|
||
|
||
Pour `email = " Jean.Dupont@Exemple.FR "`, écris le code qui :
|
||
1. nettoie les espaces et met tout en minuscules ;
|
||
2. extrait la partie avant `@` (l'identifiant) et après (le domaine) avec `split` ;
|
||
3. extrait le prénom et le nom de l'identifiant (séparés par un point) et les affiche avec une majuscule ;
|
||
4. affiche les initiales en majuscules.
|
||
|
||
Sortie attendue :
|
||
|
||
```text
|
||
Email : jean.dupont@exemple.fr
|
||
Identifiant : jean.dupont
|
||
Domaine : exemple.fr
|
||
Nom complet : Jean Dupont
|
||
Initiales : J.D.
|
||
```
|
||
|
||
### Exercice 2 — Compréhensions simples ⭐
|
||
|
||
Avec des compréhensions (une ligne chacune), à partir de `nombres = [3, -1, 8, 0, -5, 12, 7]` et `mots = ["Soleil", "lune", "Étoile", "ciel", "Nuage"]` :
|
||
1. la liste des nombres positifs strictement ;
|
||
2. la liste des carrés de tous les nombres ;
|
||
3. la liste où chaque négatif est remplacé par 0 ;
|
||
4. la liste des mots commençant par une majuscule ;
|
||
5. un dictionnaire `{mot en minuscules: longueur}`.
|
||
|
||
Sortie attendue :
|
||
|
||
```text
|
||
[3, 8, 12, 7]
|
||
[9, 1, 64, 0, 25, 144, 49]
|
||
[3, 0, 8, 0, 0, 12, 7]
|
||
['Soleil', 'Étoile', 'Nuage']
|
||
{'soleil': 6, 'lune': 4, 'étoile': 6, 'ciel': 4, 'nuage': 5}
|
||
```
|
||
|
||
### Exercice 3 — Le chiffrement de César ⭐⭐
|
||
|
||
Écris une fonction `cesar(texte, decalage)` qui décale chaque **lettre minuscule non accentuée** (a-z) de `decalage` positions dans l'alphabet (en revenant au début après `z`) et laisse les autres caractères inchangés. Écris aussi `decoder(texte, decalage)`.
|
||
|
||
Indice : `alphabet = "abcdefghijklmnopqrstuvwxyz"`, `alphabet.index(lettre)` donne la position, et `% 26` permet de « boucler ».
|
||
|
||
```python
|
||
secret = cesar("rendez-vous a midi !", 3)
|
||
print(secret)
|
||
print(decoder(secret, 3))
|
||
print(cesar("xyz", 2))
|
||
```
|
||
|
||
```text
|
||
uhqghc-yrxv d plgl !
|
||
rendez-vous a midi !
|
||
zab
|
||
```
|
||
|
||
### Exercice 4 — Tableau de notes aligné ⭐⭐
|
||
|
||
À partir de deux listes, `eleves = ["Alice", "Bob", "Chloé", "Dominique"]` et `notes = [15.5, 9, 12.25, 17]`, affiche un tableau aligné avec un rang (`enumerate`), le nom (aligné à gauche sur 11 caractères), la note (à droite, 2 décimales) et « oui »/« non » pour l'admission (note ≥ 10). Termine par la moyenne.
|
||
|
||
Sortie attendue :
|
||
|
||
```text
|
||
# Élève Note Admis
|
||
1 Alice 15.50 oui
|
||
2 Bob 9.00 non
|
||
3 Chloé 12.25 oui
|
||
4 Dominique 17.00 oui
|
||
Moyenne : 13.44
|
||
```
|
||
|
||
### Exercice 5 — Trier des produits ⭐⭐
|
||
|
||
On donne :
|
||
|
||
```python
|
||
produits = [
|
||
{"nom": "Clavier", "prix": 49.9, "stock": 12},
|
||
{"nom": "souris", "prix": 19.9, "stock": 0},
|
||
{"nom": "Écran", "prix": 189.0, "stock": 4},
|
||
{"nom": "casque", "prix": 49.9, "stock": 7},
|
||
]
|
||
```
|
||
|
||
Avec `sorted`/`min`/`max` et des lambdas, affiche :
|
||
1. les noms triés par prix croissant, puis par nom (insensible à la casse) en cas d'égalité ;
|
||
2. le nom du produit le plus cher ;
|
||
3. les noms des produits en stock, triés alphabétiquement sans tenir compte de la casse ;
|
||
4. la valeur totale du stock (somme de prix × stock), arrondie à 2 décimales.
|
||
|
||
Sortie attendue :
|
||
|
||
```text
|
||
['souris', 'casque', 'Clavier', 'Écran']
|
||
Écran
|
||
['casque', 'Clavier', 'Écran']
|
||
1704.1
|
||
```
|
||
|
||
### Exercice 6 — Acronymes et titres ⭐⭐
|
||
|
||
Écris deux fonctions :
|
||
- `acronyme(expression)` qui renvoie les initiales en majuscules des mots, en ignorant les petits mots `le, la, les, de, des, du, et, en, un, une` (`"société nationale des chemins de fer"` → `"SNCF"`) ;
|
||
- `titre_propre(texte)` qui met une majuscule au début de chaque mot **sauf** pour les petits mots `le, la, les, de, des, du, et, en, un, une` (sauf s'il s'agit du premier mot), et réduit les espaces multiples à un seul.
|
||
|
||
```python
|
||
print(acronyme("société nationale des chemins de fer"))
|
||
print(acronyme("organisation des nations unies"))
|
||
print(titre_propre(" le seigneur des anneaux et la communauté de l'anneau "))
|
||
```
|
||
|
||
```text
|
||
SNCF
|
||
ONU
|
||
Le Seigneur des Anneaux et la Communauté de L'anneau
|
||
```
|
||
|
||
### Exercice 7 — Anagrammes ⭐⭐⭐
|
||
|
||
Deux mots sont des anagrammes s'ils contiennent exactement les mêmes lettres (« chien » et « niche »). Écris :
|
||
- `signature(mot)` : renvoie les lettres du mot en minuscules, triées, sous forme de chaîne (`"Niche"` → `"cehin"`) ;
|
||
- `sont_anagrammes(mot1, mot2)` ;
|
||
- `grouper_anagrammes(mots)` : renvoie la liste des groupes (listes) d'au moins 2 mots qui sont anagrammes entre eux, chaque groupe trié, et les groupes triés par leur premier mot.
|
||
|
||
```python
|
||
print(signature("Niche"))
|
||
print(sont_anagrammes("chien", "Niche"))
|
||
print(sont_anagrammes("chat", "tache"))
|
||
mots = ["chien", "niche", "chine", "rage", "gare", "chat", "ironique", "onirique", "Agre"]
|
||
print(grouper_anagrammes(mots))
|
||
```
|
||
|
||
```text
|
||
cehin
|
||
True
|
||
False
|
||
[['Agre', 'gare', 'rage'], ['chien', 'chine', 'niche'], ['ironique', 'onirique']]
|
||
```
|
||
|
||
### Exercice 8 — Validateur de plaques d'immatriculation ⭐⭐⭐
|
||
|
||
Une plaque française (système SIV) a le format `AB-123-CD` : 2 lettres majuscules, tiret, 3 chiffres, tiret, 2 lettres majuscules. Écris une fonction `normaliser_plaque(saisie)` qui :
|
||
- retire les espaces autour, met en majuscules ;
|
||
- accepte aussi les saisies sans tirets (`ab123cd`) ou avec des espaces à la place des tirets (`AB 123 CD`) ;
|
||
- renvoie la plaque au format `AB-123-CD` si elle est valide, ou `None` sinon.
|
||
|
||
Indice : supprime d'abord tirets et espaces, vérifie qu'il reste 7 caractères, puis vérifie chaque morceau avec le slicing, `isalpha()` et `isdigit()`.
|
||
|
||
```python
|
||
for saisie in ["AB-123-CD", "ab123cd", " xy 987 zt ", "A1-234-BC", "AB-12-CD", "ÉÉ-123-AA"]:
|
||
print(f"{saisie!r:15} → {normaliser_plaque(saisie)}")
|
||
```
|
||
|
||
```text
|
||
'AB-123-CD' → AB-123-CD
|
||
'ab123cd' → AB-123-CD
|
||
' xy 987 zt ' → XY-987-ZT
|
||
'A1-234-BC' → None
|
||
'AB-12-CD' → None
|
||
'ÉÉ-123-AA' → None
|
||
```
|
||
|
||
(`{saisie!r}` affiche la chaîne avec ses guillemets, ce qui rend les espaces visibles.)
|
||
|
||
---
|
||
|
||
## Mini-projet — L'analyseur de texte
|
||
|
||
### Cahier des charges
|
||
|
||
Écris un programme qui analyse un texte et produit un rapport. Le texte est stocké dans une variable (au [chapitre 07](07-fichiers-csv-json.md), tu pourras le lire depuis un fichier). Le rapport doit contenir :
|
||
|
||
1. le nombre de **caractères** (total et hors espaces) ;
|
||
2. le nombre de **mots**, de **mots différents** et de **phrases** (une phrase se termine par `.`, `!` ou `?`) ;
|
||
3. la **longueur moyenne** des mots ;
|
||
4. le **mot le plus long** ;
|
||
5. les **5 mots les plus fréquents** parmi les mots d'au moins 4 lettres (pour éviter « le », « de »…), avec une barre de `#` proportionnelle ;
|
||
6. les **lettres** les plus fréquentes (top 5), en ignorant les accents de base (é, è, ê → e ; à → a ; ç → c…).
|
||
|
||
**Contraintes :** structure en fonctions avec docstrings, utilisation de compréhensions, `sorted` avec `key`, `enumerate` ; affichage aligné grâce aux f-strings.
|
||
|
||
### Étapes suggérées
|
||
|
||
1. `normaliser(texte)` : minuscules + remplacement des lettres accentuées (un dictionnaire `{"é": "e", ...}` et une boucle de `replace`).
|
||
2. `extraire_mots(texte)` : remplace la ponctuation par des espaces puis `split()`. Garde l'apostrophe comme séparateur (« l'analyse » → « l », « analyse »).
|
||
3. `compter_phrases(texte)` : compte les `.`, `!` et `?` (attention aux `...` ou `?!` : tu peux d'abord remplacer `...` par `.`, ou compter les morceaux non vides après découpage).
|
||
4. `frequences(elements)` : renvoie un dictionnaire de comptage.
|
||
5. `top(compteur, n)` : renvoie les `n` paires les plus fréquentes (fréquence décroissante, puis ordre alphabétique).
|
||
6. `afficher_rapport(texte)` : assemble le tout.
|
||
|
||
### Pistes d'amélioration
|
||
|
||
- Ignorer une liste de **mots vides** (« dans », « pour », « avec »…) au lieu du filtre sur la longueur.
|
||
- Calculer un **indice de lisibilité** simple (mots par phrase, lettres par mot).
|
||
- Comparer deux textes : mots communs, mots propres à chacun (avec des ensembles).
|
||
- Détecter la **langue** (français ou anglais) d'après la fréquence de mots courants (« le », « the »).
|
||
- Lire le texte depuis un fichier et écrire le rapport dans un autre ([chapitre 07](07-fichiers-csv-json.md)).
|
||
|
||
---
|
||
|
||
## Corrigés
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 1</summary>
|
||
|
||
```python
|
||
email = " Jean.Dupont@Exemple.FR "
|
||
|
||
email = email.strip().lower()
|
||
identifiant, domaine = email.split("@")
|
||
prenom, nom = identifiant.split(".")
|
||
prenom = prenom.capitalize()
|
||
nom = nom.capitalize()
|
||
initiales = f"{prenom[0]}.{nom[0]}."
|
||
|
||
print(f"Email : {email}")
|
||
print(f"Identifiant : {identifiant}")
|
||
print(f"Domaine : {domaine}")
|
||
print(f"Nom complet : {prenom} {nom}")
|
||
print(f"Initiales : {initiales}")
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
Email : jean.dupont@exemple.fr
|
||
Identifiant : jean.dupont
|
||
Domaine : exemple.fr
|
||
Nom complet : Jean Dupont
|
||
Initiales : J.D.
|
||
```
|
||
|
||
On enchaîne `strip().lower()` : chaque méthode renvoie une nouvelle chaîne sur laquelle on applique la suivante. `split("@")` renvoie une liste de deux éléments qu'on déballe directement. Les initiales s'obtiennent avec `[0]`, le premier caractère.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 2</summary>
|
||
|
||
```python
|
||
nombres = [3, -1, 8, 0, -5, 12, 7]
|
||
mots = ["Soleil", "lune", "Étoile", "ciel", "Nuage"]
|
||
|
||
print([n for n in nombres if n > 0])
|
||
print([n * n for n in nombres])
|
||
print([n if n >= 0 else 0 for n in nombres])
|
||
print([m for m in mots if m[0].isupper()])
|
||
print({m.lower(): len(m) for m in mots})
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
[3, 8, 12, 7]
|
||
[9, 1, 64, 0, 25, 144, 49]
|
||
[3, 0, 8, 0, 0, 12, 7]
|
||
['Soleil', 'Étoile', 'Nuage']
|
||
{'soleil': 6, 'lune': 4, 'étoile': 6, 'ciel': 4, 'nuage': 5}
|
||
```
|
||
|
||
Lignes 1 et 4 : `if` **après** le `for` = filtre (moins d'éléments). Ligne 3 : `if/else` **avant** le `for` = transformation (autant d'éléments). `isupper()` reconnaît aussi les majuscules accentuées comme `É`. On aurait pu écrire la ligne 3 avec `max(n, 0)`.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 3</summary>
|
||
|
||
```python
|
||
ALPHABET = "abcdefghijklmnopqrstuvwxyz"
|
||
|
||
|
||
def cesar(texte, decalage):
|
||
"""Chiffre texte en décalant chaque lettre a-z de decalage positions."""
|
||
resultat = []
|
||
for caractere in texte:
|
||
if caractere in ALPHABET:
|
||
position = ALPHABET.index(caractere)
|
||
nouvelle_position = (position + decalage) % 26
|
||
resultat.append(ALPHABET[nouvelle_position])
|
||
else:
|
||
resultat.append(caractere)
|
||
return "".join(resultat)
|
||
|
||
|
||
def decoder(texte, decalage):
|
||
"""Déchiffre un texte chiffré par cesar avec le même décalage."""
|
||
return cesar(texte, -decalage)
|
||
|
||
|
||
secret = cesar("rendez-vous a midi !", 3)
|
||
print(secret)
|
||
print(decoder(secret, 3))
|
||
print(cesar("xyz", 2))
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
uhqghc-yrxv d plgl !
|
||
rendez-vous a midi !
|
||
zab
|
||
```
|
||
|
||
Le modulo `% 26` fait « boucler » l'alphabet : `x` (23) + 2 = 25 → `z`, `y` (24) + 2 = 26 % 26 = 0 → `a`. Décoder revient à chiffrer avec le décalage opposé ; en Python, `-3 % 26` vaut `23`, donc le modulo fonctionne aussi avec les nombres négatifs. On construit une liste de caractères puis on la `join` : c'est plus efficace que de concaténer des chaînes dans une boucle.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 4</summary>
|
||
|
||
```python
|
||
eleves = ["Alice", "Bob", "Chloé", "Dominique"]
|
||
notes = [15.5, 9, 12.25, 17]
|
||
|
||
print(f"{'#':<3}{'Élève':<11}{'Note':>6} Admis")
|
||
for rang, (eleve, note) in enumerate(zip(eleves, notes), start=1):
|
||
admis = "oui" if note >= 10 else "non"
|
||
print(f"{rang:<3}{eleve:<11}{note:>6.2f} {admis}")
|
||
|
||
moyenne = sum(notes) / len(notes)
|
||
print(f"Moyenne : {moyenne:.2f}")
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
# Élève Note Admis
|
||
1 Alice 15.50 oui
|
||
2 Bob 9.00 non
|
||
3 Chloé 12.25 oui
|
||
4 Dominique 17.00 oui
|
||
Moyenne : 13.44
|
||
```
|
||
|
||
`zip` associe chaque élève à sa note, et `enumerate` ajoute un rang : on déballe `(rang, (eleve, note))`. Les formats `<3`, `<11` et `>6.2f` fixent la largeur des colonnes ; les en-têtes utilisent les mêmes largeurs pour rester alignés. Pour mettre une chaîne littérale dans une f-string délimitée par `"`, on utilise des apostrophes : `{'Élève':<11}`.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 5</summary>
|
||
|
||
```python
|
||
produits = [
|
||
{"nom": "Clavier", "prix": 49.9, "stock": 12},
|
||
{"nom": "souris", "prix": 19.9, "stock": 0},
|
||
{"nom": "Écran", "prix": 189.0, "stock": 4},
|
||
{"nom": "casque", "prix": 49.9, "stock": 7},
|
||
]
|
||
|
||
# 1. Prix croissant, puis nom insensible à la casse
|
||
par_prix = sorted(produits, key=lambda p: (p["prix"], p["nom"].lower()))
|
||
print([p["nom"] for p in par_prix])
|
||
|
||
# 2. Le plus cher
|
||
print(max(produits, key=lambda p: p["prix"])["nom"])
|
||
|
||
# 3. Produits en stock, ordre alphabétique sans casse
|
||
en_stock = [p["nom"] for p in produits if p["stock"] > 0]
|
||
print(sorted(en_stock, key=str.lower))
|
||
|
||
# 4. Valeur du stock
|
||
valeur = sum(p["prix"] * p["stock"] for p in produits)
|
||
print(round(valeur, 2))
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
['souris', 'casque', 'Clavier', 'Écran']
|
||
Écran
|
||
['casque', 'Clavier', 'Écran']
|
||
1704.1
|
||
```
|
||
|
||
La clé de tri `(p["prix"], p["nom"].lower())` est un tuple : à prix égal (49.9), « casque » passe avant « clavier ». `max(..., key=...)` renvoie le dictionnaire entier, dont on prend le nom. Pour la valeur du stock, une expression génératrice dans `sum` évite de créer une liste intermédiaire. Note que « Écran » arrive en dernier car `"é"` est classé après toutes les lettres non accentuées : trier parfaitement du texte français demande des outils plus avancés (module `locale`).
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 6</summary>
|
||
|
||
```python
|
||
PETITS_MOTS = {"le", "la", "les", "de", "des", "du", "et", "en", "un", "une"}
|
||
|
||
|
||
def acronyme(expression):
|
||
"""Renvoie l'acronyme formé des initiales des mots qui ne sont pas des petits mots."""
|
||
return "".join(mot[0].upper() for mot in expression.split() if mot.lower() not in PETITS_MOTS)
|
||
|
||
|
||
def titre_propre(texte):
|
||
"""Met une majuscule aux mots importants et réduit les espaces."""
|
||
mots = texte.lower().split()
|
||
resultat = []
|
||
for i, mot in enumerate(mots):
|
||
if i == 0 or mot not in PETITS_MOTS:
|
||
resultat.append(mot.capitalize())
|
||
else:
|
||
resultat.append(mot)
|
||
return " ".join(resultat)
|
||
|
||
|
||
print(acronyme("société nationale des chemins de fer"))
|
||
print(acronyme("organisation des nations unies"))
|
||
print(titre_propre(" le seigneur des anneaux et la communauté de l'anneau "))
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
SNCF
|
||
ONU
|
||
Le Seigneur des Anneaux et la Communauté de L'anneau
|
||
```
|
||
|
||
Les petits mots sont rangés dans un **ensemble** partagé par les deux fonctions : le test `in` y est rapide, et la liste n'est écrite qu'une fois. Pour l'acronyme, une expression génératrice filtre les petits mots et prend l'initiale de chacun des autres ; `join` les assemble. Une règle plus simple comme « mots de plus de 2 lettres » ne marcherait pas : « des » fait 3 lettres et donnerait `SNDCF`. Pour `titre_propre`, `split()` sans argument élimine les espaces multiples, et `enumerate` permet de traiter le premier mot à part. `capitalize()` sur « l'anneau » donne « L'anneau » : gérer parfaitement les apostrophes demanderait de découper aussi sur `'`.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 7</summary>
|
||
|
||
```python
|
||
def signature(mot):
|
||
"""Renvoie les lettres du mot, en minuscules, triées alphabétiquement."""
|
||
return "".join(sorted(mot.lower()))
|
||
|
||
|
||
def sont_anagrammes(mot1, mot2):
|
||
"""Renvoie True si les deux mots sont des anagrammes."""
|
||
return signature(mot1) == signature(mot2)
|
||
|
||
|
||
def grouper_anagrammes(mots):
|
||
"""Renvoie les groupes d'au moins deux anagrammes, triés."""
|
||
groupes = {}
|
||
for mot in mots:
|
||
cle = signature(mot)
|
||
if cle not in groupes:
|
||
groupes[cle] = []
|
||
groupes[cle].append(mot)
|
||
resultat = [sorted(groupe, key=str.lower) for groupe in groupes.values() if len(groupe) >= 2]
|
||
return sorted(resultat, key=lambda groupe: groupe[0].lower())
|
||
|
||
|
||
print(signature("Niche"))
|
||
print(sont_anagrammes("chien", "Niche"))
|
||
print(sont_anagrammes("chat", "tache"))
|
||
mots = ["chien", "niche", "chine", "rage", "gare", "chat", "ironique", "onirique", "Agre"]
|
||
print(grouper_anagrammes(mots))
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
cehin
|
||
True
|
||
False
|
||
[['Agre', 'gare', 'rage'], ['chien', 'chine', 'niche'], ['ironique', 'onirique']]
|
||
```
|
||
|
||
L'idée clé : deux anagrammes ont la **même signature** (leurs lettres triées). `sorted("niche")` renvoie une liste de caractères, qu'on rassemble avec `"".join`. Pour grouper, on utilise un dictionnaire `{signature: [mots]}` : tous les anagrammes atterrissent sous la même clé. Une compréhension filtre ensuite les groupes d'au moins deux mots et trie chacun.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé de l'exercice 8</summary>
|
||
|
||
```python
|
||
def normaliser_plaque(saisie):
|
||
"""Renvoie la plaque au format AB-123-CD, ou None si la saisie est invalide."""
|
||
brut = saisie.strip().upper().replace("-", "").replace(" ", "")
|
||
if len(brut) != 7:
|
||
return None
|
||
|
||
debut, chiffres, fin = brut[:2], brut[2:5], brut[5:]
|
||
lettres_ok = all(c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" for c in debut + fin)
|
||
if not lettres_ok or not chiffres.isdigit():
|
||
return None
|
||
return f"{debut}-{chiffres}-{fin}"
|
||
|
||
|
||
for saisie in ["AB-123-CD", "ab123cd", " xy 987 zt ", "A1-234-BC", "AB-12-CD", "ÉÉ-123-AA"]:
|
||
print(f"{saisie!r:15} → {normaliser_plaque(saisie)}")
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
'AB-123-CD' → AB-123-CD
|
||
'ab123cd' → AB-123-CD
|
||
' xy 987 zt ' → XY-987-ZT
|
||
'A1-234-BC' → None
|
||
'AB-12-CD' → None
|
||
'ÉÉ-123-AA' → None
|
||
```
|
||
|
||
On normalise d'abord (majuscules, sans tirets ni espaces), puis on découpe avec le slicing. `isalpha()` aurait accepté `É` (c'est une lettre !), d'où le test explicite avec la liste des 26 lettres et `all(...)`. Plusieurs `return None` permettent de sortir dès qu'un problème est détecté. Plus tard, tu pourras découvrir les expressions régulières (module `re` de la bibliothèque standard, voir le [chapitre 09](09-modules-bibliotheque-standard.md) pour les modules), conçues pour ce genre de validation.
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary>Corrigé du mini-projet</summary>
|
||
|
||
```python
|
||
ACCENTS = {
|
||
"à": "a", "â": "a", "ä": "a",
|
||
"é": "e", "è": "e", "ê": "e", "ë": "e",
|
||
"î": "i", "ï": "i",
|
||
"ô": "o", "ö": "o",
|
||
"ù": "u", "û": "u", "ü": "u",
|
||
"ç": "c", "œ": "oe", "æ": "ae",
|
||
}
|
||
PONCTUATION = ".,;:!?'\"()[]«»-–—…’"
|
||
|
||
|
||
def normaliser(texte):
|
||
"""Met le texte en minuscules et retire les accents."""
|
||
texte = texte.lower()
|
||
for accentue, simple in ACCENTS.items():
|
||
texte = texte.replace(accentue, simple)
|
||
return texte
|
||
|
||
|
||
def extraire_mots(texte):
|
||
"""Renvoie la liste des mots du texte, sans ponctuation."""
|
||
for signe in PONCTUATION:
|
||
texte = texte.replace(signe, " ")
|
||
return texte.split()
|
||
|
||
|
||
def compter_phrases(texte):
|
||
"""Compte les phrases : morceaux non vides entre deux signes . ! ou ?"""
|
||
for signe in "!?":
|
||
texte = texte.replace(signe, ".")
|
||
morceaux = texte.split(".")
|
||
return len([m for m in morceaux if m.strip() != ""])
|
||
|
||
|
||
def frequences(elements):
|
||
"""Renvoie un dictionnaire {element: nombre d'apparitions}."""
|
||
compteur = {}
|
||
for element in elements:
|
||
compteur[element] = compteur.get(element, 0) + 1
|
||
return compteur
|
||
|
||
|
||
def top(compteur, n):
|
||
"""Renvoie les n paires (element, nombre) les plus fréquentes."""
|
||
paires = sorted(compteur.items(), key=lambda p: (-p[1], p[0]))
|
||
return paires[:n]
|
||
|
||
|
||
def afficher_top(titre, paires):
|
||
"""Affiche un classement avec une barre proportionnelle."""
|
||
print(f"\n{titre}")
|
||
maximum = paires[0][1]
|
||
for rang, (element, nombre) in enumerate(paires, start=1):
|
||
barre = "#" * round(nombre / maximum * 20)
|
||
print(f" {rang}. {element:<12}{nombre:>3} {barre}")
|
||
|
||
|
||
def afficher_rapport(texte):
|
||
"""Analyse le texte et affiche un rapport complet."""
|
||
mots = extraire_mots(texte.lower())
|
||
mots_normalises = extraire_mots(normaliser(texte))
|
||
nb_caracteres = len(texte)
|
||
nb_sans_espaces = len([c for c in texte if not c.isspace()])
|
||
longueur_moyenne = sum(len(m) for m in mots) / len(mots)
|
||
plus_long = max(mots, key=len)
|
||
|
||
print("=" * 40)
|
||
print(f"{'RAPPORT D’ANALYSE':^40}")
|
||
print("=" * 40)
|
||
print(f"Caractères (total) : {nb_caracteres}")
|
||
print(f"Caractères (hors espaces): {nb_sans_espaces}")
|
||
print(f"Mots : {len(mots)}")
|
||
print(f"Mots différents : {len(set(mots))}")
|
||
print(f"Phrases : {compter_phrases(texte)}")
|
||
print(f"Longueur moyenne des mots: {longueur_moyenne:.2f}")
|
||
print(f"Mot le plus long : {plus_long} ({len(plus_long)} lettres)")
|
||
|
||
mots_longs = [m for m in mots_normalises if len(m) >= 4]
|
||
afficher_top("Mots les plus fréquents (4 lettres et +) :", top(frequences(mots_longs), 5))
|
||
|
||
lettres = [c for c in normaliser(texte) if c.isalpha()]
|
||
afficher_top("Lettres les plus fréquentes :", top(frequences(lettres), 5))
|
||
|
||
|
||
texte = """Python est un langage de programmation. Python est apprécié
|
||
pour sa lisibilité : un programme Python se lit presque comme de l'anglais !
|
||
Les débutants apprécient Python, les experts aussi. Pourquoi ? Parce que
|
||
le langage est simple, puissant et polyvalent. Programmer en Python,
|
||
c'est programmer avec plaisir."""
|
||
|
||
afficher_rapport(texte)
|
||
```
|
||
|
||
Sortie :
|
||
|
||
```text
|
||
========================================
|
||
RAPPORT D’ANALYSE
|
||
========================================
|
||
Caractères (total) : 309
|
||
Caractères (hors espaces): 262
|
||
Mots : 47
|
||
Mots différents : 35
|
||
Phrases : 6
|
||
Longueur moyenne des mots: 5.32
|
||
Mot le plus long : programmation (13 lettres)
|
||
|
||
Mots les plus fréquents (4 lettres et +) :
|
||
1. python 5 ####################
|
||
2. langage 2 ########
|
||
3. programmer 2 ########
|
||
4. anglais 1 ####
|
||
5. apprecie 1 ####
|
||
|
||
Lettres les plus fréquentes :
|
||
1. e 33 ####################
|
||
2. p 22 #############
|
||
3. a 21 #############
|
||
4. s 19 ############
|
||
5. t 19 ############
|
||
```
|
||
|
||
**Points clés de la solution :**
|
||
|
||
- Chaque fonction a une responsabilité unique : `normaliser`, `extraire_mots`, `frequences` et `top` sont réutilisables pour d'autres analyses (et `frequences` sert deux fois : pour les mots et pour les lettres).
|
||
- Les accents sont gérés par un **dictionnaire de correspondances** parcouru avec `items()`.
|
||
- On compte les mots sur le texte en minuscules (en gardant les accents pour l'affichage du mot le plus long) et les fréquences sur le texte normalisé.
|
||
- `compter_phrases` uniformise les terminaisons, découpe, puis ne garde que les morceaux non vides, ce qui gère les `?!` ou les `...`.
|
||
- `top` trie par fréquence décroissante puis ordre alphabétique grâce à la clé `(-p[1], p[0])` : le classement est stable et reproductible.
|
||
- La longueur de la barre est proportionnelle au maximum (`nombre / maximum * 20`), quelle que soit la taille du texte.
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## Récapitulatif
|
||
|
||
- Une chaîne est une **séquence immuable** : indices, slicing, `in`, `len` et `for` fonctionnent, mais toute « modification » crée une nouvelle chaîne qu'il faut récupérer.
|
||
- Slicing : `s[debut:fin:pas]`, `fin` exclu ; `s[::-1]` inverse ; pas d'erreur en cas de dépassement.
|
||
- Méthodes clés : `lower/upper/capitalize/title`, `strip`, `find/count/startswith/endswith`, `replace`, `isdigit/isalpha`, **`split`** (chaîne → liste) et **`join`** (liste → chaîne, appelé sur le séparateur).
|
||
- Les f-strings acceptent des formats : `{x:.2f}`, `{nom:<10}`, `{n:>5}`, `{p:.1%}`, `{x=}`.
|
||
- `enumerate(liste, start=1)` donne `(indice, valeur)` ; `zip(a, b)` parcourt plusieurs listes en parallèle.
|
||
- `sorted(..., key=fonction, reverse=True)` trie selon un critère ; une clé tuple permet plusieurs critères.
|
||
- `lambda params: expression` crée une petite fonction anonyme, idéale pour `key`.
|
||
- Compréhensions : `[expr for x in seq if cond]` (liste), `{k: v for ...}` (dictionnaire), `{expr for ...}` (ensemble) ; `if` final = filtre, `a if cond else b` en tête = transformation.
|
||
- `sum(...)`, `any(...)`, `all(...)`, `max(...)` acceptent une expression génératrice.
|
||
|
||
### Checklist de validation
|
||
|
||
- [ ] Je sais extraire n'importe quel morceau d'une chaîne avec le slicing.
|
||
- [ ] Je sais nettoyer une saisie avec `strip()` et `lower()` et en récupérer le résultat.
|
||
- [ ] Je sais passer d'une chaîne à une liste avec `split` et revenir avec `join`.
|
||
- [ ] Je sais aligner un tableau avec les f-strings.
|
||
- [ ] J'utilise `enumerate` au lieu de `range(len(...))`.
|
||
- [ ] Je sais trier une liste de dictionnaires avec `sorted` et une `lambda`.
|
||
- [ ] Je sais écrire une compréhension avec filtre, et une avec transformation `if/else`.
|
||
- [ ] J'ai terminé l'analyseur de texte.
|
||
|
||
---
|
||
|
||
[← Chapitre précédent](05-fonctions.md) | [Sommaire](README.md) | [Chapitre suivant →](07-fichiers-csv-json.md)
|