19  TP récapitulatif — Analyse des notes d’une classe

Ce Travail Pratique mobilise toutes les notions du niveau Basique : variables, types, opérateurs, conditions, boucles, listes, tuples, dictionnaires, ensembles, fonctions natives.

🎯 Objectifs pédagogiques
  • Consolider votre maîtrise des 4 structures de données.
  • Enchaîner conditions et boucles dans un projet concret.
  • Utiliser les fonctions natives (min, max, sum, len, sorted, enumerate…).
  • Produire des affichages propres avec print et le formatage f-string.

Temps estimé : 2 h 30.

19.1 Contexte

Un professeur vous demande de l’aider à analyser les notes de sa classe. Voici les données :

notes = {
    "Alice":   14,
    "Bob":     12,
    "Charlie": 17,
    "Diana":   9,
    "Eve":     15,
    "Frank":   11,
    "Grace":   8,
    "Hugo":    13,
    "Iris":    18,
    "Julien":  10,
}

Vous allez écrire progressivement un programme qui répond aux demandes ci-dessous. Chaque partie est indépendante — si vous bloquez sur une, passez à la suivante.

Conseil méthodologique
  1. Lisez d’abord toutes les questions pour avoir une vue d’ensemble.
  2. Commencez par la Partie 1 (la plus simple) pour vous mettre en confiance.
  3. Testez après chaque question : n’écrivez pas 30 lignes sans vérifier.
  4. Réutilisez les variables des questions précédentes quand c’est pertinent.
  5. N’hésitez pas à revenir dans les chapitres précédents pour relire une méthode.

Partie 1 — Statistiques générales

Questions

  1. Calculer et afficher la note moyenne de la classe (arrondie à 2 décimales).
  2. Afficher la note la plus haute et la note la plus basse.
  3. Afficher le nom de l’élève ayant obtenu la meilleure note et celui ayant la plus basse.
  4. Calculer et afficher l’écart entre la meilleure et la moins bonne note.
notes = {
    "Alice":   14,
    "Bob":     12,
    "Charlie": 17,
    "Diana":   9,
    "Eve":     15,
    "Frank":   11,
    "Grace":   8,
    "Hugo":    13,
    "Iris":    18,
    "Julien":  10,
}

# Q1 : moyenne
moyenne = sum(notes.values()) / len(notes)
print(f"Moyenne : {round(moyenne, 2)}")

# Q2 : max et min
note_max = max(notes.values())
note_min = min(notes.values())
print(f"Note max : {note_max}")
print(f"Note min : {note_min}")

# Q3 : noms associés
# On parcourt les items pour retrouver qui a quelle note
for prenom, note in notes.items():
    if note == note_max:
        print(f"Meilleur : {prenom}")
    if note == note_min:
        print(f"Moins bon : {prenom}")

# Q4 : écart
print(f"Écart : {note_max - note_min}")
Moyenne : 12.7
Note max : 18
Note min : 8
Moins bon : Grace
Meilleur : Iris
Écart : 10

Alternative Q3 avec max(..., key=...)

Une version plus Pythonique utilise max sur les clés avec key :

# Astuce : on prend le MAX des clés selon leur valeur associée
meilleur = max(notes, key=notes.get)
moins_bon = min(notes, key=notes.get)

print(f"Meilleur : {meilleur}")
print(f"Moins bon : {moins_bon}")
Meilleur : Iris
Moins bon : Grace

Avantage : ne renvoie qu’un seul élève, même si plusieurs ont la même note (le premier trouvé). La version avec boucle renvoie tous les ex æquo.


Partie 2 — Analyse par mentions

On définit les mentions suivantes :

Note Mention
< 10 Insuffisant
10 ≤ note < 12 Passable
12 ≤ note < 14 Assez bien
14 ≤ note < 16 Bien
≥ 16 Très bien

Questions

  1. Parcourir le dictionnaire et afficher pour chaque élève une ligne de la forme : "Alice a obtenu 14 (Bien)".
  2. Compter combien d’élèves ont chaque mention.
  3. Afficher le nombre d’élèves ayant la moyenne (note ≥ 10) et le pourcentage de réussite.
notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# On factorise le calcul de la mention dans une fonction
def mention(note):
    if note < 10:
        return "Insuffisant"
    elif note < 12:
        return "Passable"
    elif note < 14:
        return "Assez bien"
    elif note < 16:
        return "Bien"
    else:
        return "Très bien"

# Q1 : afficher chaque élève avec sa mention
for prenom, note in notes.items():
    print(f"{prenom} a obtenu {note} ({mention(note)})")

# Q2 : compter les mentions
compteur = {}
for note in notes.values():
    m = mention(note)
    compteur[m] = compteur.get(m, 0) + 1
print("\nRépartition des mentions :")
for m, n in compteur.items():
    print(f"  {m} : {n}")

# Q3 : pourcentage de réussite
admis = sum(1 for n in notes.values() if n >= 10)
total = len(notes)
print(f"\nAdmis : {admis} / {total}")
print(f"Taux de réussite : {admis / total * 100:.1f}%")
Alice a obtenu 14 (Bien)
Bob a obtenu 12 (Assez bien)
Charlie a obtenu 17 (Très bien)
Diana a obtenu 9 (Insuffisant)
Eve a obtenu 15 (Bien)
Frank a obtenu 11 (Passable)
Grace a obtenu 8 (Insuffisant)
Hugo a obtenu 13 (Assez bien)
Iris a obtenu 18 (Très bien)
Julien a obtenu 10 (Passable)

Répartition des mentions :
  Bien : 2
  Assez bien : 2
  Très bien : 2
  Insuffisant : 2
  Passable : 2

Admis : 8 / 10
Taux de réussite : 80.0%

Notions mobilisées

  • dict.get(clé, 0) pour initialiser un compteur à 0 si la clé n’existe pas encore.
  • sum(1 for n in ... if ...) pour compter les éléments qui vérifient une condition (expression génératrice, vue en Partie 3 — à ce stade, on peut aussi utiliser un for explicite avec un compteur).
  • f"{x:.1f}" pour formater un float à 1 décimale (f-strings, vues en Partie 2).

Partie 3 — Doublons et ensembles

Questions

  1. Construire la liste des notes uniques (sans doublons) en utilisant un set. Combien de notes distinctes la classe a-t-elle obtenues ?
  2. Utiliser un set pour trouver les notes qui n’ont été obtenues qu’une seule fois.
notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# Q1 : notes distinctes
notes_distinctes = set(notes.values())
print(f"Nombre de notes distinctes : {len(notes_distinctes)}")
print(f"Notes distinctes : {sorted(notes_distinctes)}")

# Q2 : notes obtenues une seule fois
# On compte l'occurrence de chaque note, puis on filtre
toutes_notes = list(notes.values())

uniques = set()
for note in notes_distinctes:
    if toutes_notes.count(note) == 1:
        uniques.add(note)

print(f"Notes obtenues une seule fois : {sorted(uniques)}")
Nombre de notes distinctes : 10
Notes distinctes : [8, 9, 10, 11, 12, 13, 14, 15, 17, 18]
Notes obtenues une seule fois : [8, 9, 10, 11, 12, 13, 14, 15, 17, 18]

Version avec Counter (bibliothèque standard)

Python propose un outil dédié pour compter les occurrences :

from collections import Counter

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

compteur = Counter(notes.values())
print(compteur)

uniques = [n for n, c in compteur.items() if c == 1]
print(f"Notes uniques : {sorted(uniques)}")
Counter({14: 1, 12: 1, 17: 1, 9: 1, 15: 1, 11: 1, 8: 1, 13: 1, 18: 1, 10: 1})
Notes uniques : [8, 9, 10, 11, 12, 13, 14, 15, 17, 18]

On voit les modules en détail au niveau Opérationnel (Partie 2).


Partie 4 — Tirage aléatoire (bonus)

Question

  1. Utiliser le module random pour tirer au hasard un élève qui passera au tableau. Afficher son nom et sa note.
import random

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# random.choice prend un élément au hasard dans une séquence
prenom_au_hasard = random.choice(list(notes.keys()))
print(f"Au tableau : {prenom_au_hasard} (note : {notes[prenom_au_hasard]})")
Au tableau : Diana (note : 9)

Variante : tirer plusieurs élèves sans remise

import random

# random.sample tire k éléments DIFFÉRENTS (sans remise)
equipe = random.sample(list(notes.keys()), k=3)
print(f"Équipe pour l'oral : {equipe}")
Équipe pour l'oral : ['Hugo', 'Eve', 'Charlie']

Notions mobilisées

  • random.choice(seq) — tire un élément au hasard dans une séquence.
  • random.sample(seq, k) — tire k éléments distincts.
  • Conversion list(notes.keys()) car random.choice a besoin d’une séquence indexable.

Les modules sont vus plus en détail en Partie 2.


Partie 5 — Interaction utilisateur

Questions

  1. Demander un prénom à l’utilisateur et afficher sa note et sa mention. Gérer le cas où le prénom n’existe pas (message d’erreur approprié).
  2. Demander une note seuil (entier) et afficher la liste des élèves ayant une note au seuil.
import random

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

def mention(note):
    if note < 10:
        return "Insuffisant"
    elif note < 12:
        return "Passable"
    elif note < 14:
        return "Assez bien"
    elif note < 16:
        return "Bien"
    else:
        return "Très bien"

# Q1 : recherche par prénom
prenom_saisi = input("Quel prénom chercher ? ").strip()

if prenom_saisi in notes:
    note = notes[prenom_saisi]
    print(f"{prenom_saisi} a obtenu {note} ({mention(note)})")
else:
    print(f"Prénom '{prenom_saisi}' non trouvé dans la classe.")

# Q2 : filtrer par seuil
seuil = int(input("Note seuil : "))
print(f"\nÉlèves ayant une note ≥ {seuil} :")
for prenom, note in notes.items():
    if note >= seuil:
        print(f"  - {prenom} ({note})")

Démonstration (sans input) avec des valeurs simulées

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

def mention(note):
    if note < 10: return "Insuffisant"
    elif note < 12: return "Passable"
    elif note < 14: return "Assez bien"
    elif note < 16: return "Bien"
    else: return "Très bien"

# Simulation Q1
for prenom_saisi in ["Alice", "Xavier"]:
    if prenom_saisi in notes:
        note = notes[prenom_saisi]
        print(f"{prenom_saisi} a obtenu {note} ({mention(note)})")
    else:
        print(f"Prénom '{prenom_saisi}' non trouvé.")

# Simulation Q2
seuil = 14
print(f"\nÉlèves ayant une note ≥ {seuil} :")
for prenom, note in notes.items():
    if note >= seuil:
        print(f"  - {prenom} ({note})")
Alice a obtenu 14 (Bien)
Prénom 'Xavier' non trouvé.

Élèves ayant une note ≥ 14 :
  - Alice (14)
  - Charlie (17)
  - Eve (15)
  - Iris (18)

Points clés

  • prenom in notes : test d’appartenance sur les clés du dict.
  • .strip() sur la saisie utilisateur pour retirer les espaces parasites.
  • int(input(...)) pour convertir la saisie en entier.

Partie 6 — Classement et présentation

Questions

  1. Afficher le classement complet de la classe (du meilleur au moins bon) avec enumerate pour numéroter :

    1. Iris : 18
    2. Charlie : 17
    3. Eve : 15
    ...

    Indice : trier les items du dictionnaire par note décroissante avec :

    sorted(notes.items(), key=lambda t: t[1], reverse=True)

    (La syntaxe lambda sera vue en Partie 3 — à ce stade, accepter la formule telle quelle ou passer par une liste de tuples classique.)

  2. Construire la chaîne "Iris, Charlie, Eve, ..." (noms séparés par virgule) représentant le classement. Indice : utilisez .join().

  3. Pour chaque élève, construire un « badge » au format [ALICE-14] en mettant le prénom en majuscules (utilisez .upper()). Afficher tous les badges sur une seule ligne.

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# Q1 : classement numéroté
classement = sorted(notes.items(), key=lambda t: t[1], reverse=True)

print("=== Classement ===")
for rang, (prenom, note) in enumerate(classement, start=1):
    print(f"{rang}. {prenom} : {note}")

# Q2 : chaîne des noms, séparés par virgule
noms_classement = [prenom for prenom, note in classement]
chaine_noms = ", ".join(noms_classement)
print(f"\nOrdre : {chaine_noms}")

# Q3 : badges
badges = []
for prenom, note in notes.items():
    badge = f"[{prenom.upper()}-{note}]"
    badges.append(badge)
print("\nBadges :", " ".join(badges))
=== Classement ===
1. Iris : 18
2. Charlie : 17
3. Eve : 15
4. Alice : 14
5. Hugo : 13
6. Bob : 12
7. Frank : 11
8. Julien : 10
9. Diana : 9
10. Grace : 8

Ordre : Iris, Charlie, Eve, Alice, Hugo, Bob, Frank, Julien, Diana, Grace

Badges : [ALICE-14] [BOB-12] [CHARLIE-17] [DIANA-9] [EVE-15] [FRANK-11] [GRACE-8] [HUGO-13] [IRIS-18] [JULIEN-10]

Version sans lambda (Partie 1)

Puisqu’on n’a pas encore vu les lambdas, voici une alternative :

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# Construire une liste de tuples (note, prenom) puis trier naturellement
# Astuce : en inversant l'ordre (note, prenom), le tri se fait sur la note
paires = []
for prenom, note in notes.items():
    paires.append((note, prenom))

# Tri décroissant par note
paires.sort(reverse=True)

print("=== Classement ===")
for rang, (note, prenom) in enumerate(paires, start=1):
    print(f"{rang}. {prenom} : {note}")
=== Classement ===
1. Iris : 18
2. Charlie : 17
3. Eve : 15
4. Alice : 14
5. Hugo : 13
6. Bob : 12
7. Frank : 11
8. Julien : 10
9. Diana : 9
10. Grace : 8

Notions mobilisées

  • sorted(dict.items(), key=..., reverse=True) — trier un dict par valeur.
  • enumerate(..., start=1) — numérotation qui démarre à 1.
  • Déballage (prenom, note) dans la boucle.
  • .join(liste) pour concaténer des chaînes avec un séparateur.
  • Chaînage de méthodes (prenom.upper()).
  • f-strings pour le formatage propre.

🏆 Bonus — Pour aller plus loin

Une fois les 6 parties maîtrisées, essayez ces défis supplémentaires :

  1. Histogramme ASCII : affichez un diagramme en barres des notes, une ligne par élève, en utilisant * (une étoile par point de note). Exemple :

    Alice    | ************** (14)
    Charlie  | ***************** (17)
  2. Recherche d’ex æquo : affichez les paires d’élèves qui ont exactement la même note.

  3. Note la plus fréquente : quelle est la note qui revient le plus souvent dans la classe ? (Si plusieurs, affichez toutes.)

Histogramme

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# Largeur max des prénoms pour aligner
largeur = max(len(p) for p in notes)

for prenom, note in notes.items():
    barres = "*" * note
    print(f"{prenom:<{largeur}} | {barres} ({note})")
Alice   | ************** (14)
Bob     | ************ (12)
Charlie | ***************** (17)
Diana   | ********* (9)
Eve     | *************** (15)
Frank   | *********** (11)
Grace   | ******** (8)
Hugo    | ************* (13)
Iris    | ****************** (18)
Julien  | ********** (10)

Ex æquo

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

# Inverser : note → liste des prénoms
par_note = {}
for prenom, note in notes.items():
    if note not in par_note:
        par_note[note] = []
    par_note[note].append(prenom)

# Afficher les notes avec plusieurs élèves
print("Ex æquo :")
for note, prenoms in par_note.items():
    if len(prenoms) > 1:
        print(f"  Note {note} : {', '.join(prenoms)}")
Ex æquo :

Sur notre jeu de données, il n’y a pas d’ex æquo car toutes les notes sont distinctes. Testez en modifiant une note pour créer un doublon.

Note la plus fréquente

from collections import Counter

notes = {
    "Alice":   14, "Bob":     12, "Charlie": 17, "Diana":   9, "Eve":     15,
    "Frank":   11, "Grace":   8, "Hugo":    13, "Iris":    18, "Julien":  10,
}

compteur = Counter(notes.values())
freq_max = max(compteur.values())

plus_frequentes = [n for n, c in compteur.items() if c == freq_max]
print(f"Note(s) la/les plus fréquente(s) : {plus_frequentes} ({freq_max} occurrences)")
Note(s) la/les plus fréquente(s) : [14, 12, 17, 9, 15, 11, 8, 13, 18, 10] (1 occurrences)

✅ Validation du niveau Basique

Vous êtes arrivé(e) au bout du TP !

Si vous avez réussi à résoudre les 6 parties (même avec l’aide des solutions pour quelques-unes), vous maîtrisez l’essentiel du niveau Basique TOSA :

  • ✅ Variables, types et conversions
  • ✅ Opérateurs arithmétiques, logiques et de comparaison
  • ✅ Chaînes de caractères et leurs méthodes
  • ✅ Conditions if/elif/else
  • ✅ Les 4 structures : listes, tuples, dictionnaires, ensembles
  • ✅ Boucles for / while, break / continue
  • ✅ Fonctions natives : min, max, sum, len, sorted, enumerate, zip

Score TOSA estimé : 351-550 (niveau Basique).

Prêt(e) pour la Partie 2 — Niveau Opérationnel ? Vous y découvrirez les fonctions (vos propres fonctions !), les f-strings, la mutabilité et bien plus.

← Chapitre précédent : Fonctions nativesPartie 2 — Niveau Opérationnel →