51 KiB
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 (
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
enumerateetzip;- trier finement avec
sorted(..., key=...)et des fonctionslambda;- 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. 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).
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()
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 » :
mot = "Python"
mot[0] = "J"
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 :
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.
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
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
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 :
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. Il fonctionne exactement pareil sur les chaînes : chaine[debut:fin:pas], avec fin exclu.
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
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.
| 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 :
mot = "chat"
print(mot[2:100]) # → at
print(mot[10:] == "") # → True (on obtient une chaîne vide)
Quelques usages typiques :
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
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 :
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.
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) :
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
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
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 :
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 :
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 :
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))
le chat dort
le-chat-dort
lechatdort
le, chat, dort
le
chat
dort
join n'accepte que des chaînes :
nombres = [1, 2, 3]
print("-".join(nombres))
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 :
phrase = " python est super "
propre = " ".join(phrase.split())
print(f"[{propre}]") # → [python est super]
Aligner et remplir
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 :
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 :
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}")
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.
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.
podium = ["Alice", "Bob", "Chloé"]
for i, nom in enumerate(podium):
print(i, nom)
0 Alice
1 Bob
2 Chloé
Le paramètre start permet de commencer à compter ailleurs qu'à 0 (pratique pour l'affichage humain) :
podium = ["Alice", "Bob", "Chloé"]
for rang, nom in enumerate(podium, start=1):
print(f"{rang}. {nom}")
1. Alice
2. Bob
3. Chloé
Ça marche aussi sur une chaîne :
for position, lettre in enumerate("abc"):
print(f"La lettre {lettre} est en position {position}")
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).
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}")
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 :
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 :
temperatures = [12, 15, 14, 18, 21]
for avant, apres in zip(temperatures, temperatures[1:]):
print(f"{avant} → {apres} : {apres - avant:+d}")
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.
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.lowercompare les versions en minuscules : ordre alphabétique « naturel ».key=lentrie 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 :
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 :
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 :
# 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 argumentkey.
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]))
[('Bob', 12), ('David', 12), ('Alice', 15), ('Chloé', 18)]
[('Chloé', 18), ('Alice', 15), ('Bob', 12), ('David', 12)]
('Chloé', 18)
Avec une liste de dictionnaires :
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"])
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 :
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)
[('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
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)
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 :
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 :
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 }.
[ 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 :
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 :
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 :
# 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).
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.
[ 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 :
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]]
['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 ...} :
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'}
{'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) :
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) :
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.
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}")
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
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.
nom = " alice "
nom = nom.strip().capitalize()
print(f"[{nom}]") # → [Alice]
Piège 2 — Écrire join à l'envers
mots = ["a", "b", "c"]
print(mots.join("-"))
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
print(", ".join([1, 2, 3]))
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(" ")
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
notes = [8, 15, 12]
print([n for n in notes if n >= 10 else 0])
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.
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
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
carre = lambda x: return x * x
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
carres = [n * n for n in range(5)]
print(n)
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 :
- nettoie les espaces et met tout en minuscules ;
- extrait la partie avant
@(l'identifiant) et après (le domaine) avecsplit; - extrait le prénom et le nom de l'identifiant (séparés par un point) et les affiche avec une majuscule ;
- affiche les initiales en majuscules.
Sortie attendue :
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"] :
- la liste des nombres positifs strictement ;
- la liste des carrés de tous les nombres ;
- la liste où chaque négatif est remplacé par 0 ;
- la liste des mots commençant par une majuscule ;
- un dictionnaire
{mot en minuscules: longueur}.
Sortie attendue :
[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 ».
secret = cesar("rendez-vous a midi !", 3)
print(secret)
print(decoder(secret, 3))
print(cesar("xyz", 2))
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 :
# É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 :
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 :
- les noms triés par prix croissant, puis par nom (insensible à la casse) en cas d'égalité ;
- le nom du produit le plus cher ;
- les noms des produits en stock, triés alphabétiquement sans tenir compte de la casse ;
- la valeur totale du stock (somme de prix × stock), arrondie à 2 décimales.
Sortie attendue :
['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 motsle, 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 motsle, 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.
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 "))
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.
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))
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-CDsi elle est valide, ouNonesinon.
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().
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)}")
'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, tu pourras le lire depuis un fichier). Le rapport doit contenir :
- le nombre de caractères (total et hors espaces) ;
- le nombre de mots, de mots différents et de phrases (une phrase se termine par
.,!ou?) ; - la longueur moyenne des mots ;
- le mot le plus long ;
- les 5 mots les plus fréquents parmi les mots d'au moins 4 lettres (pour éviter « le », « de »…), avec une barre de
#proportionnelle ; - 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
normaliser(texte): minuscules + remplacement des lettres accentuées (un dictionnaire{"é": "e", ...}et une boucle dereplace).extraire_mots(texte): remplace la ponctuation par des espaces puissplit(). Garde l'apostrophe comme séparateur (« l'analyse » → « l », « analyse »).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).frequences(elements): renvoie un dictionnaire de comptage.top(compteur, n): renvoie lesnpaires les plus fréquentes (fréquence décroissante, puis ordre alphabétique).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).
Corrigés
Corrigé de l'exercice 1
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 :
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.
Corrigé de l'exercice 2
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 :
[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).
Corrigé de l'exercice 3
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 :
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.
Corrigé de l'exercice 4
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 :
# É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}.
Corrigé de l'exercice 5
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 :
['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).
Corrigé de l'exercice 6
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 :
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 '.
Corrigé de l'exercice 7
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 :
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.
Corrigé de l'exercice 8
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 :
'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 pour les modules), conçues pour ce genre de validation.
Corrigé du mini-projet
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 :
========================================
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,frequencesettopsont réutilisables pour d'autres analyses (etfrequencessert 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_phrasesuniformise les terminaisons, découpe, puis ne garde que les morceaux non vides, ce qui gère les?!ou les....toptrie 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.
Récapitulatif
- Une chaîne est une séquence immuable : indices, slicing,
in,lenetforfonctionnent, mais toute « modification » crée une nouvelle chaîne qu'il faut récupérer. - Slicing :
s[debut:fin:pas],finexclu ;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) etjoin(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: expressioncrée une petite fonction anonyme, idéale pourkey.- Compréhensions :
[expr for x in seq if cond](liste),{k: v for ...}(dictionnaire),{expr for ...}(ensemble) ;iffinal = filtre,a if cond else ben 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()etlower()et en récupérer le résultat. - Je sais passer d'une chaîne à une liste avec
splitet revenir avecjoin. - Je sais aligner un tableau avec les f-strings.
- J'utilise
enumerateau lieu derange(len(...)). - Je sais trier une liste de dictionnaires avec
sortedet unelambda. - Je sais écrire une compréhension avec filtre, et une avec transformation
if/else. - J'ai terminé l'analyseur de texte.