498 lines
16 KiB
Markdown
498 lines
16 KiB
Markdown
# 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.
|
|
|
|
```python
|
|
# 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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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`.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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`.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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**.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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.
|
|
|
|
```python
|
|
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
|
|
|
|
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Licence Creative Commons" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a> <br />Ce cours est mis à disposition selon les termes de la <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International</a>.
|