Files
TermNSI/Revisions_Rentree/TP_ZEvent.md
T
2026-09-07 01:03:32 +02:00

16 KiB

TP : Z-Event - La régie de données d'un marathon caritatif

Thème : Révisions Python - variables, fonctions, boucles, listes, dictionnaires


Contexte

Depuis 2016, le Z-Event réunit chaque année des dizaines de streamers francophones pendant plusieurs jours d'affilée, dans un même lieu, pour récolter des dons au profit d'une association. L'événement a été créé par ZeratoR et Dach. Le principe est simple : les spectateurs donnent, les streamers relèvent des défis, et un compteur géant affiche la cagnotte en direct.

Derrière ce compteur, il y a du code. Chaque don qui tombe doit être enregistré, additionné, attribué à la chaîne sur laquelle il a été fait, comparé aux paliers annoncés, puis réaffiché en moins d'une seconde sur tous les écrans. C'est ce travail que vous allez faire ici.

Vous êtes bénévole à la régie de données de l'événement. Votre mission : écrire les outils Python qui alimentent le compteur et les classements.

Les données de ce TP (montants, pseudos, horaires, cagnottes) sont entièrement inventées pour l'exercice. Elles ne correspondent à aucune édition réelle du Z-Event.


Objectifs pédagogiques

  • Manipuler des variables et reconnaître une erreur de type
  • Écrire des fonctions qui renvoient une valeur au lieu de l'afficher
  • Parcourir une liste avec une boucle et accumuler un résultat
  • Construire et exploiter des dictionnaires, y compris des dictionnaires de listes
  • Réinvestir ces notions dans les chapitres de Terminale : POO, structures linéaires, graphes

Aucune de ces notions n'est nouvelle : tout a été vu en Première. L'objectif est de les remettre en service après la coupure estivale.


Les données

Copiez ce bloc en tête de votre fichier. Toutes les parties du TP s'appuient dessus.

# Toutes ces données sont inventées pour l'exercice.
objectif = 2500000
collecte = 1837450.75

dons = [
    {"pseudo": "Nyx_42",       "streamer": "Antoine Daniel",     "montant": 5.0,   "heure": "18:04"},
    {"pseudo": "TitiLeBg",     "streamer": "MisterMV",           "montant": 20.0,  "heure": "18:11"},
    {"pseudo": "Camille_R",    "streamer": "Joueur du Grenier",  "montant": 50.0,  "heure": "18:23"},
    {"pseudo": "Nyx_42",       "streamer": "Baghera Jones",      "montant": 15.5,  "heure": "18:37"},
    {"pseudo": "Poulpe3000",   "streamer": "Horty",              "montant": 2.0,   "heure": "18:41"},
    {"pseudo": "Anonyme",      "streamer": "Ultia",              "montant": 120.0, "heure": "18:52"},
    {"pseudo": "Sam_du_59",    "streamer": "Antoine Daniel",     "montant": 10.0,  "heure": "19:03"},
    {"pseudo": "Camille_R",    "streamer": "Sylvain Levy",       "montant": 30.0,  "heure": "19:14"},
    {"pseudo": "Lulu_2007",    "streamer": "ZeratoR",            "montant": 8.5,   "heure": "19:20"},
    {"pseudo": "TitiLeBg",     "streamer": "Etoiles",            "montant": 45.0,  "heure": "19:33"},
    {"pseudo": "Marion_NSI",   "streamer": "Joueur du Grenier",  "montant": 75.0,  "heure": "19:47"},
    {"pseudo": "Poulpe3000",   "streamer": "MisterMV",           "montant": 12.0,  "heure": "19:55"},
    {"pseudo": "Anonyme",      "streamer": "Domingo",            "montant": 250.0, "heure": "20:08"},
    {"pseudo": "Kevin_B",      "streamer": "Baghera Jones",      "montant": 5.0,   "heure": "20:16"},
    {"pseudo": "Nyx_42",       "streamer": "Maghla",             "montant": 60.0,  "heure": "20:29"},
    {"pseudo": "Sam_du_59",    "streamer": "Horty",              "montant": 3.5,   "heure": "20:38"},
    {"pseudo": "Marion_NSI",   "streamer": "Antoine Daniel",     "montant": 100.0, "heure": "20:44"},
    {"pseudo": "Lulu_2007",    "streamer": "Alphacast",          "montant": 18.0,  "heure": "20:57"},
    {"pseudo": "Camille_R",    "streamer": "Ultia",              "montant": 40.0,  "heure": "21:05"},
    {"pseudo": "Kevin_B",      "streamer": "Ponce",              "montant": 22.5,  "heure": "21:12"},
    {"pseudo": "TitiLeBg",     "streamer": "Joueur du Grenier",  "montant": 15.0,  "heure": "21:26"},
    {"pseudo": "Anonyme",      "streamer": "MisterMV",           "montant": 500.0, "heure": "21:39"},
    {"pseudo": "Poulpe3000",   "streamer": "Sylvain Levy",       "montant": 7.5,   "heure": "21:48"},
    {"pseudo": "Marion_NSI",   "streamer": "Baghera Jones",      "montant": 35.0,  "heure": "21:59"},
]

Partie 1 : Le compteur (variables et types)

1.1. État de la cagnotte

Question 1.1 : Calculez dans une variable pourcentage la part de l'objectif déjà atteinte, arrondie à deux décimales.

pourcentage = # À compléter

print(f"Objectif atteint à {pourcentage} %")  # Objectif atteint à 73.5 %

Question 1.2 : Calculez dans une variable reste la somme qu'il manque encore pour atteindre l'objectif.

reste = # À compléter

print(f"Il manque {reste} euros")  # Il manque 662549.25 euros

1.2. Le piège du compteur

Le compteur du site reçoit les montants sous forme de chaînes de caractères, parce qu'ils arrivent d'un formulaire web. Le code ci-dessous ne fait donc pas ce qu'on croit.

Question 1.3 : Exécutez ce code, observez le résultat, puis corrigez-le pour que total contienne bien la somme des deux dons.

don_1 = "25"
don_2 = "40"

total = don_1 + don_2
print(total)          # affiche 2540 au lieu de 65

# Correction :
total = # À compléter
print(total)          # 65

Partie 2 : L'annonce des dons (fonctions)

Un don qui tombe doit être annoncé à l'écran. Ces fonctions produisent les messages.

2.1. Formater un don

Question 2.1 : Écrivez la fonction formater_don(pseudo, montant) qui renvoie la chaîne d'annonce. Attention : elle doit la renvoyer, pas l'afficher.

def formater_don(pseudo, montant):
    # À compléter
    pass

# Test
print(formater_don("Nyx_42", 5.0))          # Nyx_42 vient de donner 5.0 euros
print(formater_don("Marion_NSI", 100.0))    # Marion_NSI vient de donner 100.0 euros

2.2. Repérer les gros dons

La régie déclenche une animation spéciale au-delà d'un certain montant.

Question 2.2 : Écrivez est_gros_don(montant, seuil) qui renvoie True si le montant est supérieur ou égal au seuil, False sinon. Le seuil vaut 50 par défaut.

def est_gros_don(montant, seuil=50):
    # À compléter
    pass

# Test
print(est_gros_don(120.0))       # True
print(est_gros_don(50.0))        # True
print(est_gros_don(20.0))        # False
print(est_gros_don(20.0, 10))    # True

2.3. Les paliers

Les organisateurs annoncent des paliers au fil de la collecte.

Cagnotte Palier
moins de 500 000 "Départ"
de 500 000 à 999 999 "Palier 1"
de 1 000 000 à 1 999 999 "Palier 2"
2 000 000 et plus "Objectif"

Question 2.3 : Écrivez palier(total) qui renvoie le nom du palier atteint.

def palier(total):
    # À compléter
    pass

# Test
print(palier(120000))        # Départ
print(palier(750000))        # Palier 1
print(palier(1837450.75))    # Palier 2
print(palier(2500000))       # Objectif

2.4. Le message complet

Question 2.4 : Écrivez annonce_regie(pseudo, montant) qui renvoie le message affiché en régie. Votre fonction doit appeler formater_don et est_gros_don : ne recopiez pas leur contenu.

Le message se termine par " [GROS DON]" lorsque le montant déclenche l'animation.

def annonce_regie(pseudo, montant):
    # À compléter
    pass

# Test
print(annonce_regie("Poulpe3000", 2.0))    # Poulpe3000 vient de donner 2.0 euros
print(annonce_regie("Anonyme", 250.0))     # Anonyme vient de donner 250.0 euros [GROS DON]

Partie 3 : L'historique des dons (boucles et listes)

La liste dons contient les vingt-quatre derniers dons enregistrés. Chaque don est un dictionnaire à quatre clés : pseudo, streamer, montant, heure.

3.1. Le total collecté

Question 3.1 : Écrivez total_dons(liste_dons) qui renvoie la somme de tous les montants. Utilisez une boucle et un accumulateur, pas la fonction sum.

def total_dons(liste_dons):
    # À compléter
    pass

# Test
print(total_dons(dons))    # 1449.5
print(total_dons([]))      # 0

3.2. Le don le plus élevé

Question 3.2 : Écrivez plus_gros_don(liste_dons) qui renvoie le dictionnaire du don le plus élevé. L'usage de max est interdit. On suppose la liste non vide.

def plus_gros_don(liste_dons):
    # À compléter
    pass

# Test
record = plus_gros_don(dons)
print(record["pseudo"], record["montant"])    # Anonyme 500.0

3.3. Compter et filtrer

Question 3.3 : Écrivez compter_gros_dons(liste_dons, seuil) qui renvoie le nombre de dons supérieurs ou égaux au seuil. Réutilisez est_gros_don.

def compter_gros_dons(liste_dons, seuil=50):
    # À compléter
    pass

# Test
print(compter_gros_dons(dons))         # 7
print(compter_gros_dons(dons, 100))    # 4

Question 3.4 : Écrivez donateurs(liste_dons) qui renvoie la liste des pseudos sans doublon, dans l'ordre de leur premier don.

def donateurs(liste_dons):
    # À compléter
    pass

# Test
print(donateurs(dons))
# ['Nyx_42', 'TitiLeBg', 'Camille_R', 'Poulpe3000', 'Anonyme', 'Sam_du_59', 'Lulu_2007', 'Marion_NSI', 'Kevin_B']

3.4. Le bandeau des derniers dons

Le site n'affiche que les tout derniers dons reçus.

Question 3.5 : Écrivez derniers_dons(liste_dons, n) qui renvoie les n derniers dons de la liste, en utilisant une tranche.

def derniers_dons(liste_dons, n):
    # À compléter
    pass

# Test
for don in derniers_dons(dons, 3):
    print(don["heure"], don["pseudo"])
# 21:39 Anonyme
# 21:48 Poulpe3000
# 21:59 Marion_NSI

Partie 4 : Le classement des chaînes (dictionnaires)

Chaque don est attribué à la chaîne sur laquelle il a été fait. La régie tient à jour la cagnotte de chaque streamer.

4.1. La cagnotte de chaque chaîne

Question 4.1 : Écrivez cagnottes(liste_dons) qui renvoie un dictionnaire associant à chaque streamer le total des dons reçus sur sa chaîne.

def cagnottes(liste_dons):
    # À compléter
    pass

# Test
totaux = cagnottes(dons)
print(totaux["Antoine Daniel"])       # 115.0
print(totaux["Joueur du Grenier"])    # 140.0
print(len(totaux))                    # 13

4.2. La chaîne en tête

Question 4.2 : Écrivez meilleure_chaine(totaux) qui renvoie le nom du streamer dont la cagnotte est la plus élevée.

Rappel : for cle in dictionnaire parcourt les clés.

def meilleure_chaine(totaux):
    # À compléter
    pass

# Test
print(meilleure_chaine(cagnottes(dons)))    # MisterMV

4.3. Le détail des dons

On veut maintenant, pour chaque streamer, la liste des montants reçus et plus seulement leur somme : un dictionnaire dont les valeurs sont des listes.

Question 4.3 : Écrivez dons_par_chaine(liste_dons) qui renvoie un dictionnaire associant à chaque streamer la liste des montants reçus, dans l'ordre chronologique.

def dons_par_chaine(liste_dons):
    # À compléter
    pass

# Test
detail = dons_par_chaine(dons)
print(detail["MisterMV"])         # [20.0, 12.0, 500.0]
print(detail["Baghera Jones"])    # [15.5, 5.0, 35.0]

4.4. Le podium

Question 4.4 : Écrivez podium(totaux) qui renvoie la liste des trois premiers streamers, du plus gros total au plus petit.

Indication : sorted(totaux, key=..., reverse=True) trie les clés selon un critère.

def podium(totaux):
    # À compléter
    pass

# Test
print(podium(cagnottes(dons)))
# ['MisterMV', 'Domingo', 'Ultia']

Partie 5 : Le planning et les raids (synthèse)

Un raid, c'est lorsqu'un streamer envoie son public sur une autre chaîne à la fin de son passage. Cette partie combine tout ce qui précède.

Copiez ce second bloc de données à la suite du premier.

planning = [
    ("Antoine Daniel",    18, 20),
    ("MisterMV",          20, 22),
    ("Joueur du Grenier", 22, 24),
    ("Baghera Jones",      0,  3),
    ("Ultia",              3,  6),
]

raids = {
    "Antoine Daniel":    ["MisterMV"],
    "MisterMV":          ["Joueur du Grenier", "Horty"],
    "Joueur du Grenier": ["Baghera Jones"],
    "Baghera Jones":     ["Ultia"],
    "Ultia":             [],
    "Horty":             ["Sylvain Levy"],
    "Sylvain Levy":      [],
}

5.1. Qui est à l'antenne

Le planning est une liste de triplets (streamer, heure_de_debut, heure_de_fin), en heures entières sur vingt-quatre heures. Attention au créneau de Baghera Jones : il commence à minuit, donc son heure de début est plus petite que celle de la personne qui la précède.

Question 5.1 : Écrivez a_l_antenne(planning, heure) qui renvoie le nom du streamer en direct à cette heure, ou None si personne ne l'est.

L'heure de début est incluse, l'heure de fin est exclue.

def a_l_antenne(planning, heure):
    # À compléter
    pass

# Test
print(a_l_antenne(planning, 19))    # Antoine Daniel
print(a_l_antenne(planning, 22))    # Joueur du Grenier
print(a_l_antenne(planning, 1))     # Baghera Jones
print(a_l_antenne(planning, 10))    # None

5.2. Suivre la chaîne des raids

Le dictionnaire raids associe à chaque streamer la liste de ceux vers qui il envoie son public. Certains n'envoient personne : leur liste est vide.

Question 5.2 : Écrivez suivre_raids(raids, depart) qui renvoie la liste des chaînes traversées en partant de depart et en suivant à chaque fois le premier raid, jusqu'à tomber sur un streamer qui n'en fait aucun.

Le point de départ fait partie du résultat.

def suivre_raids(raids, depart):
    # À compléter
    pass

# Test
print(suivre_raids(raids, "Antoine Daniel"))
# ['Antoine Daniel', 'MisterMV', 'Joueur du Grenier', 'Baghera Jones', 'Ultia']
print(suivre_raids(raids, "Horty"))
# ['Horty', 'Sylvain Levy']
print(suivre_raids(raids, "Ultia"))
# ['Ultia']

Questions de synthèse

Répondez en quelques lignes, sans machine.

  1. À la question 1.3, "25" + "40" donne "2540". Expliquez pourquoi Python ne signale aucune erreur alors que le résultat est faux du point de vue de la régie.

  2. À la question 3.2, pourquoi initialise-t-on record avec le premier don de la liste plutôt qu'avec un montant de 0 ?

  3. Aux questions 4.1 et 4.3, on parcourt la même liste de dons mais on ne construit pas la même structure. Dans quel cas le dictionnaire de listes est-il préférable au dictionnaire de totaux ?

  4. La question 5.2 s'arrête lorsqu'un streamer ne fait aucun raid. Que se passerait-il si deux streamers se renvoyaient mutuellement leur public ? Comment modifieriez-vous la fonction pour l'éviter ?


Ce que vous venez de réviser

Partie Notion Chapitre de Terminale concerné
1 Variables, types, conversions POO, structures linéaires
2 Fonctions, return, paramètre par défaut Récursivité, Modularité
3 Boucles, accumulateur, listes, tranches Parcours d'arbres et de graphes, tri fusion
4 Dictionnaires, dictionnaires de listes Graphes, programmation dynamique
5 Synthèse, parcours d'un enchaînement Graphes, Pile et File

La question 5.2 est un parcours de graphe : raids est une liste d'adjacence, exactement la structure que vous utiliserez au chapitre Graphes.


Barème indicatif

Partie Points
Partie 1 : Le compteur 3
Partie 2 : L'annonce des dons 5
Partie 3 : L'historique des dons 5
Partie 4 : Le classement des chaînes 4
Partie 5 : Le planning et les raids 2
Questions de synthèse 1
Total 20

Auteur : Florian Mathieu

Licence CC BY-SA

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International.