From 05f67de5beb517e0b520101ebcab6d143bbfd85c Mon Sep 17 00:00:00 2001 From: Florian Mathieu Date: Mon, 7 Sep 2026 01:03:32 +0200 Subject: [PATCH] =?UTF-8?q?ajout=20du=20TP=20de=20rentr=C3=A9e=20Z-Event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Revisions_Rentree/Corrige_TP_ZEvent.md | 568 ++++++++++++++++ Revisions_Rentree/NOTES_PROF.md | 61 +- Revisions_Rentree/README.md | 45 +- Revisions_Rentree/TP_ZEvent.ipynb | 857 +++++++++++++++++++++++ Revisions_Rentree/TP_ZEvent.md | 497 ++++++++++++++ Revisions_Rentree/_generateur_zevent.py | 862 ++++++++++++++++++++++++ 6 files changed, 2864 insertions(+), 26 deletions(-) create mode 100644 Revisions_Rentree/Corrige_TP_ZEvent.md create mode 100644 Revisions_Rentree/TP_ZEvent.ipynb create mode 100644 Revisions_Rentree/TP_ZEvent.md create mode 100644 Revisions_Rentree/_generateur_zevent.py diff --git a/Revisions_Rentree/Corrige_TP_ZEvent.md b/Revisions_Rentree/Corrige_TP_ZEvent.md new file mode 100644 index 0000000..68646e5 --- /dev/null +++ b/Revisions_Rentree/Corrige_TP_ZEvent.md @@ -0,0 +1,568 @@ +# Corrigé du TP Z-Event + +--- + +## Partie 1 : Le compteur (variables et types) + +### 1.1. État de la cagnotte + +**Question 1.1** : + +```python +pourcentage = round(collecte / objectif * 100, 2) + +print(f"Objectif atteint à {pourcentage} %") # Objectif atteint à 73.5 % +``` + +**Question 1.2** : + +```python +reste = objectif - collecte + +print(f"Il manque {reste} euros") # Il manque 662549.25 euros +``` + +### 1.2. Le piège du compteur + +**Question 1.3** : + +```python +don_1 = "25" +don_2 = "40" + +total = don_1 + don_2 +print(total) # affiche 2540 : sur deux chaînes, + est une concaténation + +# Correction : convertir les chaînes en nombres avant d'additionner +total = int(don_1) + int(don_2) +print(total) # 65 +``` + +## Partie 2 : L'annonce des dons (fonctions) + +### 2.1. Formater un don + +**Question 2.1** : + +```python +def formater_don(pseudo, montant): + return f"{pseudo} vient de donner {montant} euros" + +# 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 + +**Question 2.2** : + +```python +def est_gros_don(montant, seuil=50): + return montant >= seuil + +# 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 + +**Question 2.3** : + +```python +def palier(total): + if total < 500000: + return "Départ" + elif total < 1000000: + return "Palier 1" + elif total < 2000000: + return "Palier 2" + else: + return "Objectif" + +# 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** : + +```python +def annonce_regie(pseudo, montant): + message = formater_don(pseudo, montant) + if est_gros_don(montant): + message = message + " [GROS DON]" + return message + +# 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) + +### 3.1. Le total collecté + +**Question 3.1** : + +```python +def total_dons(liste_dons): + total = 0 + for don in liste_dons: + total = total + don["montant"] + return total + +# Test +print(total_dons(dons)) # 1449.5 +print(total_dons([])) # 0 +``` + +### 3.2. Le don le plus élevé + +**Question 3.2** : + +```python +def plus_gros_don(liste_dons): + record = liste_dons[0] # on part du premier don, surtout pas de 0 + for don in liste_dons: + if don["montant"] > record["montant"]: + record = don + return record + +# Test +record = plus_gros_don(dons) +print(record["pseudo"], record["montant"]) # Anonyme 500.0 +``` + +### 3.3. Compter et filtrer + +**Question 3.3** : + +```python +def compter_gros_dons(liste_dons, seuil=50): + compteur = 0 + for don in liste_dons: + if est_gros_don(don["montant"], seuil): + compteur = compteur + 1 + return compteur + +# Test +print(compter_gros_dons(dons)) # 7 +print(compter_gros_dons(dons, 100)) # 4 +``` + +**Question 3.4** : + +```python +def donateurs(liste_dons): + pseudos = [] + for don in liste_dons: + if don["pseudo"] not in pseudos: + pseudos.append(don["pseudo"]) + return pseudos + +# 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 + +**Question 3.5** : + +```python +def derniers_dons(liste_dons, n): + return liste_dons[-n:] + +# 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) + +### 4.1. La cagnotte de chaque chaîne + +**Question 4.1** : + +```python +def cagnottes(liste_dons): + totaux = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer in totaux: + totaux[streamer] = totaux[streamer] + don["montant"] + else: + totaux[streamer] = don["montant"] + return totaux + +# 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** : + +```python +def meilleure_chaine(totaux): + meilleur = None + for streamer in totaux: + if meilleur is None or totaux[streamer] > totaux[meilleur]: + meilleur = streamer + return meilleur + +# Test +print(meilleure_chaine(cagnottes(dons))) # MisterMV +``` + +### 4.3. Le détail des dons + +**Question 4.3** : + +```python +def dons_par_chaine(liste_dons): + detail = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer not in detail: + detail[streamer] = [] + detail[streamer].append(don["montant"]) + return detail + +# 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** : + +```python +def podium(totaux): + classement = sorted(totaux, key=lambda streamer: totaux[streamer], reverse=True) + return classement[:3] + +# Test +print(podium(cagnottes(dons))) +# ['MisterMV', 'Domingo', 'Ultia'] +``` + +## Partie 5 : Le planning et les raids (synthèse) + +### 5.1. Qui est à l'antenne + +**Question 5.1** : + +```python +def a_l_antenne(planning, heure): + for streamer, debut, fin in planning: + if debut < fin: + if debut <= heure < fin: + return streamer + else: + # créneau à cheval sur minuit + if heure >= debut or heure < fin: + return streamer + return None + +# 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 + +**Question 5.2** : + +```python +def suivre_raids(raids, depart): + parcours = [depart] + courant = depart + while raids.get(courant): # .get évite l'erreur si la clé est absente + courant = raids[courant][0] + parcours.append(courant) + return parcours + +# 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'] +``` + +--- + +## Code complet fonctionnel + +```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"}, +] + +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": [], +} + +pourcentage = round(collecte / objectif * 100, 2) + +print(f"Objectif atteint à {pourcentage} %") # Objectif atteint à 73.5 % + +reste = objectif - collecte + +print(f"Il manque {reste} euros") # Il manque 662549.25 euros + +don_1 = "25" +don_2 = "40" + +total = don_1 + don_2 +print(total) # affiche 2540 : sur deux chaînes, + est une concaténation + +# Correction : convertir les chaînes en nombres avant d'additionner +total = int(don_1) + int(don_2) +print(total) # 65 + +def formater_don(pseudo, montant): + return f"{pseudo} vient de donner {montant} euros" + +# 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 + +def est_gros_don(montant, seuil=50): + return montant >= seuil + +# 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 + +def palier(total): + if total < 500000: + return "Départ" + elif total < 1000000: + return "Palier 1" + elif total < 2000000: + return "Palier 2" + else: + return "Objectif" + +# Test +print(palier(120000)) # Départ +print(palier(750000)) # Palier 1 +print(palier(1837450.75)) # Palier 2 +print(palier(2500000)) # Objectif + +def annonce_regie(pseudo, montant): + message = formater_don(pseudo, montant) + if est_gros_don(montant): + message = message + " [GROS DON]" + return message + +# 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] + +def total_dons(liste_dons): + total = 0 + for don in liste_dons: + total = total + don["montant"] + return total + +# Test +print(total_dons(dons)) # 1449.5 +print(total_dons([])) # 0 + +def plus_gros_don(liste_dons): + record = liste_dons[0] # on part du premier don, surtout pas de 0 + for don in liste_dons: + if don["montant"] > record["montant"]: + record = don + return record + +# Test +record = plus_gros_don(dons) +print(record["pseudo"], record["montant"]) # Anonyme 500.0 + +def compter_gros_dons(liste_dons, seuil=50): + compteur = 0 + for don in liste_dons: + if est_gros_don(don["montant"], seuil): + compteur = compteur + 1 + return compteur + +# Test +print(compter_gros_dons(dons)) # 7 +print(compter_gros_dons(dons, 100)) # 4 + +def donateurs(liste_dons): + pseudos = [] + for don in liste_dons: + if don["pseudo"] not in pseudos: + pseudos.append(don["pseudo"]) + return pseudos + +# Test +print(donateurs(dons)) +# ['Nyx_42', 'TitiLeBg', 'Camille_R', 'Poulpe3000', 'Anonyme', 'Sam_du_59', 'Lulu_2007', 'Marion_NSI', 'Kevin_B'] + +def derniers_dons(liste_dons, n): + return liste_dons[-n:] + +# Test +for don in derniers_dons(dons, 3): + print(don["heure"], don["pseudo"]) +# 21:39 Anonyme +# 21:48 Poulpe3000 +# 21:59 Marion_NSI + +def cagnottes(liste_dons): + totaux = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer in totaux: + totaux[streamer] = totaux[streamer] + don["montant"] + else: + totaux[streamer] = don["montant"] + return totaux + +# Test +totaux = cagnottes(dons) +print(totaux["Antoine Daniel"]) # 115.0 +print(totaux["Joueur du Grenier"]) # 140.0 +print(len(totaux)) # 13 + +def meilleure_chaine(totaux): + meilleur = None + for streamer in totaux: + if meilleur is None or totaux[streamer] > totaux[meilleur]: + meilleur = streamer + return meilleur + +# Test +print(meilleure_chaine(cagnottes(dons))) # MisterMV + +def dons_par_chaine(liste_dons): + detail = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer not in detail: + detail[streamer] = [] + detail[streamer].append(don["montant"]) + return detail + +# 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] + +def podium(totaux): + classement = sorted(totaux, key=lambda streamer: totaux[streamer], reverse=True) + return classement[:3] + +# Test +print(podium(cagnottes(dons))) +# ['MisterMV', 'Domingo', 'Ultia'] + +def a_l_antenne(planning, heure): + for streamer, debut, fin in planning: + if debut < fin: + if debut <= heure < fin: + return streamer + else: + # créneau à cheval sur minuit + if heure >= debut or heure < fin: + return streamer + return None + +# 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 + +def suivre_raids(raids, depart): + parcours = [depart] + courant = depart + while raids.get(courant): # .get évite l'erreur si la clé est absente + courant = raids[courant][0] + parcours.append(courant) + return parcours + +# 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'] +``` + +--- + +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. diff --git a/Revisions_Rentree/NOTES_PROF.md b/Revisions_Rentree/NOTES_PROF.md index 0d4ecfa..3101157 100644 --- a/Revisions_Rentree/NOTES_PROF.md +++ b/Revisions_Rentree/NOTES_PROF.md @@ -1,7 +1,55 @@ -# Révisions de rentrée : PERRIAND-7 +# Révisions de rentrée : notes de séance -Activités de reprise pour la rentrée de Terminale, après la coupure estivale. -Fil rouge unique : le réveil d'une station orbitale après deux mois d'hibernation. +Deux activités de reprise pour la rentrée de Terminale, après la coupure estivale. +Elles couvrent les mêmes notions et sont interchangeables : Z-Event (marathon caritatif de +streaming) et PERRIAND-7 (station orbitale en avarie). + +## Z-Event + +| Fichier | Pour qui | Publié sur hub-docs | +|---|---|---| +| `TP_ZEvent.md` | Élèves | oui | +| `TP_ZEvent.ipynb` | Élèves | seulement si ajouté à `NOTEBOOK_ALLOWLIST` | +| `Corrige_TP_ZEvent.md` | Professeur | **oui par défaut** (c'est un `.md`), voir ci-dessous | +| `_generateur_zevent.py` | Professeur | non (`EXCLUDE_FILES`) | + +Écrit au gabarit de tes TP : `## Contexte`, `## Objectifs pédagogiques`, `## Partie N`, +`**Question N.M**`, sorties attendues en commentaire à droite des `print`, `## Barème +indicatif` sur 20, auteur et licence. Cinq parties, dix-huit questions, environ deux heures. + +**Le contexte factuel est volontairement prudent.** Le TP ne dit rien de l'édition en cours : +ni montant, ni association, ni participants réels. Seuls figurent des éléments stables +(l'événement existe depuis 2016, créé par ZeratoR et Dach). Toutes les données manipulées +sont inventées, et un encadré le dit explicitement aux élèves dès le contexte. Les streamers +cités ne servent que de noms dans un jeu de données fictif. + +**Deux décisions de publication à prendre** avant de pousser : +- `Corrige_TP_ZEvent.md` sera publié automatiquement sur hub-docs, comme tous tes autres + `Corrige_TP_*.md`. Le `robots.txt` le désindexe des moteurs de recherche, mais il reste + accessible depuis la navigation du site. Si tu ne veux pas ça pour demain, il faut soit + l'ajouter temporairement à `EXCLUDE_FILES`, soit ne pas le pousser tout de suite. +- `TP_ZEvent.ipynb` ne sera **pas** publié tant qu'il n'est pas dans + `NOTEBOOK_ALLOWLIST["terminale"]`. Sans ça, les élèves n'auront que la version Markdown + en ligne, et le choix de format annoncé dans le README n'existera pas. + +### Points de vigilance + +- **Question 1.3** : `"25" + "40"` donne `"2540"`. Leur faire exécuter avant d'expliquer. +- **Question 3.2** : l'initialisation de l'accumulateur avec `liste[0]` et non `0` est le + point qui coince le plus. C'est la question 2 de la synthèse. +- **Question 5.1** : le créneau de Baghera Jones est à cheval sur minuit, donc `debut > fin`. + C'est volontaire, et c'est le seul endroit du TP qui demande de distinguer deux cas. +- **Question 5.2** : parcours d'une liste d'adjacence. La question 4 de la synthèse pose le + problème du cycle sans demander de le résoudre, c'est le pont vers le chapitre Graphes. + +### Barème + +Réparti sur 20 dans l'énoncé. Les parties 1 à 3 pèsent 13 points : un élève qui a décroché +pendant l'été peut valider la moitié du TP sans toucher aux dictionnaires. + +--- + +## PERRIAND-7 | Fichier | Pour qui | Publié sur hub-docs | |---|---|---| @@ -93,9 +141,14 @@ Les deux notebooks sont produits par un script unique (énoncé et corrigé part même source, ce qui évite qu'ils divergent) : ```bash -python3 _generateur.py +python3 _generateur.py # PERRIAND-7 +python3 _generateur_zevent.py # Z-Event ``` +Pour Z-Event, les sorties attendues affichées en commentaire ne sont pas écrites à la main : +le générateur exécute les solutions de référence et injecte les valeurs obtenues. Modifier +un montant dans le jeu de données met donc à jour tous les résultats de l'énoncé. + Modifier un exercice dans `_generateur.py` puis relancer met à jour les deux notebooks. Attention : `_generateur.py` contient le corrigé en clair, et `build_docs.py` publie les diff --git a/Revisions_Rentree/README.md b/Revisions_Rentree/README.md index cb2a6e5..fc8cd43 100644 --- a/Revisions_Rentree/README.md +++ b/Revisions_Rentree/README.md @@ -1,34 +1,35 @@ -# Révisions de rentrée : PERRIAND-7 +# Révisions de rentrée -Activité de reprise, à faire en début d'année de Terminale. +Deux activités de reprise, à faire en début d'année de Terminale. Elles couvrent les mêmes +notions de Première : variables, fonctions, boucles, listes, dictionnaires. Prenez celle +que vous voulez, elles sont indépendantes. -La station orbitale PERRIAND-7 sort de deux mois d'hibernation et ses programmes de bord -ne répondent plus correctement. À vous de les réveiller, un module à la fois. +Rien de nouveau là-dedans : tout a été vu l'an dernier. L'objectif est de remettre le pied +à l'étrier avant d'attaquer les chapitres de Terminale. -## Ce qu'on révise +## Z-Event : la régie de données d'un marathon caritatif -| Module | Notion | -|---|---| -| 1. Le réacteur | variables, affectation, mutabilité | -| 2. Les protocoles | fonctions, paramètres, `return` | -| 3. Le balayage | boucles `for` et `while` | -| 4. La soute | listes, listes de listes | -| 5. L'équipage | dictionnaires | -| 6. Sortie d'orbite | synthèse | +Vous êtes bénévole à la régie d'un marathon caritatif de streaming. Vous écrivez les outils +Python qui alimentent le compteur de dons, les classements par chaîne et le planning. -Rien de nouveau : tout a été vu en Première. Mais chaque notion révisée ici resservira -directement cette année, en récursivité, en POO, sur les arbres et sur les graphes. +- [Énoncé au format Markdown](TP_ZEvent.md) +- [Énoncé au format notebook](TP_ZEvent.ipynb) -## Le notebook +Le TP existe dans les deux formats, au choix. Le contenu est identique. -[PERRIAND-7_Reveil_des_systemes.ipynb](PERRIAND-7_Reveil_des_systemes.ipynb) +## PERRIAND-7 : le réveil d'une station orbitale -Ouvrable sur [Basthon](https://basthon.fr), Jupyter ou VS Code. Aucune installation, -aucun fichier annexe. Chaque exercice est accompagné de ses tests : si `OK` s'affiche, -c'est gagné. +Une station spatiale sort de deux mois d'hibernation et ses programmes de bord ne répondent +plus. Vous les réparez, module par module. -Les modules sont indépendants. Chaque module commence par une cellule SECOURS qui remet -en place ses données : vous pouvez passer au suivant sans avoir fini le précédent. +- [Notebook](PERRIAND-7_Reveil_des_systemes.ipynb) + +Chaque exercice est accompagné de ses tests : si `OK` s'affiche, c'est gagné. Les modules +sont indépendants, chacun commence par une cellule SECOURS qui remet en place ses données. + +## Comment ouvrir un notebook + +[Basthon](https://basthon.fr), Jupyter ou VS Code. Aucune installation, aucun fichier annexe. --- *Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0* diff --git a/Revisions_Rentree/TP_ZEvent.ipynb b/Revisions_Rentree/TP_ZEvent.ipynb new file mode 100644 index 0000000..d3b70ca --- /dev/null +++ b/Revisions_Rentree/TP_ZEvent.ipynb @@ -0,0 +1,857 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "bf05817a", + "metadata": {}, + "source": [ + "# TP : Z-Event - La régie de données d'un marathon caritatif\n", + "\n", + "> **Thème** : Révisions Python - variables, fonctions, boucles, listes, dictionnaires\n", + "\n", + "---\n", + "\n", + "## Contexte\n", + "\n", + "Depuis 2016, le **Z-Event** réunit chaque année des dizaines de streamers francophones\n", + "pendant plusieurs jours d'affilée, dans un même lieu, pour récolter des dons au profit\n", + "d'une association. L'événement a été créé par **ZeratoR** et **Dach**. Le principe est\n", + "simple : les spectateurs donnent, les streamers relèvent des défis, et un compteur géant\n", + "affiche la cagnotte en direct.\n", + "\n", + "Derrière ce compteur, il y a du code. Chaque don qui tombe doit être enregistré, additionné,\n", + "attribué à la chaîne sur laquelle il a été fait, comparé aux paliers annoncés, puis réaffiché\n", + "en moins d'une seconde sur tous les écrans. C'est ce travail que vous allez faire ici.\n", + "\n", + "Vous êtes bénévole à la **régie de données** de l'événement. Votre mission : écrire les\n", + "outils Python qui alimentent le compteur et les classements.\n", + "\n", + "> Les données de ce TP (montants, pseudos, horaires, cagnottes) sont **entièrement inventées**\n", + "> pour l'exercice. Elles ne correspondent à aucune édition réelle du Z-Event.\n", + "\n", + "---\n", + "\n", + "## Objectifs pédagogiques\n", + "\n", + "- Manipuler des variables et reconnaître une erreur de type\n", + "- Écrire des fonctions qui **renvoient** une valeur au lieu de l'afficher\n", + "- Parcourir une liste avec une boucle et accumuler un résultat\n", + "- Construire et exploiter des dictionnaires, y compris des dictionnaires de listes\n", + "- Réinvestir ces notions dans les chapitres de Terminale : POO, structures linéaires, graphes\n", + "\n", + "Aucune de ces notions n'est nouvelle : tout a été vu en Première. L'objectif est de les\n", + "remettre en service après la coupure estivale.\n", + "\n", + "---\n", + "\n", + "## Les données\n", + "\n", + "Copiez ce bloc en tête de votre fichier. Toutes les parties du TP s'appuient dessus." + ] + }, + { + "cell_type": "code", + "id": "62c3fbf5", + "metadata": {}, + "source": [ + "# Toutes ces données sont inventées pour l'exercice.\n", + "objectif = 2500000\n", + "collecte = 1837450.75\n", + "\n", + "dons = [\n", + " {\"pseudo\": \"Nyx_42\", \"streamer\": \"Antoine Daniel\", \"montant\": 5.0, \"heure\": \"18:04\"},\n", + " {\"pseudo\": \"TitiLeBg\", \"streamer\": \"MisterMV\", \"montant\": 20.0, \"heure\": \"18:11\"},\n", + " {\"pseudo\": \"Camille_R\", \"streamer\": \"Joueur du Grenier\", \"montant\": 50.0, \"heure\": \"18:23\"},\n", + " {\"pseudo\": \"Nyx_42\", \"streamer\": \"Baghera Jones\", \"montant\": 15.5, \"heure\": \"18:37\"},\n", + " {\"pseudo\": \"Poulpe3000\", \"streamer\": \"Horty\", \"montant\": 2.0, \"heure\": \"18:41\"},\n", + " {\"pseudo\": \"Anonyme\", \"streamer\": \"Ultia\", \"montant\": 120.0, \"heure\": \"18:52\"},\n", + " {\"pseudo\": \"Sam_du_59\", \"streamer\": \"Antoine Daniel\", \"montant\": 10.0, \"heure\": \"19:03\"},\n", + " {\"pseudo\": \"Camille_R\", \"streamer\": \"Sylvain Levy\", \"montant\": 30.0, \"heure\": \"19:14\"},\n", + " {\"pseudo\": \"Lulu_2007\", \"streamer\": \"ZeratoR\", \"montant\": 8.5, \"heure\": \"19:20\"},\n", + " {\"pseudo\": \"TitiLeBg\", \"streamer\": \"Etoiles\", \"montant\": 45.0, \"heure\": \"19:33\"},\n", + " {\"pseudo\": \"Marion_NSI\", \"streamer\": \"Joueur du Grenier\", \"montant\": 75.0, \"heure\": \"19:47\"},\n", + " {\"pseudo\": \"Poulpe3000\", \"streamer\": \"MisterMV\", \"montant\": 12.0, \"heure\": \"19:55\"},\n", + " {\"pseudo\": \"Anonyme\", \"streamer\": \"Domingo\", \"montant\": 250.0, \"heure\": \"20:08\"},\n", + " {\"pseudo\": \"Kevin_B\", \"streamer\": \"Baghera Jones\", \"montant\": 5.0, \"heure\": \"20:16\"},\n", + " {\"pseudo\": \"Nyx_42\", \"streamer\": \"Maghla\", \"montant\": 60.0, \"heure\": \"20:29\"},\n", + " {\"pseudo\": \"Sam_du_59\", \"streamer\": \"Horty\", \"montant\": 3.5, \"heure\": \"20:38\"},\n", + " {\"pseudo\": \"Marion_NSI\", \"streamer\": \"Antoine Daniel\", \"montant\": 100.0, \"heure\": \"20:44\"},\n", + " {\"pseudo\": \"Lulu_2007\", \"streamer\": \"Alphacast\", \"montant\": 18.0, \"heure\": \"20:57\"},\n", + " {\"pseudo\": \"Camille_R\", \"streamer\": \"Ultia\", \"montant\": 40.0, \"heure\": \"21:05\"},\n", + " {\"pseudo\": \"Kevin_B\", \"streamer\": \"Ponce\", \"montant\": 22.5, \"heure\": \"21:12\"},\n", + " {\"pseudo\": \"TitiLeBg\", \"streamer\": \"Joueur du Grenier\", \"montant\": 15.0, \"heure\": \"21:26\"},\n", + " {\"pseudo\": \"Anonyme\", \"streamer\": \"MisterMV\", \"montant\": 500.0, \"heure\": \"21:39\"},\n", + " {\"pseudo\": \"Poulpe3000\", \"streamer\": \"Sylvain Levy\", \"montant\": 7.5, \"heure\": \"21:48\"},\n", + " {\"pseudo\": \"Marion_NSI\", \"streamer\": \"Baghera Jones\", \"montant\": 35.0, \"heure\": \"21:59\"},\n", + "]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "bc90ce2a", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Partie 1 : Le compteur (variables et types)\n", + "\n", + "### 1.1. État de la cagnotte" + ] + }, + { + "cell_type": "markdown", + "id": "245368be", + "metadata": {}, + "source": [ + "**Question 1.1** : Calculez dans une variable `pourcentage` la part de l'objectif déjà\n", + "atteinte, arrondie à deux décimales." + ] + }, + { + "cell_type": "code", + "id": "2052888a", + "metadata": {}, + "source": [ + "pourcentage = # À compléter\n", + "\n", + "print(f\"Objectif atteint à {pourcentage} %\") # Objectif atteint à 73.5 %" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b97c5eaf", + "metadata": {}, + "source": [ + "**Question 1.2** : Calculez dans une variable `reste` la somme qu'il manque encore pour\n", + "atteindre l'objectif." + ] + }, + { + "cell_type": "code", + "id": "00b6e536", + "metadata": {}, + "source": [ + "reste = # À compléter\n", + "\n", + "print(f\"Il manque {reste} euros\") # Il manque 662549.25 euros" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "35235678", + "metadata": {}, + "source": [ + "### 1.2. Le piège du compteur\n", + "\n", + "Le compteur du site reçoit les montants sous forme de **chaînes de caractères**, parce qu'ils\n", + "arrivent d'un formulaire web. Le code ci-dessous ne fait donc pas ce qu'on croit." + ] + }, + { + "cell_type": "markdown", + "id": "729d870a", + "metadata": {}, + "source": [ + "**Question 1.3** : Exécutez ce code, observez le résultat, puis corrigez-le pour que\n", + "`total` contienne bien la somme des deux dons." + ] + }, + { + "cell_type": "code", + "id": "0ba70c50", + "metadata": {}, + "source": [ + "don_1 = \"25\"\n", + "don_2 = \"40\"\n", + "\n", + "total = don_1 + don_2\n", + "print(total) # affiche 2540 au lieu de 65\n", + "\n", + "# Correction :\n", + "total = # À compléter\n", + "print(total) # 65" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "ca07a1b6", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Partie 2 : L'annonce des dons (fonctions)\n", + "\n", + "Un don qui tombe doit être annoncé à l'écran. Ces fonctions produisent les messages.\n", + "\n", + "### 2.1. Formater un don" + ] + }, + { + "cell_type": "markdown", + "id": "2d6d0204", + "metadata": {}, + "source": [ + "**Question 2.1** : Écrivez la fonction `formater_don(pseudo, montant)` qui **renvoie** la\n", + "chaîne d'annonce. Attention : elle doit la renvoyer, pas l'afficher." + ] + }, + { + "cell_type": "code", + "id": "91af8ea0", + "metadata": {}, + "source": [ + "def formater_don(pseudo, montant):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(formater_don(\"Nyx_42\", 5.0)) # Nyx_42 vient de donner 5.0 euros\n", + "print(formater_don(\"Marion_NSI\", 100.0)) # Marion_NSI vient de donner 100.0 euros" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "dbccc201", + "metadata": {}, + "source": [ + "### 2.2. Repérer les gros dons\n", + "\n", + "La régie déclenche une animation spéciale au-delà d'un certain montant." + ] + }, + { + "cell_type": "markdown", + "id": "e5808117", + "metadata": {}, + "source": [ + "**Question 2.2** : Écrivez `est_gros_don(montant, seuil)` qui renvoie `True` si le montant\n", + "est **supérieur ou égal** au seuil, `False` sinon. Le seuil vaut 50 par défaut." + ] + }, + { + "cell_type": "code", + "id": "927dd827", + "metadata": {}, + "source": [ + "def est_gros_don(montant, seuil=50):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(est_gros_don(120.0)) # True\n", + "print(est_gros_don(50.0)) # True\n", + "print(est_gros_don(20.0)) # False\n", + "print(est_gros_don(20.0, 10)) # True" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "88a5c245", + "metadata": {}, + "source": [ + "### 2.3. Les paliers\n", + "\n", + "Les organisateurs annoncent des paliers au fil de la collecte.\n", + "\n", + "| Cagnotte | Palier |\n", + "|----------|--------|\n", + "| moins de 500 000 | `\"Départ\"` |\n", + "| de 500 000 à 999 999 | `\"Palier 1\"` |\n", + "| de 1 000 000 à 1 999 999 | `\"Palier 2\"` |\n", + "| 2 000 000 et plus | `\"Objectif\"` |" + ] + }, + { + "cell_type": "markdown", + "id": "c0f285d0", + "metadata": {}, + "source": [ + "**Question 2.3** : Écrivez `palier(total)` qui renvoie le nom du palier atteint." + ] + }, + { + "cell_type": "code", + "id": "5a353ce4", + "metadata": {}, + "source": [ + "def palier(total):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(palier(120000)) # Départ\n", + "print(palier(750000)) # Palier 1\n", + "print(palier(1837450.75)) # Palier 2\n", + "print(palier(2500000)) # Objectif" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "fd1db284", + "metadata": {}, + "source": [ + "### 2.4. Le message complet" + ] + }, + { + "cell_type": "markdown", + "id": "0eda16ce", + "metadata": {}, + "source": [ + "**Question 2.4** : Écrivez `annonce_regie(pseudo, montant)` qui renvoie le message affiché\n", + "en régie. Votre fonction **doit appeler** `formater_don` et `est_gros_don` : ne recopiez pas\n", + "leur contenu.\n", + "\n", + "Le message se termine par `\" [GROS DON]\"` lorsque le montant déclenche l'animation." + ] + }, + { + "cell_type": "code", + "id": "63490a01", + "metadata": {}, + "source": [ + "def annonce_regie(pseudo, montant):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(annonce_regie(\"Poulpe3000\", 2.0)) # Poulpe3000 vient de donner 2.0 euros\n", + "print(annonce_regie(\"Anonyme\", 250.0)) # Anonyme vient de donner 250.0 euros [GROS DON]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2e4c7e7f", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Partie 3 : L'historique des dons (boucles et listes)\n", + "\n", + "La liste `dons` contient les vingt-quatre derniers dons enregistrés. Chaque don est un\n", + "dictionnaire à quatre clés : `pseudo`, `streamer`, `montant`, `heure`.\n", + "\n", + "### 3.1. Le total collecté" + ] + }, + { + "cell_type": "markdown", + "id": "3c9df8f3", + "metadata": {}, + "source": [ + "**Question 3.1** : Écrivez `total_dons(liste_dons)` qui renvoie la somme de tous les\n", + "montants. Utilisez une boucle et un accumulateur, pas la fonction `sum`." + ] + }, + { + "cell_type": "code", + "id": "b2e587a4", + "metadata": {}, + "source": [ + "def total_dons(liste_dons):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(total_dons(dons)) # 1449.5\n", + "print(total_dons([])) # 0" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c65fb763", + "metadata": {}, + "source": [ + "### 3.2. Le don le plus élevé" + ] + }, + { + "cell_type": "markdown", + "id": "3d3882b8", + "metadata": {}, + "source": [ + "**Question 3.2** : Écrivez `plus_gros_don(liste_dons)` qui renvoie le **dictionnaire** du\n", + "don le plus élevé. L'usage de `max` est interdit. On suppose la liste non vide." + ] + }, + { + "cell_type": "code", + "id": "9eaac029", + "metadata": {}, + "source": [ + "def plus_gros_don(liste_dons):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "record = plus_gros_don(dons)\n", + "print(record[\"pseudo\"], record[\"montant\"]) # Anonyme 500.0" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "aa89e941", + "metadata": {}, + "source": [ + "### 3.3. Compter et filtrer" + ] + }, + { + "cell_type": "markdown", + "id": "d357b858", + "metadata": {}, + "source": [ + "**Question 3.3** : Écrivez `compter_gros_dons(liste_dons, seuil)` qui renvoie le nombre de\n", + "dons supérieurs ou égaux au seuil. Réutilisez `est_gros_don`." + ] + }, + { + "cell_type": "code", + "id": "4c02a2b9", + "metadata": {}, + "source": [ + "def compter_gros_dons(liste_dons, seuil=50):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(compter_gros_dons(dons)) # 7\n", + "print(compter_gros_dons(dons, 100)) # 4" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "fae9409f", + "metadata": {}, + "source": [ + "**Question 3.4** : Écrivez `donateurs(liste_dons)` qui renvoie la liste des pseudos **sans\n", + "doublon**, dans l'ordre de leur premier don." + ] + }, + { + "cell_type": "code", + "id": "b98464c0", + "metadata": {}, + "source": [ + "def donateurs(liste_dons):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(donateurs(dons))\n", + "# ['Nyx_42', 'TitiLeBg', 'Camille_R', 'Poulpe3000', 'Anonyme', 'Sam_du_59', 'Lulu_2007', 'Marion_NSI', 'Kevin_B']" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "87fd84e5", + "metadata": {}, + "source": [ + "### 3.4. Le bandeau des derniers dons\n", + "\n", + "Le site n'affiche que les tout derniers dons reçus." + ] + }, + { + "cell_type": "markdown", + "id": "a57d2849", + "metadata": {}, + "source": [ + "**Question 3.5** : Écrivez `derniers_dons(liste_dons, n)` qui renvoie les `n` derniers dons\n", + "de la liste, en utilisant une **tranche**." + ] + }, + { + "cell_type": "code", + "id": "769c9428", + "metadata": {}, + "source": [ + "def derniers_dons(liste_dons, n):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "for don in derniers_dons(dons, 3):\n", + " print(don[\"heure\"], don[\"pseudo\"])\n", + "# 21:39 Anonyme\n", + "# 21:48 Poulpe3000\n", + "# 21:59 Marion_NSI" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "9e3fe8d8", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Partie 4 : Le classement des chaînes (dictionnaires)\n", + "\n", + "Chaque don est attribué à la chaîne sur laquelle il a été fait. La régie tient à jour la\n", + "cagnotte de chaque streamer.\n", + "\n", + "### 4.1. La cagnotte de chaque chaîne" + ] + }, + { + "cell_type": "markdown", + "id": "166e98fd", + "metadata": {}, + "source": [ + "**Question 4.1** : Écrivez `cagnottes(liste_dons)` qui renvoie un dictionnaire associant à\n", + "chaque streamer le total des dons reçus sur sa chaîne." + ] + }, + { + "cell_type": "code", + "id": "1c270111", + "metadata": {}, + "source": [ + "def cagnottes(liste_dons):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "totaux = cagnottes(dons)\n", + "print(totaux[\"Antoine Daniel\"]) # 115.0\n", + "print(totaux[\"Joueur du Grenier\"]) # 140.0\n", + "print(len(totaux)) # 13" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "9f6b6bf5", + "metadata": {}, + "source": [ + "### 4.2. La chaîne en tête" + ] + }, + { + "cell_type": "markdown", + "id": "a759ae20", + "metadata": {}, + "source": [ + "**Question 4.2** : Écrivez `meilleure_chaine(totaux)` qui renvoie le nom du streamer dont\n", + "la cagnotte est la plus élevée.\n", + "\n", + "Rappel : `for cle in dictionnaire` parcourt les clés." + ] + }, + { + "cell_type": "code", + "id": "0a6ce0d9", + "metadata": {}, + "source": [ + "def meilleure_chaine(totaux):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(meilleure_chaine(cagnottes(dons))) # MisterMV" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "a2ee2282", + "metadata": {}, + "source": [ + "### 4.3. Le détail des dons\n", + "\n", + "On veut maintenant, pour chaque streamer, la **liste** des montants reçus et plus seulement\n", + "leur somme : un dictionnaire dont les valeurs sont des listes." + ] + }, + { + "cell_type": "markdown", + "id": "b9eac959", + "metadata": {}, + "source": [ + "**Question 4.3** : Écrivez `dons_par_chaine(liste_dons)` qui renvoie un dictionnaire\n", + "associant à chaque streamer la liste des montants reçus, dans l'ordre chronologique." + ] + }, + { + "cell_type": "code", + "id": "d47e5375", + "metadata": {}, + "source": [ + "def dons_par_chaine(liste_dons):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "detail = dons_par_chaine(dons)\n", + "print(detail[\"MisterMV\"]) # [20.0, 12.0, 500.0]\n", + "print(detail[\"Baghera Jones\"]) # [15.5, 5.0, 35.0]" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "f5a46137", + "metadata": {}, + "source": [ + "### 4.4. Le podium" + ] + }, + { + "cell_type": "markdown", + "id": "ea80de46", + "metadata": {}, + "source": [ + "**Question 4.4** : Écrivez `podium(totaux)` qui renvoie la liste des trois premiers\n", + "streamers, du plus gros total au plus petit.\n", + "\n", + "Indication : `sorted(totaux, key=..., reverse=True)` trie les clés selon un critère." + ] + }, + { + "cell_type": "code", + "id": "262c0672", + "metadata": {}, + "source": [ + "def podium(totaux):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(podium(cagnottes(dons)))\n", + "# ['MisterMV', 'Domingo', 'Ultia']" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "4ebedc4c", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Partie 5 : Le planning et les raids (synthèse)\n", + "\n", + "Un **raid**, c'est lorsqu'un streamer envoie son public sur une autre chaîne à la fin de son\n", + "passage. Cette partie combine tout ce qui précède.\n", + "\n", + "Copiez ce second bloc de données à la suite du premier." + ] + }, + { + "cell_type": "code", + "id": "2ed28b19", + "metadata": {}, + "source": [ + "planning = [\n", + " (\"Antoine Daniel\", 18, 20),\n", + " (\"MisterMV\", 20, 22),\n", + " (\"Joueur du Grenier\", 22, 24),\n", + " (\"Baghera Jones\", 0, 3),\n", + " (\"Ultia\", 3, 6),\n", + "]\n", + "\n", + "raids = {\n", + " \"Antoine Daniel\": [\"MisterMV\"],\n", + " \"MisterMV\": [\"Joueur du Grenier\", \"Horty\"],\n", + " \"Joueur du Grenier\": [\"Baghera Jones\"],\n", + " \"Baghera Jones\": [\"Ultia\"],\n", + " \"Ultia\": [],\n", + " \"Horty\": [\"Sylvain Levy\"],\n", + " \"Sylvain Levy\": [],\n", + "}" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "d741bfa1", + "metadata": {}, + "source": [ + "### 5.1. Qui est à l'antenne\n", + "\n", + "Le planning est une liste de triplets `(streamer, heure_de_debut, heure_de_fin)`, en heures\n", + "entières sur vingt-quatre heures. Attention au créneau de Baghera Jones : il commence à\n", + "minuit, donc son heure de début est plus petite que celle de la personne qui la précède." + ] + }, + { + "cell_type": "markdown", + "id": "18718573", + "metadata": {}, + "source": [ + "**Question 5.1** : Écrivez `a_l_antenne(planning, heure)` qui renvoie le nom du streamer en\n", + "direct à cette heure, ou `None` si personne ne l'est.\n", + "\n", + "L'heure de début est incluse, l'heure de fin est exclue." + ] + }, + { + "cell_type": "code", + "id": "9f1639da", + "metadata": {}, + "source": [ + "def a_l_antenne(planning, heure):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(a_l_antenne(planning, 19)) # Antoine Daniel\n", + "print(a_l_antenne(planning, 22)) # Joueur du Grenier\n", + "print(a_l_antenne(planning, 1)) # Baghera Jones\n", + "print(a_l_antenne(planning, 10)) # None" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "0dbf8608", + "metadata": {}, + "source": [ + "### 5.2. Suivre la chaîne des raids\n", + "\n", + "Le dictionnaire `raids` associe à chaque streamer la liste de ceux vers qui il envoie son\n", + "public. Certains n'envoient personne : leur liste est vide." + ] + }, + { + "cell_type": "markdown", + "id": "342c85a1", + "metadata": {}, + "source": [ + "**Question 5.2** : Écrivez `suivre_raids(raids, depart)` qui renvoie la liste des chaînes\n", + "traversées en partant de `depart` et en suivant à chaque fois le **premier** raid, jusqu'à\n", + "tomber sur un streamer qui n'en fait aucun.\n", + "\n", + "Le point de départ fait partie du résultat." + ] + }, + { + "cell_type": "code", + "id": "67ef757e", + "metadata": {}, + "source": [ + "def suivre_raids(raids, depart):\n", + " # À compléter\n", + " pass\n", + "\n", + "# Test\n", + "print(suivre_raids(raids, \"Antoine Daniel\"))\n", + "# ['Antoine Daniel', 'MisterMV', 'Joueur du Grenier', 'Baghera Jones', 'Ultia']\n", + "print(suivre_raids(raids, \"Horty\"))\n", + "# ['Horty', 'Sylvain Levy']\n", + "print(suivre_raids(raids, \"Ultia\"))\n", + "# ['Ultia']" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "f66fcb63", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Questions de synthèse\n", + "\n", + "Répondez en quelques lignes, sans machine.\n", + "\n", + "1. À la question 1.3, `\"25\" + \"40\"` donne `\"2540\"`. Expliquez pourquoi Python ne signale\n", + " aucune erreur alors que le résultat est faux du point de vue de la régie.\n", + "\n", + "2. À la question 3.2, pourquoi initialise-t-on `record` avec le premier don de la liste\n", + " plutôt qu'avec un montant de 0 ?\n", + "\n", + "3. Aux questions 4.1 et 4.3, on parcourt la même liste de dons mais on ne construit pas la\n", + " même structure. Dans quel cas le dictionnaire de listes est-il préférable au dictionnaire\n", + " de totaux ?\n", + "\n", + "4. La question 5.2 s'arrête lorsqu'un streamer ne fait aucun raid. Que se passerait-il si\n", + " deux streamers se renvoyaient mutuellement leur public ? Comment modifieriez-vous la\n", + " fonction pour l'éviter ?\n", + "\n", + "---\n", + "\n", + "## Ce que vous venez de réviser\n", + "\n", + "| Partie | Notion | Chapitre de Terminale concerné |\n", + "|--------|--------|--------------------------------|\n", + "| 1 | Variables, types, conversions | POO, structures linéaires |\n", + "| 2 | Fonctions, `return`, paramètre par défaut | Récursivité, Modularité |\n", + "| 3 | Boucles, accumulateur, listes, tranches | Parcours d'arbres et de graphes, tri fusion |\n", + "| 4 | Dictionnaires, dictionnaires de listes | Graphes, programmation dynamique |\n", + "| 5 | Synthèse, parcours d'un enchaînement | Graphes, Pile et File |\n", + "\n", + "La question 5.2 est un parcours de graphe : `raids` est une **liste d'adjacence**, exactement\n", + "la structure que vous utiliserez au chapitre Graphes.\n", + "\n", + "---\n", + "\n", + "## Barème indicatif\n", + "\n", + "| Partie | Points |\n", + "|--------|--------|\n", + "| Partie 1 : Le compteur | 3 |\n", + "| Partie 2 : L'annonce des dons | 5 |\n", + "| Partie 3 : L'historique des dons | 5 |\n", + "| Partie 4 : Le classement des chaînes | 4 |\n", + "| Partie 5 : Le planning et les raids | 2 |\n", + "| Questions de synthèse | 1 |\n", + "| **Total** | **20** |\n", + "\n", + "---\n", + "\n", + "Auteur : Florian Mathieu\n", + "\n", + "Licence CC BY-SA\n", + "\n", + "\"Licence
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python", + "version": "3.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file diff --git a/Revisions_Rentree/TP_ZEvent.md b/Revisions_Rentree/TP_ZEvent.md new file mode 100644 index 0000000..e5efd5b --- /dev/null +++ b/Revisions_Rentree/TP_ZEvent.md @@ -0,0 +1,497 @@ +# 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 + +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. diff --git a/Revisions_Rentree/_generateur_zevent.py b/Revisions_Rentree/_generateur_zevent.py new file mode 100644 index 0000000..d1bafb0 --- /dev/null +++ b/Revisions_Rentree/_generateur_zevent.py @@ -0,0 +1,862 @@ +# -*- coding: utf-8 -*- +"""Génère le TP Z-Event : énoncé Markdown, corrigé Markdown et notebook élève. + +Source unique : les trois fichiers sortent d'ici, ils ne peuvent pas diverger. +Les sorties attendues affichées dans les commentaires (# Test) ne sont PAS écrites +à la main : elles sont calculées en exécutant les solutions de référence au moment +de la génération. +""" +import io, json, os, uuid + +DEST = os.path.dirname(os.path.abspath(__file__)) + +LICENCE = """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.""" + +# ---------------------------------------------------------------- LES DONNÉES + +DONNEES_SRC = '''# 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"}, +]''' + +PLANNING_SRC = '''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": [], +}''' + +# Exécution des données pour calculer les sorties attendues. +ENV = {} +exec(DONNEES_SRC, ENV) +exec(PLANNING_SRC, ENV) +dons, planning, raids = ENV["dons"], ENV["planning"], ENV["raids"] +objectif, collecte = ENV["objectif"], ENV["collecte"] + +pourcentage_att = round(collecte / objectif * 100, 2) +reste_att = objectif - collecte +total_att = 0 +for d in dons: + total_att += d["montant"] +record_att = dons[0] +for d in dons: + if d["montant"] > record_att["montant"]: + record_att = d +gros50_att = len([d for d in dons if d["montant"] >= 50]) +gros100_att = len([d for d in dons if d["montant"] >= 100]) +donateurs_att = [] +for d in dons: + if d["pseudo"] not in donateurs_att: + donateurs_att.append(d["pseudo"]) +trois_derniers = dons[-3:] + +cagnottes_att = {} +for d in dons: + cagnottes_att[d["streamer"]] = cagnottes_att.get(d["streamer"], 0) + d["montant"] +meilleure_att = max(cagnottes_att, key=lambda s: cagnottes_att[s]) +podium_att = sorted(cagnottes_att, key=lambda s: cagnottes_att[s], reverse=True)[:3] +detail_att = {} +for d in dons: + detail_att.setdefault(d["streamer"], []).append(d["montant"]) + + +def antenne_att(h): + for nom, debut, fin in planning: + if debut < fin: + if debut <= h < fin: + return nom + else: + if h >= debut or h < fin: + return nom + return None + + +def raids_att(depart): + parcours = [depart] + courant = depart + while raids.get(courant): + courant = raids[courant][0] + parcours.append(courant) + return parcours + + +# ---------------------------------------------------------------- LES BLOCS + +BLOCS = [] + + +def md(texte, dans_corrige=False): + BLOCS.append(("md", texte, dans_corrige)) + + +def code(source): + BLOCS.append(("code", source, source)) + + +def question(num, enonce, eleve, solution): + BLOCS.append(("question", num, enonce, eleve, solution)) + + +# ---------------------------------------------------------------- EN-TÊTE + +md("""# 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. +""") + +code(DONNEES_SRC) + +# ---------------------------------------------------------------- PARTIE 1 + +md("""--- + +## Partie 1 : Le compteur (variables et types) + +### 1.1. État de la cagnotte +""", dans_corrige=True) + +question( + "1.1", + """**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 à ''' + str(pourcentage_att) + " %", + '''pourcentage = round(collecte / objectif * 100, 2) + +print(f"Objectif atteint à {pourcentage} %") # Objectif atteint à ''' + str(pourcentage_att) + " %", +) + +question( + "1.2", + """**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 ''' + str(reste_att) + " euros", + '''reste = objectif - collecte + +print(f"Il manque {reste} euros") # Il manque ''' + str(reste_att) + " euros", +) + +md("""### 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", + """**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''', + '''don_1 = "25" +don_2 = "40" + +total = don_1 + don_2 +print(total) # affiche 2540 : sur deux chaînes, + est une concaténation + +# Correction : convertir les chaînes en nombres avant d'additionner +total = int(don_1) + int(don_2) +print(total) # 65''', +) + +# ---------------------------------------------------------------- PARTIE 2 + +md("""--- + +## 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 +""", dans_corrige=True) + +question( + "2.1", + """**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''', + '''def formater_don(pseudo, montant): + return f"{pseudo} vient de donner {montant} euros" + +# 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''', +) + +md("""### 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", + """**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''', + '''def est_gros_don(montant, seuil=50): + return montant >= seuil + +# 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''', +) + +md("""### 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", + """**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''', + '''def palier(total): + if total < 500000: + return "Départ" + elif total < 1000000: + return "Palier 1" + elif total < 2000000: + return "Palier 2" + else: + return "Objectif" + +# Test +print(palier(120000)) # Départ +print(palier(750000)) # Palier 1 +print(palier(1837450.75)) # Palier 2 +print(palier(2500000)) # Objectif''', +) + +md("""### 2.4. Le message complet +""") + +question( + "2.4", + """**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]''', + '''def annonce_regie(pseudo, montant): + message = formater_don(pseudo, montant) + if est_gros_don(montant): + message = message + " [GROS DON]" + return message + +# 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 + +md("""--- + +## 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é +""", dans_corrige=True) + +question( + "3.1", + """**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)) # ''' + str(total_att) + ''' +print(total_dons([])) # 0''', + '''def total_dons(liste_dons): + total = 0 + for don in liste_dons: + total = total + don["montant"] + return total + +# Test +print(total_dons(dons)) # ''' + str(total_att) + ''' +print(total_dons([])) # 0''', +) + +md("""### 3.2. Le don le plus élevé +""") + +question( + "3.2", + """**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"]) # ''' + record_att["pseudo"] + " " + str(record_att["montant"]), + '''def plus_gros_don(liste_dons): + record = liste_dons[0] # on part du premier don, surtout pas de 0 + for don in liste_dons: + if don["montant"] > record["montant"]: + record = don + return record + +# Test +record = plus_gros_don(dons) +print(record["pseudo"], record["montant"]) # ''' + record_att["pseudo"] + " " + str(record_att["montant"]), +) + +md("""### 3.3. Compter et filtrer +""") + +question( + "3.3", + """**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)) # ''' + str(gros50_att) + ''' +print(compter_gros_dons(dons, 100)) # ''' + str(gros100_att), + '''def compter_gros_dons(liste_dons, seuil=50): + compteur = 0 + for don in liste_dons: + if est_gros_don(don["montant"], seuil): + compteur = compteur + 1 + return compteur + +# Test +print(compter_gros_dons(dons)) # ''' + str(gros50_att) + ''' +print(compter_gros_dons(dons, 100)) # ''' + str(gros100_att), +) + +question( + "3.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)) +# ''' + str(donateurs_att), + '''def donateurs(liste_dons): + pseudos = [] + for don in liste_dons: + if don["pseudo"] not in pseudos: + pseudos.append(don["pseudo"]) + return pseudos + +# Test +print(donateurs(dons)) +# ''' + str(donateurs_att), +) + +md("""### 3.4. Le bandeau des derniers dons + +Le site n'affiche que les tout derniers dons reçus. +""") + +question( + "3.5", + """**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"]) +# ''' + "\n# ".join("%s %s" % (d["heure"], d["pseudo"]) for d in trois_derniers), + '''def derniers_dons(liste_dons, n): + return liste_dons[-n:] + +# Test +for don in derniers_dons(dons, 3): + print(don["heure"], don["pseudo"]) +# ''' + "\n# ".join("%s %s" % (d["heure"], d["pseudo"]) for d in trois_derniers), +) + +# ---------------------------------------------------------------- PARTIE 4 + +md("""--- + +## 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 +""", dans_corrige=True) + +question( + "4.1", + """**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"]) # ''' + str(cagnottes_att["Antoine Daniel"]) + ''' +print(totaux["Joueur du Grenier"]) # ''' + str(cagnottes_att["Joueur du Grenier"]) + ''' +print(len(totaux)) # ''' + str(len(cagnottes_att)), + '''def cagnottes(liste_dons): + totaux = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer in totaux: + totaux[streamer] = totaux[streamer] + don["montant"] + else: + totaux[streamer] = don["montant"] + return totaux + +# Test +totaux = cagnottes(dons) +print(totaux["Antoine Daniel"]) # ''' + str(cagnottes_att["Antoine Daniel"]) + ''' +print(totaux["Joueur du Grenier"]) # ''' + str(cagnottes_att["Joueur du Grenier"]) + ''' +print(len(totaux)) # ''' + str(len(cagnottes_att)), +) + +md("""### 4.2. La chaîne en tête +""") + +question( + "4.2", + """**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))) # ''' + meilleure_att, + '''def meilleure_chaine(totaux): + meilleur = None + for streamer in totaux: + if meilleur is None or totaux[streamer] > totaux[meilleur]: + meilleur = streamer + return meilleur + +# Test +print(meilleure_chaine(cagnottes(dons))) # ''' + meilleure_att, +) + +md("""### 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", + """**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"]) # ''' + str(detail_att["MisterMV"]) + ''' +print(detail["Baghera Jones"]) # ''' + str(detail_att["Baghera Jones"]), + '''def dons_par_chaine(liste_dons): + detail = {} + for don in liste_dons: + streamer = don["streamer"] + if streamer not in detail: + detail[streamer] = [] + detail[streamer].append(don["montant"]) + return detail + +# Test +detail = dons_par_chaine(dons) +print(detail["MisterMV"]) # ''' + str(detail_att["MisterMV"]) + ''' +print(detail["Baghera Jones"]) # ''' + str(detail_att["Baghera Jones"]), +) + +md("""### 4.4. Le podium +""") + +question( + "4.4", + """**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))) +# ''' + str(podium_att), + '''def podium(totaux): + classement = sorted(totaux, key=lambda streamer: totaux[streamer], reverse=True) + return classement[:3] + +# Test +print(podium(cagnottes(dons))) +# ''' + str(podium_att), +) + +# ---------------------------------------------------------------- PARTIE 5 + +md("""--- + +## 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. +""", dans_corrige=True) + +code(PLANNING_SRC) + +md("""### 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", + """**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)) # ''' + str(antenne_att(19)) + ''' +print(a_l_antenne(planning, 22)) # ''' + str(antenne_att(22)) + ''' +print(a_l_antenne(planning, 1)) # ''' + str(antenne_att(1)) + ''' +print(a_l_antenne(planning, 10)) # ''' + str(antenne_att(10)), + '''def a_l_antenne(planning, heure): + for streamer, debut, fin in planning: + if debut < fin: + if debut <= heure < fin: + return streamer + else: + # créneau à cheval sur minuit + if heure >= debut or heure < fin: + return streamer + return None + +# Test +print(a_l_antenne(planning, 19)) # ''' + str(antenne_att(19)) + ''' +print(a_l_antenne(planning, 22)) # ''' + str(antenne_att(22)) + ''' +print(a_l_antenne(planning, 1)) # ''' + str(antenne_att(1)) + ''' +print(a_l_antenne(planning, 10)) # ''' + str(antenne_att(10)), +) + +md("""### 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", + """**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")) +# ''' + str(raids_att("Antoine Daniel")) + ''' +print(suivre_raids(raids, "Horty")) +# ''' + str(raids_att("Horty")) + ''' +print(suivre_raids(raids, "Ultia")) +# ''' + str(raids_att("Ultia")), + '''def suivre_raids(raids, depart): + parcours = [depart] + courant = depart + while raids.get(courant): # .get évite l'erreur si la clé est absente + courant = raids[courant][0] + parcours.append(courant) + return parcours + +# Test +print(suivre_raids(raids, "Antoine Daniel")) +# ''' + str(raids_att("Antoine Daniel")) + ''' +print(suivre_raids(raids, "Horty")) +# ''' + str(raids_att("Horty")) + ''' +print(suivre_raids(raids, "Ultia")) +# ''' + str(raids_att("Ultia")), +) + +# ---------------------------------------------------------------- FIN + +md("""--- + +## 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** | + +--- + +""" + LICENCE) + + +# ---------------------------------------------------------------- SORTIES + +def enonce_md(): + parts = [] + for b in BLOCS: + if b[0] == "md": + parts.append(b[1].strip("\n")) + elif b[0] == "code": + parts.append("```python\n" + b[1] + "\n```") + else: + parts.append(b[2].strip("\n") + "\n\n```python\n" + b[3] + "\n```") + return "\n\n".join(parts) + "\n" + + +def corrige_md(): + parts = ["# Corrigé du TP Z-Event", "---"] + exclus = ("## Contexte", "## Objectifs pédagogiques", "## Les données", + "## Questions de synthèse", "## Ce que vous venez de réviser", + "## Barème indicatif") + for b in BLOCS: + if b[0] == "md": + # On reprend toute la hiérarchie de titres de l'énoncé (parties et + # sous-parties), sauf le titre du TP et les sections de clôture. + titres = [l for l in b[1].split("\n") + if l.startswith("## ") or l.startswith("### ")] + titres = [l for l in titres if l not in exclus] + if titres: + parts.append("\n\n".join(titres)) + elif b[0] == "question": + parts.append("**Question %s** :\n\n```python\n%s\n```" % (b[1], b[4])) + complet = [DONNEES_SRC, PLANNING_SRC] + [b[4] for b in BLOCS if b[0] == "question"] + parts.append("---") + parts.append("## Code complet fonctionnel") + parts.append("```python\n" + "\n\n".join(complet) + "\n```") + parts.append("---") + parts.append(LICENCE) + return "\n\n".join(parts) + "\n" + + +def notebook(): + cells = [] + + def add(kind, src): + c = {"cell_type": kind, "id": uuid.uuid4().hex[:8], "metadata": {}, + "source": src.splitlines(keepends=True)} + if kind == "code": + c["execution_count"] = None + c["outputs"] = [] + cells.append(c) + + for b in BLOCS: + if b[0] == "md": + add("markdown", b[1].strip("\n")) + elif b[0] == "code": + add("code", b[1]) + else: + add("markdown", b[2].strip("\n")) + add("code", b[3]) + return {"cells": cells, + "metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", + "name": "python3"}, + "language_info": {"name": "python", "version": "3.11"}}, + "nbformat": 4, "nbformat_minor": 5} + + +io.open(os.path.join(DEST, "TP_ZEvent.md"), "w", encoding="utf-8").write(enonce_md()) +io.open(os.path.join(DEST, "Corrige_TP_ZEvent.md"), "w", encoding="utf-8").write(corrige_md()) +with io.open(os.path.join(DEST, "TP_ZEvent.ipynb"), "w", encoding="utf-8") as f: + json.dump(notebook(), f, ensure_ascii=False, indent=1) + +print("écrits : TP_ZEvent.md, Corrige_TP_ZEvent.md, TP_ZEvent.ipynb (%d questions)" + % sum(1 for b in BLOCS if b[0] == "question"))