diff --git a/Revisions_Rentree/NOTES_PROF.md b/Revisions_Rentree/NOTES_PROF.md new file mode 100644 index 0000000..0d4ecfa --- /dev/null +++ b/Revisions_Rentree/NOTES_PROF.md @@ -0,0 +1,107 @@ +# Révisions de rentrée : PERRIAND-7 + +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. + +| Fichier | Pour qui | Publié sur hub-docs | +|---|---|---| +| `README.md` | Élèves | oui (page d'index de la section) | +| `PERRIAND-7_Reveil_des_systemes.ipynb` | Élèves | oui (`NOTEBOOK_ALLOWLIST`) | +| `PERRIAND-7_CORRIGE.ipynb` | Professeur | non (aucun notebook Terminale hors allowlist) | +| `NOTES_PROF.md` | Professeur | non (`EXCLUDE_FILES`) | +| `_generateur.py` | Professeur | non (`EXCLUDE_FILES`, il contient le corrigé) | + +Notebooks ouvrables sur [Basthon](https://basthon.fr), Jupyter ou VS Code. +Aucune bibliothèque externe, aucun fichier annexe. + +--- + +## Intention + +Ces activités ne révisent **pas** le programme de Première pour lui-même : chaque notion +est choisie et cadrée pour ce qui arrive derrière en Terminale. + +| Module | Notion révisée | Prépare | +|---|---|---| +| 1, Le réacteur | affectation, mutabilité, aliasing de listes | POO (attributs), listes chaînées | +| 2, Les protocoles | `return` vs `print`, paramètres, portée | **Récursivité**, Modularité | +| 3, Le balayage | `for` / `while`, accumulateur, `range(len(...))` | parcours d'arbres et de graphes | +| 4, La soute | indices, tranches, listes de listes, compréhensions | structures linéaires, tri fusion | +| 5, L'équipage | dictionnaires, `.get()`, dict de listes | **graphes** (liste d'adjacence), mémoïsation | +| 6, Sortie d'orbite | synthèse : parcours avec une pile, pile d'appels | Pile/File, **Récursivité** | + +Le module 6 se termine sur un compte à rebours récursif à tracer à la main : c'est le +raccord direct avec le chapitre en cours. + +--- + +## Parti pris pédagogique + +**Lire et réparer avant d'écrire.** Après deux mois d'arrêt, la page blanche bloque. Chaque +module suit donc la même montée : *diagnostic* (prédire la sortie sans exécuter), puis +*réparation* (code cassé à corriger), puis *production* (écrire), puis *bonus*. +Les diagnostics donnent aussi un repérage rapide de qui a réellement décroché. + +**La difficulté monte, comme au bac.** Les derniers exercices de chaque module, et le +module 6 dans son ensemble, sont volontairement plus exigeants. C'est annoncé aux élèves +dès l'introduction du notebook : les dernières questions servent à voir jusqu'où vont les +meilleurs, ne pas les terminer n'est pas un échec. + +**Auto-correction par `assert`.** Chaque production est suivie d'une cellule de tests qui +affiche `OK`. Les élèves avancent sans file d'attente devant le bureau, et ça amorce le +chapitre Modularité (jeux de tests). Tous les `assert` du corrigé ont été exécutés et +passent. + +**Modules indépendants.** Chaque module s'ouvre sur une cellule **SECOURS** qui redéfinit +ses données. Un élève bloqué au module 2 peut sauter au module 5 sans rien casser : le fil +rouge est narratif, pas technique. + +--- + +## Conduite de séance + +Compter **2 h** pour le parcours complet bonus inclus. Sur **1 h**, le noyau utile est : + +- Module 1 : diagnostics 1.1 et **1.2** (l'aliasing, celui qui rapporte le plus) +- Module 2 : diagnostic 2.1 et réparation 2.2 (`print` vs `return`) +- Module 3 : production 3.4 (maximum à la main) +- Module 5 : production 5.3 (dictionnaire de comptage) + +Les bonus (1.5, 2.5, 3.6, 4.5, 5.5) sont là pour occuper les rapides. 4.5 (fusion de deux +listes triées) et 6.1 (parcours en profondeur) sont les deux vrais morceaux. + +**À faire au tableau plutôt qu'en autonomie :** + +- **1.2 (aliasing)** : un schéma mémoire de deux flèches vers la même liste vaut mieux + qu'une explication écrite. +- **6.2 (compte à rebours)** : à tracer collectivement, c'est le pont vers la pile d'appels. + +**Points de vigilance :** + +- Le diagnostic **3.2** contient une boucle infinie. Elle est en bloc Markdown non + exécutable et signalée comme telle, mais prévenir à l'oral évite un Basthon figé. +- Le diagnostic **2.3** provoque volontairement une `UnboundLocalError`. Insister sur le + fait que la bonne réponse est le passage en paramètre, **pas** `global`. +- La réparation **4.2** (suppression pendant l'itération) est le plus difficile des + exercices non-bonus : leur faire d'abord exécuter le code cassé pour constater. + +--- + +## Régénérer les notebooks + +Les deux notebooks sont produits par un script unique (énoncé et corrigé partagent la +même source, ce qui évite qu'ils divergent) : + +```bash +python3 _generateur.py +``` + +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 +fichiers `.py` comme ressources téléchargeables. Il est explicitement exclu du build +(`EXCLUDE_FILES`), tout comme le présent fichier. Ne pas le renommer sans mettre à jour +cette exclusion côté serveur. + +--- +*Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0* diff --git a/Revisions_Rentree/PERRIAND-7_CORRIGE.ipynb b/Revisions_Rentree/PERRIAND-7_CORRIGE.ipynb new file mode 100644 index 0000000..35a82ab --- /dev/null +++ b/Revisions_Rentree/PERRIAND-7_CORRIGE.ipynb @@ -0,0 +1,1549 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a93f33a5", + "metadata": {}, + "source": [ + "# PERRIAND-7 : protocole de réveil\n", + "\n", + "> **Journal de bord, jour 68.**\n", + "> Il y a soixante-huit jours, l'équipage de la station orbitale **PERRIAND-7** est entré\n", + "> en cryosommeil pour traverser la ceinture de radiations de Van Allen étendue.\n", + "> Vous venez d'ouvrir les yeux.\n", + ">\n", + "> La station, elle, s'est moins bien réveillée que vous. Les radiations ont provoqué des\n", + "> corruptions mémoire dans l'ordinateur de bord : plusieurs programmes affichent des\n", + "> valeurs absurdes, d'autres refusent tout simplement de s'exécuter.\n", + ">\n", + "> Vous êtes l'**ingénieur système** de bord. Cinq modules à réveiller, puis la sortie\n", + "> d'orbite. Dans l'ordre.\n", + "\n", + "---\n", + "\n", + "## Comment travailler\n", + "\n", + "Chaque module contient quatre types d'exercices :\n", + "\n", + "| Type | Ce qu'on attend de vous |\n", + "|---|---|\n", + "| **DIAGNOSTIC** | Lire le code et **prédire** ce qu'il affiche **avant** de l'exécuter. Écrivez votre prédiction dans le commentaire prévu, puis exécutez pour vérifier. |\n", + "| **RÉPARATION** | Le code est cassé. Trouvez l'erreur et corrigez-la. |\n", + "| **PRODUCTION** | Écrivez le code demandé. |\n", + "| **BONUS** | Facultatif. À faire si vous avez terminé le module. |\n", + "\n", + "**La difficulté monte.** Dans chaque module, les derniers exercices sont volontairement\n", + "plus exigeants, et le module 6 l'est encore davantage. C'est le principe d'un sujet de\n", + "bac : les dernières questions servent à voir jusqu'où vont les meilleurs. Ne pas les\n", + "terminer n'est pas un échec ; les faire, c'est du bonus.\n", + "\n", + "**Auto-correction.** Chaque exercice de production est suivi d'une cellule de tests.\n", + "Exécutez-la : si `OK` s'affiche, c'est gagné. Si vous obtenez une `AssertionError`,\n", + "c'est que le code ne fait pas encore ce qui est demandé.\n", + "\n", + "**Bloqué ?** Chaque module commence par une cellule **SECOURS** qui remet en place\n", + "toutes les données dont il a besoin. Les modules sont **indépendants** : vous pouvez\n", + "passer au suivant sans avoir fini le précédent.\n", + "\n", + "---\n", + "\n", + "*Objectif de la séance : redémarrer le moteur après deux mois d'arrêt. Rien de ce qui suit\n", + "n'est nouveau : tout a été vu en Première. Mais chaque notion révisée ici sert directement\n", + "un chapitre de Terminale.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "78b55328", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 1 : LE RÉACTEUR\n", + "\n", + "### *Variables, affectation, mutabilité*\n", + "\n", + "> Le réacteur tourne. C'est déjà ça. Mais les jauges du poste de pilotage affichent\n", + "> n'importe quoi : 4 % d'oxygène ici, 340 % là. Avant de réparer un seul programme,\n", + "> il faut comprendre **comment l'ordinateur de bord range ses valeurs en mémoire**.\n", + "\n", + "*Ce module prépare : POO (les attributs d'un objet sont des variables), et les listes\n", + "chaînées (où deux variables peuvent pointer vers le même maillon).*\n" + ] + }, + { + "cell_type": "markdown", + "id": "5dadd73c", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 1" + ] + }, + { + "cell_type": "code", + "id": "478848a2", + "metadata": {}, + "source": [ + "# Relevés des capteurs au réveil\n", + "TAUX_O2_NOMINAL = 21 # en pourcentage\n", + "PRESSION_NOMINALE = 101 # en kilopascals\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "95786cfa", + "metadata": {}, + "source": [ + "## Diagnostic 1.1 : l'ordre des affectations\n", + "\n", + "L'ordinateur a exécuté ces quatre lignes. **Avant d'exécuter la cellule**, écrivez ce que\n", + "vous pensez qu'elle va afficher.\n", + "\n", + "*Rappel : une affectation `a = b` calcule d'abord la valeur de droite, puis la range dans\n", + "la variable de gauche. Ce qui est calculé est calculé, ça ne se recalcule pas tout seul.*\n" + ] + }, + { + "cell_type": "code", + "id": "689a973e", + "metadata": {}, + "source": [ + "oxygene = 21\n", + "oxygene = oxygene - 4\n", + "pression = oxygene * 2\n", + "oxygene = 21\n", + "\n", + "print(oxygene, pression)\n", + "\n", + "# Ma prédiction : 21 34\n", + "# La ligne 3 a utilisé la valeur d'oxygene à cet instant (17), pas sa valeur finale.\n", + "# pression vaut donc 34 et ne « suit » pas les changements ultérieurs d'oxygene.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "6d90a2da", + "metadata": {}, + "source": [ + "## Diagnostic 1.2 : deux noms, une seule liste\n", + "\n", + "Celui-ci piège tout le monde. Prédisez, puis exécutez.\n" + ] + }, + { + "cell_type": "code", + "id": "992a7317", + "metadata": {}, + "source": [ + "reserve_principale = [120, 80, 50]\n", + "reserve_secours = reserve_principale\n", + "reserve_secours.append(0)\n", + "\n", + "print(\"principale :\", reserve_principale)\n", + "print(\"secours :\", reserve_secours)\n", + "\n", + "# Ma prédiction : les deux affichent [120, 80, 50, 0]\n", + "# `reserve_secours = reserve_principale` ne copie PAS la liste : elle donne un\n", + "# deuxième nom à la même liste en mémoire. Modifier l'une modifie l'autre.\n", + "# Pour obtenir une vraie copie : reserve_secours = list(reserve_principale)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "7591d969", + "metadata": {}, + "source": [ + "> **À retenir.** Une liste est **mutable** : on peut la modifier sur place.\n", + "> Un entier ou une chaîne ne le sont pas. C'est pour ça que le piège ci-dessus\n", + "> n'existe pas avec des entiers.\n" + ] + }, + { + "cell_type": "markdown", + "id": "77d8d7e9", + "metadata": {}, + "source": [ + "## Réparation 1.3 : l'échange raté\n", + "\n", + "Le programme de bord doit **échanger** les valeurs de deux capteurs mal branchés.\n", + "Le technicien précédent a écrit ceci. Ça ne marche pas.\n", + "\n", + "**Corrigez** la cellule pour que l'échange fonctionne réellement.\n" + ] + }, + { + "cell_type": "code", + "id": "17fd2126", + "metadata": {}, + "source": [ + "temp_interne = 18\n", + "temp_externe = -270\n", + "\n", + "# --- échange (corrigé) ---\n", + "temp_interne, temp_externe = temp_externe, temp_interne\n", + "# Variante sans affectation multiple, avec une variable tampon :\n", + "# tampon = temp_interne\n", + "# temp_interne = temp_externe\n", + "# temp_externe = tampon\n", + "# -------------------------\n", + "\n", + "print(temp_interne, temp_externe)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "4a6cadfc", + "metadata": {}, + "source": [ + "assert temp_interne == -270 and temp_externe == 18\n", + "print(\"OK. Échange réussi\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c0af578e", + "metadata": {}, + "source": [ + "## Production 1.4 : calcul de l'autonomie\n", + "\n", + "Il reste `litres_oxygene` litres d'oxygène dans les réserves. Chaque membre d'équipage\n", + "consomme `consommation_par_heure` litres par heure.\n", + "\n", + "Calculez dans une variable **`autonomie_h`** le nombre **entier** d'heures complètes\n", + "que l'équipage peut encore tenir.\n", + "\n", + "*Indice : `//` est la division entière, celle qui ne garde que le nombre de tours complets.*\n" + ] + }, + { + "cell_type": "code", + "id": "335286c9", + "metadata": {}, + "source": [ + "litres_oxygene = 480\n", + "consommation_par_heure = 3\n", + "nb_membres = 4\n", + "\n", + "autonomie_h = litres_oxygene // (consommation_par_heure * nb_membres)\n", + "\n", + "print(\"Autonomie :\", autonomie_h, \"heures\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "7078945c", + "metadata": {}, + "source": [ + "assert autonomie_h == 40, \"Attendu 40, obtenu \" + str(autonomie_h)\n", + "print(\"OK. Autonomie correcte : 40 h\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b5e97d99", + "metadata": {}, + "source": [ + "## Bonus 1.5 : copie indépendante\n", + "\n", + "Créez `sauvegarde` : une copie de `releves` qui **ne bouge pas** quand on modifie `releves`.\n" + ] + }, + { + "cell_type": "code", + "id": "b14a568c", + "metadata": {}, + "source": [ + "releves = [21, 101, 18]\n", + "\n", + "sauvegarde = list(releves) # ou releves[:] , ou releves.copy()\n", + "\n", + "releves.append(999)\n", + "print(\"releves :\", releves)\n", + "print(\"sauvegarde:\", sauvegarde)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "501e5545", + "metadata": {}, + "source": [ + "assert sauvegarde == [21, 101, 18]\n", + "assert releves == [21, 101, 18, 999]\n", + "print(\"OK. La sauvegarde est bien indépendante\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "f641f089", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 2 : LES PROTOCOLES\n", + "\n", + "### *Fonctions : paramètres, `return`, portée*\n", + "\n", + "> Les protocoles de sécurité de la station sont des **fonctions**. On les appelle, elles\n", + "> répondent. Sauf que depuis les radiations, plusieurs d'entre elles ne répondent plus\n", + "> rien du tout : elles se contentent de parler dans le vide.\n", + "\n", + "*Ce module prépare : la **récursivité** (une fonction qui s'appelle elle-même n'a de\n", + "sens que si elle **renvoie** quelque chose) et la Modularité.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "256f655e", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 2" + ] + }, + { + "cell_type": "code", + "id": "1a2d260b", + "metadata": {}, + "source": [ + "SEUIL_CRITIQUE = 80\n", + "SEUIL_ALERTE = 95\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "1f5288be", + "metadata": {}, + "source": [ + "## Diagnostic 2.1 : `print` n'est pas `return`\n", + "\n", + "Prédisez les **deux** lignes affichées.\n" + ] + }, + { + "cell_type": "code", + "id": "0d34f3b6", + "metadata": {}, + "source": [ + "def niveau_pression(pression):\n", + " if pression < 80:\n", + " print(\"CRITIQUE\")\n", + " else:\n", + " print(\"NOMINAL\")\n", + "\n", + "resultat = niveau_pression(45)\n", + "print(\"resultat vaut :\", resultat)\n", + "\n", + "# Ma prédiction : CRITIQUE puis resultat vaut : None\n", + "# La fonction AFFICHE mais ne RENVOIE rien. Une fonction sans return renvoie None.\n", + "# On ne peut donc rien faire du résultat : ni le tester, ni le stocker, ni le réutiliser.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "87105d54", + "metadata": {}, + "source": [ + "> **À retenir.** `print` **montre** à l'humain. `return` **rend** une valeur au\n", + "> programme. Une fonction qui ne fait que `print` est un cul-de-sac : on ne peut pas\n", + "> construire quoi que ce soit par-dessus.\n" + ] + }, + { + "cell_type": "markdown", + "id": "1cc2433d", + "metadata": {}, + "source": [ + "## Réparation 2.2 : rendre le protocole utilisable\n", + "\n", + "Réécrivez `niveau_pression` pour qu'elle **renvoie** la chaîne au lieu de l'afficher.\n", + "\n", + "- pression strictement inférieure à 80 : renvoie `\"CRITIQUE\"`\n", + "- pression entre 80 (inclus) et 95 (exclu) : renvoie `\"ALERTE\"`\n", + "- pression supérieure ou égale à 95 : renvoie `\"NOMINAL\"`\n" + ] + }, + { + "cell_type": "code", + "id": "69a2966b", + "metadata": {}, + "source": [ + "def niveau_pression(pression):\n", + " if pression < 80:\n", + " return \"CRITIQUE\"\n", + " elif pression < 95:\n", + " return \"ALERTE\"\n", + " else:\n", + " return \"NOMINAL\"\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "c2619cc3", + "metadata": {}, + "source": [ + "assert niveau_pression(45) == \"CRITIQUE\"\n", + "assert niveau_pression(79) == \"CRITIQUE\"\n", + "assert niveau_pression(80) == \"ALERTE\"\n", + "assert niveau_pression(94) == \"ALERTE\"\n", + "assert niveau_pression(95) == \"NOMINAL\"\n", + "assert niveau_pression(120) == \"NOMINAL\"\n", + "print(\"OK. Protocole de pression opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "8378d251", + "metadata": {}, + "source": [ + "## Diagnostic 2.3 : la variable qui n'existe pas\n", + "\n", + "Ce code **plante**. Avant de l'exécuter, essayez de dire **pourquoi**.\n" + ] + }, + { + "cell_type": "code", + "id": "6db4687c", + "metadata": {}, + "source": [ + "compteur_alertes = 0\n", + "\n", + "def signaler_alerte():\n", + " compteur_alertes = compteur_alertes + 1\n", + " return compteur_alertes\n", + "\n", + "print(signaler_alerte())\n", + "\n", + "# Ma prédiction : UnboundLocalError.\n", + "# Comme `compteur_alertes` reçoit une affectation DANS la fonction, Python la considère\n", + "# comme une variable LOCALE. Au moment du calcul `compteur_alertes + 1`, cette variable\n", + "# locale n'a encore aucune valeur : erreur.\n", + "# La bonne solution n'est PAS `global` : c'est de passer la valeur en PARAMÈTRE\n", + "# et de renvoyer la nouvelle (voir cellule suivante).\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "dcfee713", + "metadata": {}, + "source": [ + "**Corrigez** en transformant `signaler_alerte` en une fonction qui prend le compteur\n", + "actuel en paramètre et renvoie le compteur augmenté de 1.\n" + ] + }, + { + "cell_type": "code", + "id": "c3c6acd6", + "metadata": {}, + "source": [ + "def signaler_alerte(compteur):\n", + " return compteur + 1\n", + "\n", + "compteur_alertes = 0\n", + "compteur_alertes = signaler_alerte(compteur_alertes)\n", + "compteur_alertes = signaler_alerte(compteur_alertes)\n", + "print(compteur_alertes)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "d257cafd", + "metadata": {}, + "source": [ + "assert signaler_alerte(0) == 1\n", + "assert signaler_alerte(41) == 42\n", + "assert compteur_alertes == 2\n", + "print(\"OK. Compteur d'alertes réparé\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "58f7e8e7", + "metadata": {}, + "source": [ + "## Production 2.4 : une fonction qui en appelle une autre\n", + "\n", + "Écrivez `diagnostic(pression, temperature)` qui renvoie une chaîne de la forme :\n", + "\n", + "```\n", + "\"Pression NOMINAL / Temperature 18\"\n", + "```\n", + "\n", + "Votre fonction **doit appeler** `niveau_pression` : ne recopiez pas les tests de seuil.\n", + "\n", + "*Indice : pour coller des morceaux de texte, `+` fonctionne, mais il faut convertir les\n", + "nombres avec `str(...)`.*\n" + ] + }, + { + "cell_type": "code", + "id": "2198c1e1", + "metadata": {}, + "source": [ + "def diagnostic(pression, temperature):\n", + " return \"Pression \" + niveau_pression(pression) + \" / Temperature \" + str(temperature)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "b3bda8d7", + "metadata": {}, + "source": [ + "assert diagnostic(101, 18) == \"Pression NOMINAL / Temperature 18\"\n", + "assert diagnostic(45, -3) == \"Pression CRITIQUE / Temperature -3\"\n", + "assert diagnostic(88, 0) == \"Pression ALERTE / Temperature 0\"\n", + "print(\"OK. Diagnostic complet opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "d2b97f8e", + "metadata": {}, + "source": [ + "## Bonus 2.5 : paramètre par défaut\n", + "\n", + "Écrivez `verrouiller(module, code_acces=\"0000\")` qui renvoie :\n", + "- `\"MODULE VERROUILLE\"` si le code fourni est `\"0000\"`\n", + "- `\"CODE REFUSE\"` sinon\n" + ] + }, + { + "cell_type": "code", + "id": "1c8cf8b8", + "metadata": {}, + "source": [ + "def verrouiller(module, code_acces=\"0000\"):\n", + " if code_acces == \"0000\":\n", + " return \"MODULE VERROUILLE\"\n", + " else:\n", + " return \"CODE REFUSE\"\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "9b41596d", + "metadata": {}, + "source": [ + "assert verrouiller(\"Sas\") == \"MODULE VERROUILLE\"\n", + "assert verrouiller(\"Sas\", \"0000\") == \"MODULE VERROUILLE\"\n", + "assert verrouiller(\"Sas\", \"1234\") == \"CODE REFUSE\"\n", + "print(\"OK. Verrouillage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "d944cb75", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 3 : LE BALAYAGE\n", + "\n", + "### *Boucles : `for`, `while`, accumulateur*\n", + "\n", + "> Il faut maintenant **balayer** les 5 000 relevés de capteurs accumulés pendant\n", + "> l'hibernation, pour repérer les anomalies. Personne ne fera ça à la main.\n", + "\n", + "*Ce module prépare : le parcours des arbres et des graphes, où l'on visite un à un\n", + "tous les éléments d'une structure sans savoir à l'avance combien il y en a.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "e7028649", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 3" + ] + }, + { + "cell_type": "code", + "id": "7f6e4205", + "metadata": {}, + "source": [ + "mesures = [12, 45, 7, 88, 23, 91, 4]\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c8f03981", + "metadata": {}, + "source": [ + "## Diagnostic 3.1 : combien de tours ?\n", + "\n", + "Prédisez ce qui s'affiche, **et combien de lignes**.\n" + ] + }, + { + "cell_type": "code", + "id": "173b3b0d", + "metadata": {}, + "source": [ + "i = 0\n", + "while i < 5:\n", + " print(i)\n", + " i = i + 2\n", + "\n", + "print(\"fin, i vaut\", i)\n", + "\n", + "# Ma prédiction : 0, 2, 4 (trois lignes), puis « fin, i vaut 6 ».\n", + "# Le test est réévalué AVANT chaque tour. Après le tour où i valait 4, i passe à 6 :\n", + "# 6 < 5 est faux, la boucle s'arrête. i vaut donc 6 à la sortie, pas 5.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "5bfb904c", + "metadata": {}, + "source": [ + "## Diagnostic 3.2 : NE PAS EXÉCUTER\n", + "\n", + "Cette cellule est en mode texte : **ne l'exécutez pas**, elle ne s'arrêterait jamais.\n", + "\n", + "```python\n", + "carburant = 100\n", + "while carburant > 0:\n", + " print(\"Purge en cours...\")\n", + " carburant = 100\n", + "```\n", + "\n", + "**Question (répondez dans la cellule ci-dessous) :** pourquoi cette boucle ne se\n", + "termine-t-elle jamais, et quelle est la plus petite modification qui la ferait terminer ?\n" + ] + }, + { + "cell_type": "code", + "id": "7101e62c", + "metadata": {}, + "source": [ + "# Ma réponse :\n", + "# La condition `carburant > 0` reste vraie éternellement parce que le corps de la boucle\n", + "# remet carburant à 100 au lieu de le faire décroître. Rien ne rapproche le programme de\n", + "# la condition d'arrêt.\n", + "# Plus petite correction : remplacer `carburant = 100` par une décrémentation,\n", + "# par exemple `carburant = carburant - 10`.\n", + "#\n", + "# Règle : dans un while, une variable au moins doit évoluer VERS la condition d'arrêt.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2ff42b17", + "metadata": {}, + "source": [ + "## Réparation 3.3 : le capteur oublié\n", + "\n", + "Ce code doit afficher **toutes** les mesures. Il en rate une. Trouvez laquelle, et corrigez.\n" + ] + }, + { + "cell_type": "code", + "id": "351f2dfd", + "metadata": {}, + "source": [ + "for i in range(len(mesures)): # range(len(...)) commence à 0, pas à 1\n", + " print(\"capteur\", i, \":\", mesures[i])\n", + "\n", + "# La version initiale commençait à l'indice 1 et sautait donc mesures[0] (la valeur 12).\n", + "# En Python le premier indice d'une liste est 0.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "295ff3a3", + "metadata": {}, + "source": [ + "## Production 3.4 : le maximum, à la main\n", + "\n", + "Écrivez `maximum(valeurs)` qui renvoie la plus grande valeur d'une liste **non vide**,\n", + "**sans utiliser `max()`**.\n", + "\n", + "*Méthode de l'accumulateur : on retient un « meilleur pour l'instant », et on le met à\n", + "jour à chaque tour.*\n" + ] + }, + { + "cell_type": "code", + "id": "988911f1", + "metadata": {}, + "source": [ + "def maximum(valeurs):\n", + " plus_grand = valeurs[0] # on part du premier élément, pas de 0 !\n", + " for v in valeurs:\n", + " if v > plus_grand:\n", + " plus_grand = v\n", + " return plus_grand\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "52c478ad", + "metadata": {}, + "source": [ + "assert maximum([12, 45, 7, 88, 23, 91, 4]) == 91\n", + "assert maximum([5]) == 5\n", + "assert maximum([3, 3, 3]) == 3\n", + "assert maximum([-10, -3, -50]) == -3, \"attention aux valeurs négatives : ne partez pas de 0\"\n", + "print(\"OK. Détection du maximum opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "9aed0761", + "metadata": {}, + "source": [ + "## Production 3.5 : le premier capteur défaillant\n", + "\n", + "Écrivez `premier_defaillant(valeurs, seuil)` qui renvoie **l'indice** de la première\n", + "mesure strictement inférieure à `seuil`, et `-1` si aucune ne l'est.\n", + "\n", + "*Indice : ici on a besoin de l'indice, donc de `range(len(valeurs))`. Et on peut sortir\n", + "de la fonction dès qu'on a trouvé.*\n" + ] + }, + { + "cell_type": "code", + "id": "b4f9a7da", + "metadata": {}, + "source": [ + "def premier_defaillant(valeurs, seuil):\n", + " for i in range(len(valeurs)):\n", + " if valeurs[i] < seuil:\n", + " return i # return sort immédiatement de la fonction\n", + " return -1 # atteint seulement si la boucle s'est terminée\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "cc575948", + "metadata": {}, + "source": [ + "assert premier_defaillant([12, 45, 7, 88], 20) == 0\n", + "assert premier_defaillant([45, 88, 7, 4], 10) == 2\n", + "assert premier_defaillant([45, 88, 91], 10) == -1\n", + "assert premier_defaillant([], 10) == -1\n", + "print(\"OK. Détection de défaillance opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "93fd32a3", + "metadata": {}, + "source": [ + "## Bonus 3.6 : la sentinelle\n", + "\n", + "Le capteur d'altitude envoie ses relevés en continu et termine sa transmission par la\n", + "valeur `-1`. Écrivez `somme_jusqu_a_sentinelle(flux)` qui additionne les valeurs\n", + "**jusqu'au premier `-1` exclu**. Les valeurs après le `-1` sont du bruit : on les ignore.\n", + "\n", + "*Indice : un `while` avec deux conditions est plus naturel qu'un `for` ici.*\n" + ] + }, + { + "cell_type": "code", + "id": "884ea8be", + "metadata": {}, + "source": [ + "def somme_jusqu_a_sentinelle(flux):\n", + " total = 0\n", + " i = 0\n", + " while i < len(flux) and flux[i] != -1:\n", + " total = total + flux[i]\n", + " i = i + 1\n", + " return total\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "18d2eefd", + "metadata": {}, + "source": [ + "assert somme_jusqu_a_sentinelle([10, 20, 30, -1, 999, 999]) == 60\n", + "assert somme_jusqu_a_sentinelle([-1, 5, 5]) == 0\n", + "assert somme_jusqu_a_sentinelle([1, 2, 3]) == 6\n", + "assert somme_jusqu_a_sentinelle([]) == 0\n", + "print(\"OK. Lecture du flux opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "fb04347d", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 4 : LA SOUTE\n", + "\n", + "### *Listes : indices, tranches, listes de listes*\n", + "\n", + "> La soute est organisée en rayonnages. L'inventaire numérique a survécu, mais les\n", + "> programmes qui l'exploitent, non.\n", + "\n", + "*Ce module prépare : les structures linéaires (pile, file, listes chaînées) et le\n", + "tri fusion du chapitre « diviser pour régner ».*\n" + ] + }, + { + "cell_type": "markdown", + "id": "371df149", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 4" + ] + }, + { + "cell_type": "code", + "id": "4cf96ad9", + "metadata": {}, + "source": [ + "soute = [\"eau\", \"vivres\", \"outils\", \"piles\", \"filtres\"]\n", + "\n", + "# Chaque sous-liste est un rayonnage : [rayon_0, rayon_1, rayon_2]\n", + "# Chaque ligne = un étage de la soute.\n", + "stock = [[12, 5, 0],\n", + " [3, 8, 7],\n", + " [0, 0, 4],\n", + " [9, 1, 2]]\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "48338cbd", + "metadata": {}, + "source": [ + "## Diagnostic 4.1 : indices et tranches\n", + "\n", + "Prédisez les quatre affichages.\n" + ] + }, + { + "cell_type": "code", + "id": "cd9f1502", + "metadata": {}, + "source": [ + "print(soute[0])\n", + "print(soute[-1])\n", + "print(soute[1:3])\n", + "print(len(soute[1:3]))\n", + "\n", + "# Ma prédiction :\n", + "# eau\n", + "# filtres\n", + "# ['vivres', 'outils']\n", + "# 2\n", + "# Une tranche [a:b] va de l'indice a INCLUS à l'indice b EXCLU : elle contient b - a\n", + "# éléments. L'indice -1 désigne le dernier élément.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "8a19ce4b", + "metadata": {}, + "source": [ + "## Réparation 4.2 : le bug du technicien\n", + "\n", + "Ce code doit retirer de `soute` tout ce qui commence par la lettre `\"p\"`.\n", + "Il en oublie. **Exécutez-le d'abord** pour constater le problème, puis corrigez-le.\n", + "\n", + "*Indice : modifier une liste pendant qu'on la parcourt décale les indices sous les pieds\n", + "de la boucle. La solution habituelle est de **construire une nouvelle liste** plutôt que\n", + "de retirer dans l'ancienne.*\n" + ] + }, + { + "cell_type": "code", + "id": "e4e03699", + "metadata": {}, + "source": [ + "soute_test = [\"piles\", \"pinces\", \"eau\", \"outils\"]\n", + "\n", + "# Version corrigée : on construit une nouvelle liste au lieu de modifier celle qu'on parcourt\n", + "soute_gardee = []\n", + "for objet in soute_test:\n", + " if objet[0] != \"p\":\n", + " soute_gardee.append(objet)\n", + "soute_test = soute_gardee\n", + "\n", + "print(soute_test) # ['eau', 'outils']\n", + "\n", + "# Pourquoi la version initiale échouait : après avoir retiré \"piles\" (indice 0), tous les\n", + "# éléments se décalent d'un cran. La boucle passe alors à l'indice 1, qui contient\n", + "# désormais \"eau\" : \"pinces\" a été SAUTÉ.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "c14b821d", + "metadata": {}, + "source": [ + "assert soute_test == [\"eau\", \"outils\"]\n", + "print(\"OK. Purge de la soute correcte\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "fd1106a9", + "metadata": {}, + "source": [ + "## Production 4.3 : total d'un rayonnage\n", + "\n", + "`stock` est une **liste de listes** : `stock[i][j]` est la quantité stockée à l'étage `i`,\n", + "rayon `j`.\n", + "\n", + "Écrivez `total_rayon(grille, r)` qui renvoie la somme des quantités du rayon `r`,\n", + "**tous étages confondus**.\n", + "\n", + "*Attention : le rayon `r` est une **colonne**, pas une ligne. Il faut parcourir les étages.*\n" + ] + }, + { + "cell_type": "code", + "id": "0ae629a8", + "metadata": {}, + "source": [ + "def total_rayon(grille, r):\n", + " total = 0\n", + " for etage in grille: # etage est une liste : [rayon_0, rayon_1, rayon_2]\n", + " total = total + etage[r]\n", + " return total\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "e42135a8", + "metadata": {}, + "source": [ + "assert total_rayon(stock, 0) == 24\n", + "assert total_rayon(stock, 1) == 14\n", + "assert total_rayon(stock, 2) == 13\n", + "print(\"OK. Inventaire par rayon opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b55b34d2", + "metadata": {}, + "source": [ + "## Production 4.4 : filtrer en une ligne\n", + "\n", + "Écrivez `au_dessus(valeurs, seuil)` qui renvoie la liste des valeurs strictement\n", + "supérieures à `seuil`, **en utilisant une compréhension de liste**.\n", + "\n", + "*Rappel de la forme : `[expression for element in liste if condition]`*\n" + ] + }, + { + "cell_type": "code", + "id": "981dd8ce", + "metadata": {}, + "source": [ + "def au_dessus(valeurs, seuil):\n", + " return [v for v in valeurs if v > seuil]\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "1e09e55a", + "metadata": {}, + "source": [ + "assert au_dessus([12, 45, 7, 88, 23], 20) == [45, 88, 23]\n", + "assert au_dessus([1, 2, 3], 10) == []\n", + "assert au_dessus([], 0) == []\n", + "print(\"OK. Filtrage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "92fa2192", + "metadata": {}, + "source": [ + "## Bonus 4.5 : fusionner deux listes triées\n", + "\n", + "Deux capteurs ont enregistré des relevés, chacun **déjà trié dans l'ordre croissant**.\n", + "Écrivez `fusion(a, b)` qui renvoie une seule liste triée contenant tous les éléments,\n", + "**sans utiliser `sorted()` ni `.sort()`**.\n", + "\n", + "*Méthode : deux indices qui avancent, on prend à chaque tour le plus petit des deux\n", + "éléments courants.*\n", + "\n", + "> Cet exercice est **exactement** la seconde moitié du tri fusion, que vous verrez\n", + "> dans le chapitre « diviser pour régner ».\n" + ] + }, + { + "cell_type": "code", + "id": "36133017", + "metadata": {}, + "source": [ + "def fusion(a, b):\n", + " resultat = []\n", + " i = 0\n", + " j = 0\n", + " while i < len(a) and j < len(b):\n", + " if a[i] <= b[j]:\n", + " resultat.append(a[i])\n", + " i = i + 1\n", + " else:\n", + " resultat.append(b[j])\n", + " j = j + 1\n", + " # une des deux listes est épuisée : on recopie ce qui reste de l'autre\n", + " while i < len(a):\n", + " resultat.append(a[i])\n", + " i = i + 1\n", + " while j < len(b):\n", + " resultat.append(b[j])\n", + " j = j + 1\n", + " return resultat\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "f0810b0c", + "metadata": {}, + "source": [ + "assert fusion([1, 4, 9], [2, 3, 10]) == [1, 2, 3, 4, 9, 10]\n", + "assert fusion([], [2, 3]) == [2, 3]\n", + "assert fusion([5], []) == [5]\n", + "assert fusion([1, 1], [1]) == [1, 1, 1]\n", + "print(\"OK. Fusion opérationnelle, vous venez d'écrire la moitié d'un tri fusion\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "41d890c2", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 5 : L'ÉQUIPAGE\n", + "\n", + "### *Dictionnaires : clés, valeurs, parcours*\n", + "\n", + "> Reste le plus important : les **dossiers de l'équipage** et le **plan de la station**.\n", + "> Une liste ne suffit plus : on ne cherche pas « le 3ᵉ membre », on cherche « Kowalski ».\n", + "\n", + "*Ce module prépare : les graphes (un graphe se représente par un dictionnaire de\n", + "listes) et la mémoïsation en programmation dynamique (un dictionnaire de résultats\n", + "déjà calculés).*\n" + ] + }, + { + "cell_type": "markdown", + "id": "c5832921", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 5" + ] + }, + { + "cell_type": "code", + "id": "ab51fa07", + "metadata": {}, + "source": [ + "equipage = {\n", + " \"Kowalski\": \"pilote\",\n", + " \"Ndiaye\": \"medecin\",\n", + " \"Reyes\": \"ingenieur\",\n", + " \"Sokolova\": \"pilote\",\n", + " \"Tanaka\": \"ingenieur\",\n", + "}\n", + "\n", + "# Plan de la station : à chaque module, la liste des modules directement accessibles\n", + "plan = {\n", + " \"Sas\": [\"Couloir\"],\n", + " \"Couloir\": [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"],\n", + " \"Cockpit\": [\"Couloir\"],\n", + " \"Soute\": [\"Couloir\", \"Reacteur\"],\n", + " \"Labo\": [\"Couloir\"],\n", + " \"Reacteur\": [\"Soute\"],\n", + "}\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "6aae023f", + "metadata": {}, + "source": [ + "## Diagnostic 5.1 : accès et absence\n", + "\n", + "Prédisez le résultat des trois premières lignes. La quatrième est volontairement\n", + "commentée : dites ce qui se passerait si on la décommentait.\n" + ] + }, + { + "cell_type": "code", + "id": "1c0f1d04", + "metadata": {}, + "source": [ + "print(equipage[\"Reyes\"])\n", + "print(len(equipage))\n", + "print(\"Bowman\" in equipage)\n", + "# print(equipage[\"Bowman\"])\n", + "\n", + "# Ma prédiction :\n", + "# ingenieur\n", + "# 5 (len d'un dictionnaire = son nombre de clés)\n", + "# False\n", + "# La 4e ligne lèverait une KeyError : accéder à une clé absente est une ERREUR,\n", + "# ce n'est pas None. Pour éviter ça : equipage.get(\"Bowman\", \"inconnu\").\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2b3a3910", + "metadata": {}, + "source": [ + "## Réparation 5.2 : le poste qui n'existe pas\n", + "\n", + "Cette fonction plante dès qu'on lui donne un nom inconnu. Corrigez-la pour qu'elle\n", + "renvoie `\"inconnu\"` dans ce cas.\n" + ] + }, + { + "cell_type": "code", + "id": "28951116", + "metadata": {}, + "source": [ + "def poste_de(registre, nom):\n", + " return registre.get(nom, \"inconnu\")\n", + "\n", + "# Variante explicite, tout aussi correcte :\n", + "# def poste_de(registre, nom):\n", + "# if nom in registre:\n", + "# return registre[nom]\n", + "# return \"inconnu\"\n", + "\n", + "print(poste_de(equipage, \"Tanaka\"))\n", + "print(poste_de(equipage, \"Bowman\"))\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "52538f19", + "metadata": {}, + "source": [ + "assert poste_de(equipage, \"Tanaka\") == \"ingenieur\"\n", + "assert poste_de(equipage, \"Bowman\") == \"inconnu\"\n", + "print(\"OK. Registre sécurisé\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "4346f061", + "metadata": {}, + "source": [ + "## Production 5.3 : compter les postes\n", + "\n", + "Écrivez `compter_postes(registre)` qui renvoie un **dictionnaire** associant à chaque\n", + "poste le nombre de membres qui l'occupent.\n", + "\n", + "Sur `equipage`, on doit obtenir : `{'pilote': 2, 'medecin': 1, 'ingenieur': 2}`\n", + "\n", + "*Méthode classique : on part d'un dictionnaire vide, et pour chaque valeur rencontrée,\n", + "soit la clé existe déjà et on l'incrémente, soit elle n'existe pas et on la crée à 1.*\n" + ] + }, + { + "cell_type": "code", + "id": "cf3aff2d", + "metadata": {}, + "source": [ + "def compter_postes(registre):\n", + " comptes = {}\n", + " for poste in registre.values():\n", + " if poste in comptes:\n", + " comptes[poste] = comptes[poste] + 1\n", + " else:\n", + " comptes[poste] = 1\n", + " return comptes\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "080dba1a", + "metadata": {}, + "source": [ + "resultat = compter_postes(equipage)\n", + "assert resultat == {\"pilote\": 2, \"medecin\": 1, \"ingenieur\": 2}\n", + "assert compter_postes({}) == {}\n", + "assert compter_postes({\"A\": \"pilote\"}) == {\"pilote\": 1}\n", + "print(\"OK. Recensement de l'équipage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "3cb9f1f6", + "metadata": {}, + "source": [ + "## Production 5.4 : lire le plan de la station\n", + "\n", + "`plan` est un dictionnaire dont **les valeurs sont des listes**.\n", + "\n", + "1. Écrivez `voisins(plan, module)` : la liste des modules directement accessibles depuis\n", + " `module`, ou `[]` si le module n'existe pas.\n", + "2. Écrivez `nb_liaisons(plan)` : le nombre **total** de liaisons listées dans le plan\n", + " (on compte simplement la longueur de toutes les listes).\n", + "\n", + "> Cette structure a un nom : c'est une **liste d'adjacence**. C'est celle que vous\n", + "> utiliserez pour représenter un graphe.\n" + ] + }, + { + "cell_type": "code", + "id": "c9bbd89e", + "metadata": {}, + "source": [ + "def voisins(plan, module):\n", + " return plan.get(module, [])\n", + "\n", + "\n", + "def nb_liaisons(plan):\n", + " total = 0\n", + " for liste_voisins in plan.values():\n", + " total = total + len(liste_voisins)\n", + " return total\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "71153a02", + "metadata": {}, + "source": [ + "assert voisins(plan, \"Cockpit\") == [\"Couloir\"]\n", + "assert voisins(plan, \"Couloir\") == [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"]\n", + "assert voisins(plan, \"Hangar\") == []\n", + "assert nb_liaisons(plan) == 10\n", + "print(\"OK. Lecture du plan opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "17b8efed", + "metadata": {}, + "source": [ + "## Bonus 5.5 : regrouper par poste\n", + "\n", + "Écrivez `par_poste(registre)` qui renvoie un dictionnaire associant à chaque poste la\n", + "**liste des noms** qui l'occupent :\n", + "\n", + "```python\n", + "{'pilote': ['Kowalski', 'Sokolova'], 'medecin': ['Ndiaye'], 'ingenieur': ['Reyes', 'Tanaka']}\n", + "```\n" + ] + }, + { + "cell_type": "code", + "id": "3ac9ea76", + "metadata": {}, + "source": [ + "def par_poste(registre):\n", + " groupes = {}\n", + " for nom, poste in registre.items():\n", + " if poste not in groupes:\n", + " groupes[poste] = []\n", + " groupes[poste].append(nom)\n", + " return groupes\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "61b6d199", + "metadata": {}, + "source": [ + "attendu = {\"pilote\": [\"Kowalski\", \"Sokolova\"],\n", + " \"medecin\": [\"Ndiaye\"],\n", + " \"ingenieur\": [\"Reyes\", \"Tanaka\"]}\n", + "assert par_poste(equipage) == attendu\n", + "assert par_poste({}) == {}\n", + "print(\"OK. Regroupement opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "1ba5316e", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 6 : SORTIE D'ORBITE\n", + "\n", + "### *Synthèse, et retour à la récursivité*\n", + "\n", + "> Tous les systèmes répondent. Reste à vérifier qu'aucun module n'est resté isolé après\n", + "> les avaries : si un compartiment n'est plus relié au reste de la station, l'équipage\n", + "> qui s'y trouve est piégé.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9ad78317", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 6" + ] + }, + { + "cell_type": "code", + "id": "c9624030", + "metadata": {}, + "source": [ + "plan = {\n", + " \"Sas\": [\"Couloir\"],\n", + " \"Couloir\": [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"],\n", + " \"Cockpit\": [\"Couloir\"],\n", + " \"Soute\": [\"Couloir\", \"Reacteur\"],\n", + " \"Labo\": [\"Couloir\"],\n", + " \"Reacteur\": [\"Soute\"],\n", + " \"Serre\": [\"Cellule\"], # compartiment isolé du reste !\n", + " \"Cellule\": [\"Serre\"],\n", + "}\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "bc3b6c0a", + "metadata": {}, + "source": [ + "## Synthèse 6.1 : quels modules sont atteignables ?\n", + "\n", + "Écrivez `atteignables(plan, depart)` : la liste des modules que l'on peut rejoindre\n", + "depuis `depart`, en comptant `depart` lui-même.\n", + "\n", + "L'algorithme vous est donné, il ne reste qu'à le traduire :\n", + "\n", + "```\n", + "a_visiter ← liste contenant seulement depart\n", + "visites ← liste vide\n", + "tant que a_visiter n'est pas vide :\n", + " module ← on retire le DERNIER élément de a_visiter\n", + " si module n'est pas déjà dans visites :\n", + " ajouter module à visites\n", + " pour chaque v dans les voisins de module :\n", + " ajouter v à a_visiter\n", + "renvoyer visites\n", + "```\n", + "\n", + "*Indice : « retirer le dernier élément » se dit `liste.pop()` en Python.*\n", + "\n", + "> Ce que vous venez d'écrire est un **parcours en profondeur**. `a_visiter` est une\n", + "> **pile**. Vous retrouverez les deux, sous ces noms, dans les chapitres Pile/File et\n", + "> Graphes.\n" + ] + }, + { + "cell_type": "code", + "id": "d47b5cbd", + "metadata": {}, + "source": [ + "def atteignables(plan, depart):\n", + " a_visiter = [depart]\n", + " visites = []\n", + " while len(a_visiter) > 0:\n", + " module = a_visiter.pop() # retire et renvoie le dernier élément\n", + " if module not in visites:\n", + " visites.append(module)\n", + " for v in plan.get(module, []):\n", + " a_visiter.append(v)\n", + " return visites\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "7f35621f", + "metadata": {}, + "source": [ + "r = atteignables(plan, \"Sas\")\n", + "assert sorted(r) == [\"Cockpit\", \"Couloir\", \"Labo\", \"Reacteur\", \"Sas\", \"Soute\"]\n", + "assert \"Serre\" not in r, \"La serre ne doit PAS être atteignable depuis le sas\"\n", + "assert sorted(atteignables(plan, \"Serre\")) == [\"Cellule\", \"Serre\"]\n", + "assert atteignables(plan, \"Hangar\") == [\"Hangar\"]\n", + "print(\"OK. Cartographie terminée, 2 compartiments isolés détectés : Serre et Cellule\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "1fb2944f", + "metadata": {}, + "source": [ + "## Diagnostic 6.2 : le compte à rebours\n", + "\n", + "Dernière étape avant le décollage. **Ne l'exécutez pas tout de suite** : écrivez d'abord,\n", + "sur votre feuille, l'ordre exact des affichages pour `compte_a_rebours(3)`.\n" + ] + }, + { + "cell_type": "code", + "id": "4c32541e", + "metadata": {}, + "source": [ + "def compte_a_rebours(n):\n", + " if n == 0:\n", + " print(\"DECOLLAGE\")\n", + " else:\n", + " print(n)\n", + " compte_a_rebours(n - 1)\n", + " print(\"verif\", n)\n", + "\n", + "compte_a_rebours(3)\n", + "\n", + "# Ma prédiction :\n", + "# 3\n", + "# 2\n", + "# 1\n", + "# DECOLLAGE\n", + "# verif 1\n", + "# verif 2\n", + "# verif 3\n", + "#\n", + "# Les « verif » sortent dans l'ORDRE INVERSE : chaque appel est mis en attente pendant\n", + "# que l'appel suivant se déroule, puis reprend là où il s'était arrêté. C'est exactement\n", + "# la PILE D'APPELS du chapitre en cours.\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "9e4a972b", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Fin du protocole\n", + "\n", + "**PERRIAND-7 est de nouveau opérationnelle.**\n", + "\n", + "Ce que vous venez de réutiliser, et où ça resservira :\n", + "\n", + "| Module | Notion | Chapitre de Terminale concerné |\n", + "|---|---|---|\n", + "| 1 · Réacteur | variables, mutabilité, aliasing | POO, listes chaînées |\n", + "| 2 · Protocoles | `return`, paramètres, portée | **Récursivité**, Modularité |\n", + "| 3 · Balayage | boucles, accumulateur | parcours d'arbres et de graphes |\n", + "| 4 · Soute | listes, listes de listes, compréhensions | structures linéaires, tri fusion |\n", + "| 5 · Équipage | dictionnaires, dict de listes | **graphes**, mémoïsation |\n", + "| 6 · Sortie d'orbite | pile, parcours, pile d'appels | Pile/File, **Récursivité** |\n", + "\n", + "---\n", + "*Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0*\n" + ] + } + ], + "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/PERRIAND-7_Reveil_des_systemes.ipynb b/Revisions_Rentree/PERRIAND-7_Reveil_des_systemes.ipynb new file mode 100644 index 0000000..8a4f4c6 --- /dev/null +++ b/Revisions_Rentree/PERRIAND-7_Reveil_des_systemes.ipynb @@ -0,0 +1,1425 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9cedca3e", + "metadata": {}, + "source": [ + "# PERRIAND-7 : protocole de réveil\n", + "\n", + "> **Journal de bord, jour 68.**\n", + "> Il y a soixante-huit jours, l'équipage de la station orbitale **PERRIAND-7** est entré\n", + "> en cryosommeil pour traverser la ceinture de radiations de Van Allen étendue.\n", + "> Vous venez d'ouvrir les yeux.\n", + ">\n", + "> La station, elle, s'est moins bien réveillée que vous. Les radiations ont provoqué des\n", + "> corruptions mémoire dans l'ordinateur de bord : plusieurs programmes affichent des\n", + "> valeurs absurdes, d'autres refusent tout simplement de s'exécuter.\n", + ">\n", + "> Vous êtes l'**ingénieur système** de bord. Cinq modules à réveiller, puis la sortie\n", + "> d'orbite. Dans l'ordre.\n", + "\n", + "---\n", + "\n", + "## Comment travailler\n", + "\n", + "Chaque module contient quatre types d'exercices :\n", + "\n", + "| Type | Ce qu'on attend de vous |\n", + "|---|---|\n", + "| **DIAGNOSTIC** | Lire le code et **prédire** ce qu'il affiche **avant** de l'exécuter. Écrivez votre prédiction dans le commentaire prévu, puis exécutez pour vérifier. |\n", + "| **RÉPARATION** | Le code est cassé. Trouvez l'erreur et corrigez-la. |\n", + "| **PRODUCTION** | Écrivez le code demandé. |\n", + "| **BONUS** | Facultatif. À faire si vous avez terminé le module. |\n", + "\n", + "**La difficulté monte.** Dans chaque module, les derniers exercices sont volontairement\n", + "plus exigeants, et le module 6 l'est encore davantage. C'est le principe d'un sujet de\n", + "bac : les dernières questions servent à voir jusqu'où vont les meilleurs. Ne pas les\n", + "terminer n'est pas un échec ; les faire, c'est du bonus.\n", + "\n", + "**Auto-correction.** Chaque exercice de production est suivi d'une cellule de tests.\n", + "Exécutez-la : si `OK` s'affiche, c'est gagné. Si vous obtenez une `AssertionError`,\n", + "c'est que le code ne fait pas encore ce qui est demandé.\n", + "\n", + "**Bloqué ?** Chaque module commence par une cellule **SECOURS** qui remet en place\n", + "toutes les données dont il a besoin. Les modules sont **indépendants** : vous pouvez\n", + "passer au suivant sans avoir fini le précédent.\n", + "\n", + "---\n", + "\n", + "*Objectif de la séance : redémarrer le moteur après deux mois d'arrêt. Rien de ce qui suit\n", + "n'est nouveau : tout a été vu en Première. Mais chaque notion révisée ici sert directement\n", + "un chapitre de Terminale.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "ac079dd1", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 1 : LE RÉACTEUR\n", + "\n", + "### *Variables, affectation, mutabilité*\n", + "\n", + "> Le réacteur tourne. C'est déjà ça. Mais les jauges du poste de pilotage affichent\n", + "> n'importe quoi : 4 % d'oxygène ici, 340 % là. Avant de réparer un seul programme,\n", + "> il faut comprendre **comment l'ordinateur de bord range ses valeurs en mémoire**.\n", + "\n", + "*Ce module prépare : POO (les attributs d'un objet sont des variables), et les listes\n", + "chaînées (où deux variables peuvent pointer vers le même maillon).*\n" + ] + }, + { + "cell_type": "markdown", + "id": "d26e115e", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 1" + ] + }, + { + "cell_type": "code", + "id": "7e59265c", + "metadata": {}, + "source": [ + "# Relevés des capteurs au réveil\n", + "TAUX_O2_NOMINAL = 21 # en pourcentage\n", + "PRESSION_NOMINALE = 101 # en kilopascals\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "12e8a187", + "metadata": {}, + "source": [ + "## Diagnostic 1.1 : l'ordre des affectations\n", + "\n", + "L'ordinateur a exécuté ces quatre lignes. **Avant d'exécuter la cellule**, écrivez ce que\n", + "vous pensez qu'elle va afficher.\n", + "\n", + "*Rappel : une affectation `a = b` calcule d'abord la valeur de droite, puis la range dans\n", + "la variable de gauche. Ce qui est calculé est calculé, ça ne se recalcule pas tout seul.*\n" + ] + }, + { + "cell_type": "code", + "id": "046ffdf2", + "metadata": {}, + "source": [ + "oxygene = 21\n", + "oxygene = oxygene - 4\n", + "pression = oxygene * 2\n", + "oxygene = 21\n", + "\n", + "print(oxygene, pression)\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "a7e98bb1", + "metadata": {}, + "source": [ + "## Diagnostic 1.2 : deux noms, une seule liste\n", + "\n", + "Celui-ci piège tout le monde. Prédisez, puis exécutez.\n" + ] + }, + { + "cell_type": "code", + "id": "e82b0b0e", + "metadata": {}, + "source": [ + "reserve_principale = [120, 80, 50]\n", + "reserve_secours = reserve_principale\n", + "reserve_secours.append(0)\n", + "\n", + "print(\"principale :\", reserve_principale)\n", + "print(\"secours :\", reserve_secours)\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2cdd1c50", + "metadata": {}, + "source": [ + "> **À retenir.** Une liste est **mutable** : on peut la modifier sur place.\n", + "> Un entier ou une chaîne ne le sont pas. C'est pour ça que le piège ci-dessus\n", + "> n'existe pas avec des entiers.\n" + ] + }, + { + "cell_type": "markdown", + "id": "7ca950ac", + "metadata": {}, + "source": [ + "## Réparation 1.3 : l'échange raté\n", + "\n", + "Le programme de bord doit **échanger** les valeurs de deux capteurs mal branchés.\n", + "Le technicien précédent a écrit ceci. Ça ne marche pas.\n", + "\n", + "**Corrigez** la cellule pour que l'échange fonctionne réellement.\n" + ] + }, + { + "cell_type": "code", + "id": "45ce169a", + "metadata": {}, + "source": [ + "temp_interne = 18\n", + "temp_externe = -270\n", + "\n", + "# --- échange (cassé) ---\n", + "temp_interne = temp_externe\n", + "temp_externe = temp_interne\n", + "# -----------------------\n", + "\n", + "print(temp_interne, temp_externe)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "738e0cdf", + "metadata": {}, + "source": [ + "assert temp_interne == -270 and temp_externe == 18\n", + "print(\"OK. Échange réussi\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "a0c630bd", + "metadata": {}, + "source": [ + "## Production 1.4 : calcul de l'autonomie\n", + "\n", + "Il reste `litres_oxygene` litres d'oxygène dans les réserves. Chaque membre d'équipage\n", + "consomme `consommation_par_heure` litres par heure.\n", + "\n", + "Calculez dans une variable **`autonomie_h`** le nombre **entier** d'heures complètes\n", + "que l'équipage peut encore tenir.\n", + "\n", + "*Indice : `//` est la division entière, celle qui ne garde que le nombre de tours complets.*\n" + ] + }, + { + "cell_type": "code", + "id": "2e94b40a", + "metadata": {}, + "source": [ + "litres_oxygene = 480\n", + "consommation_par_heure = 3\n", + "nb_membres = 4\n", + "\n", + "autonomie_h = ... # à compléter\n", + "\n", + "print(\"Autonomie :\", autonomie_h, \"heures\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "1aed1664", + "metadata": {}, + "source": [ + "assert autonomie_h == 40, \"Attendu 40, obtenu \" + str(autonomie_h)\n", + "print(\"OK. Autonomie correcte : 40 h\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "4350e6ad", + "metadata": {}, + "source": [ + "## Bonus 1.5 : copie indépendante\n", + "\n", + "Créez `sauvegarde` : une copie de `releves` qui **ne bouge pas** quand on modifie `releves`.\n" + ] + }, + { + "cell_type": "code", + "id": "40103661", + "metadata": {}, + "source": [ + "releves = [21, 101, 18]\n", + "\n", + "sauvegarde = ... # à compléter\n", + "\n", + "releves.append(999)\n", + "print(\"releves :\", releves)\n", + "print(\"sauvegarde:\", sauvegarde)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "5f75eb8a", + "metadata": {}, + "source": [ + "assert sauvegarde == [21, 101, 18]\n", + "assert releves == [21, 101, 18, 999]\n", + "print(\"OK. La sauvegarde est bien indépendante\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "303bbff3", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 2 : LES PROTOCOLES\n", + "\n", + "### *Fonctions : paramètres, `return`, portée*\n", + "\n", + "> Les protocoles de sécurité de la station sont des **fonctions**. On les appelle, elles\n", + "> répondent. Sauf que depuis les radiations, plusieurs d'entre elles ne répondent plus\n", + "> rien du tout : elles se contentent de parler dans le vide.\n", + "\n", + "*Ce module prépare : la **récursivité** (une fonction qui s'appelle elle-même n'a de\n", + "sens que si elle **renvoie** quelque chose) et la Modularité.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "63724e09", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 2" + ] + }, + { + "cell_type": "code", + "id": "511a487d", + "metadata": {}, + "source": [ + "SEUIL_CRITIQUE = 80\n", + "SEUIL_ALERTE = 95\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "72959168", + "metadata": {}, + "source": [ + "## Diagnostic 2.1 : `print` n'est pas `return`\n", + "\n", + "Prédisez les **deux** lignes affichées.\n" + ] + }, + { + "cell_type": "code", + "id": "9aec1b79", + "metadata": {}, + "source": [ + "def niveau_pression(pression):\n", + " if pression < 80:\n", + " print(\"CRITIQUE\")\n", + " else:\n", + " print(\"NOMINAL\")\n", + "\n", + "resultat = niveau_pression(45)\n", + "print(\"resultat vaut :\", resultat)\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b93de0e2", + "metadata": {}, + "source": [ + "> **À retenir.** `print` **montre** à l'humain. `return` **rend** une valeur au\n", + "> programme. Une fonction qui ne fait que `print` est un cul-de-sac : on ne peut pas\n", + "> construire quoi que ce soit par-dessus.\n" + ] + }, + { + "cell_type": "markdown", + "id": "8ba6160b", + "metadata": {}, + "source": [ + "## Réparation 2.2 : rendre le protocole utilisable\n", + "\n", + "Réécrivez `niveau_pression` pour qu'elle **renvoie** la chaîne au lieu de l'afficher.\n", + "\n", + "- pression strictement inférieure à 80 : renvoie `\"CRITIQUE\"`\n", + "- pression entre 80 (inclus) et 95 (exclu) : renvoie `\"ALERTE\"`\n", + "- pression supérieure ou égale à 95 : renvoie `\"NOMINAL\"`\n" + ] + }, + { + "cell_type": "code", + "id": "2c7c648f", + "metadata": {}, + "source": [ + "def niveau_pression(pression):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "235aaf17", + "metadata": {}, + "source": [ + "assert niveau_pression(45) == \"CRITIQUE\"\n", + "assert niveau_pression(79) == \"CRITIQUE\"\n", + "assert niveau_pression(80) == \"ALERTE\"\n", + "assert niveau_pression(94) == \"ALERTE\"\n", + "assert niveau_pression(95) == \"NOMINAL\"\n", + "assert niveau_pression(120) == \"NOMINAL\"\n", + "print(\"OK. Protocole de pression opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "df5b2857", + "metadata": {}, + "source": [ + "## Diagnostic 2.3 : la variable qui n'existe pas\n", + "\n", + "Ce code **plante**. Avant de l'exécuter, essayez de dire **pourquoi**.\n" + ] + }, + { + "cell_type": "code", + "id": "8f53752a", + "metadata": {}, + "source": [ + "compteur_alertes = 0\n", + "\n", + "def signaler_alerte():\n", + " compteur_alertes = compteur_alertes + 1\n", + " return compteur_alertes\n", + "\n", + "print(signaler_alerte())\n", + "\n", + "# Ma prédiction (quelle erreur, et pourquoi ?) :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2db401da", + "metadata": {}, + "source": [ + "**Corrigez** en transformant `signaler_alerte` en une fonction qui prend le compteur\n", + "actuel en paramètre et renvoie le compteur augmenté de 1.\n" + ] + }, + { + "cell_type": "code", + "id": "e8a6fc67", + "metadata": {}, + "source": [ + "def signaler_alerte(compteur):\n", + " ... # à compléter\n", + "\n", + "compteur_alertes = 0\n", + "compteur_alertes = signaler_alerte(compteur_alertes)\n", + "compteur_alertes = signaler_alerte(compteur_alertes)\n", + "print(compteur_alertes)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "9220f6e4", + "metadata": {}, + "source": [ + "assert signaler_alerte(0) == 1\n", + "assert signaler_alerte(41) == 42\n", + "assert compteur_alertes == 2\n", + "print(\"OK. Compteur d'alertes réparé\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "74da1f8f", + "metadata": {}, + "source": [ + "## Production 2.4 : une fonction qui en appelle une autre\n", + "\n", + "Écrivez `diagnostic(pression, temperature)` qui renvoie une chaîne de la forme :\n", + "\n", + "```\n", + "\"Pression NOMINAL / Temperature 18\"\n", + "```\n", + "\n", + "Votre fonction **doit appeler** `niveau_pression` : ne recopiez pas les tests de seuil.\n", + "\n", + "*Indice : pour coller des morceaux de texte, `+` fonctionne, mais il faut convertir les\n", + "nombres avec `str(...)`.*\n" + ] + }, + { + "cell_type": "code", + "id": "f675b126", + "metadata": {}, + "source": [ + "def diagnostic(pression, temperature):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "c808569d", + "metadata": {}, + "source": [ + "assert diagnostic(101, 18) == \"Pression NOMINAL / Temperature 18\"\n", + "assert diagnostic(45, -3) == \"Pression CRITIQUE / Temperature -3\"\n", + "assert diagnostic(88, 0) == \"Pression ALERTE / Temperature 0\"\n", + "print(\"OK. Diagnostic complet opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "f38f5656", + "metadata": {}, + "source": [ + "## Bonus 2.5 : paramètre par défaut\n", + "\n", + "Écrivez `verrouiller(module, code_acces=\"0000\")` qui renvoie :\n", + "- `\"MODULE VERROUILLE\"` si le code fourni est `\"0000\"`\n", + "- `\"CODE REFUSE\"` sinon\n" + ] + }, + { + "cell_type": "code", + "id": "9bbac347", + "metadata": {}, + "source": [ + "def verrouiller(module, code_acces=\"0000\"):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "3be9ed12", + "metadata": {}, + "source": [ + "assert verrouiller(\"Sas\") == \"MODULE VERROUILLE\"\n", + "assert verrouiller(\"Sas\", \"0000\") == \"MODULE VERROUILLE\"\n", + "assert verrouiller(\"Sas\", \"1234\") == \"CODE REFUSE\"\n", + "print(\"OK. Verrouillage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c9a85d18", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 3 : LE BALAYAGE\n", + "\n", + "### *Boucles : `for`, `while`, accumulateur*\n", + "\n", + "> Il faut maintenant **balayer** les 5 000 relevés de capteurs accumulés pendant\n", + "> l'hibernation, pour repérer les anomalies. Personne ne fera ça à la main.\n", + "\n", + "*Ce module prépare : le parcours des arbres et des graphes, où l'on visite un à un\n", + "tous les éléments d'une structure sans savoir à l'avance combien il y en a.*\n" + ] + }, + { + "cell_type": "markdown", + "id": "7558e6b5", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 3" + ] + }, + { + "cell_type": "code", + "id": "c10a8eb5", + "metadata": {}, + "source": [ + "mesures = [12, 45, 7, 88, 23, 91, 4]\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "d77cebe9", + "metadata": {}, + "source": [ + "## Diagnostic 3.1 : combien de tours ?\n", + "\n", + "Prédisez ce qui s'affiche, **et combien de lignes**.\n" + ] + }, + { + "cell_type": "code", + "id": "aa6ae61c", + "metadata": {}, + "source": [ + "i = 0\n", + "while i < 5:\n", + " print(i)\n", + " i = i + 2\n", + "\n", + "print(\"fin, i vaut\", i)\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "5c8282f2", + "metadata": {}, + "source": [ + "## Diagnostic 3.2 : NE PAS EXÉCUTER\n", + "\n", + "Cette cellule est en mode texte : **ne l'exécutez pas**, elle ne s'arrêterait jamais.\n", + "\n", + "```python\n", + "carburant = 100\n", + "while carburant > 0:\n", + " print(\"Purge en cours...\")\n", + " carburant = 100\n", + "```\n", + "\n", + "**Question (répondez dans la cellule ci-dessous) :** pourquoi cette boucle ne se\n", + "termine-t-elle jamais, et quelle est la plus petite modification qui la ferait terminer ?\n" + ] + }, + { + "cell_type": "code", + "id": "65da8f42", + "metadata": {}, + "source": [ + "# Ma réponse :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "571a3166", + "metadata": {}, + "source": [ + "## Réparation 3.3 : le capteur oublié\n", + "\n", + "Ce code doit afficher **toutes** les mesures. Il en rate une. Trouvez laquelle, et corrigez.\n" + ] + }, + { + "cell_type": "code", + "id": "1793514f", + "metadata": {}, + "source": [ + "for i in range(1, len(mesures)):\n", + " print(\"capteur\", i, \":\", mesures[i])\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "5ff70b05", + "metadata": {}, + "source": [ + "## Production 3.4 : le maximum, à la main\n", + "\n", + "Écrivez `maximum(valeurs)` qui renvoie la plus grande valeur d'une liste **non vide**,\n", + "**sans utiliser `max()`**.\n", + "\n", + "*Méthode de l'accumulateur : on retient un « meilleur pour l'instant », et on le met à\n", + "jour à chaque tour.*\n" + ] + }, + { + "cell_type": "code", + "id": "38ce42e7", + "metadata": {}, + "source": [ + "def maximum(valeurs):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "508af429", + "metadata": {}, + "source": [ + "assert maximum([12, 45, 7, 88, 23, 91, 4]) == 91\n", + "assert maximum([5]) == 5\n", + "assert maximum([3, 3, 3]) == 3\n", + "assert maximum([-10, -3, -50]) == -3, \"attention aux valeurs négatives : ne partez pas de 0\"\n", + "print(\"OK. Détection du maximum opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "bb2ed90c", + "metadata": {}, + "source": [ + "## Production 3.5 : le premier capteur défaillant\n", + "\n", + "Écrivez `premier_defaillant(valeurs, seuil)` qui renvoie **l'indice** de la première\n", + "mesure strictement inférieure à `seuil`, et `-1` si aucune ne l'est.\n", + "\n", + "*Indice : ici on a besoin de l'indice, donc de `range(len(valeurs))`. Et on peut sortir\n", + "de la fonction dès qu'on a trouvé.*\n" + ] + }, + { + "cell_type": "code", + "id": "c8e95e62", + "metadata": {}, + "source": [ + "def premier_defaillant(valeurs, seuil):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "b1ef7f88", + "metadata": {}, + "source": [ + "assert premier_defaillant([12, 45, 7, 88], 20) == 0\n", + "assert premier_defaillant([45, 88, 7, 4], 10) == 2\n", + "assert premier_defaillant([45, 88, 91], 10) == -1\n", + "assert premier_defaillant([], 10) == -1\n", + "print(\"OK. Détection de défaillance opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2fbc699e", + "metadata": {}, + "source": [ + "## Bonus 3.6 : la sentinelle\n", + "\n", + "Le capteur d'altitude envoie ses relevés en continu et termine sa transmission par la\n", + "valeur `-1`. Écrivez `somme_jusqu_a_sentinelle(flux)` qui additionne les valeurs\n", + "**jusqu'au premier `-1` exclu**. Les valeurs après le `-1` sont du bruit : on les ignore.\n", + "\n", + "*Indice : un `while` avec deux conditions est plus naturel qu'un `for` ici.*\n" + ] + }, + { + "cell_type": "code", + "id": "db64a7cd", + "metadata": {}, + "source": [ + "def somme_jusqu_a_sentinelle(flux):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "c2dee877", + "metadata": {}, + "source": [ + "assert somme_jusqu_a_sentinelle([10, 20, 30, -1, 999, 999]) == 60\n", + "assert somme_jusqu_a_sentinelle([-1, 5, 5]) == 0\n", + "assert somme_jusqu_a_sentinelle([1, 2, 3]) == 6\n", + "assert somme_jusqu_a_sentinelle([]) == 0\n", + "print(\"OK. Lecture du flux opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "019e79d6", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 4 : LA SOUTE\n", + "\n", + "### *Listes : indices, tranches, listes de listes*\n", + "\n", + "> La soute est organisée en rayonnages. L'inventaire numérique a survécu, mais les\n", + "> programmes qui l'exploitent, non.\n", + "\n", + "*Ce module prépare : les structures linéaires (pile, file, listes chaînées) et le\n", + "tri fusion du chapitre « diviser pour régner ».*\n" + ] + }, + { + "cell_type": "markdown", + "id": "f55cbffb", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 4" + ] + }, + { + "cell_type": "code", + "id": "77c49e75", + "metadata": {}, + "source": [ + "soute = [\"eau\", \"vivres\", \"outils\", \"piles\", \"filtres\"]\n", + "\n", + "# Chaque sous-liste est un rayonnage : [rayon_0, rayon_1, rayon_2]\n", + "# Chaque ligne = un étage de la soute.\n", + "stock = [[12, 5, 0],\n", + " [3, 8, 7],\n", + " [0, 0, 4],\n", + " [9, 1, 2]]\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c557dcd6", + "metadata": {}, + "source": [ + "## Diagnostic 4.1 : indices et tranches\n", + "\n", + "Prédisez les quatre affichages.\n" + ] + }, + { + "cell_type": "code", + "id": "afd7698a", + "metadata": {}, + "source": [ + "print(soute[0])\n", + "print(soute[-1])\n", + "print(soute[1:3])\n", + "print(len(soute[1:3]))\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "722fe0e0", + "metadata": {}, + "source": [ + "## Réparation 4.2 : le bug du technicien\n", + "\n", + "Ce code doit retirer de `soute` tout ce qui commence par la lettre `\"p\"`.\n", + "Il en oublie. **Exécutez-le d'abord** pour constater le problème, puis corrigez-le.\n", + "\n", + "*Indice : modifier une liste pendant qu'on la parcourt décale les indices sous les pieds\n", + "de la boucle. La solution habituelle est de **construire une nouvelle liste** plutôt que\n", + "de retirer dans l'ancienne.*\n" + ] + }, + { + "cell_type": "code", + "id": "eb031359", + "metadata": {}, + "source": [ + "soute_test = [\"piles\", \"pinces\", \"eau\", \"outils\"]\n", + "\n", + "for objet in soute_test:\n", + " if objet[0] == \"p\":\n", + " soute_test.remove(objet)\n", + "\n", + "print(soute_test) # devrait afficher ['eau', 'outils']\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "9eb6ad74", + "metadata": {}, + "source": [ + "assert soute_test == [\"eau\", \"outils\"]\n", + "print(\"OK. Purge de la soute correcte\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "3e7af438", + "metadata": {}, + "source": [ + "## Production 4.3 : total d'un rayonnage\n", + "\n", + "`stock` est une **liste de listes** : `stock[i][j]` est la quantité stockée à l'étage `i`,\n", + "rayon `j`.\n", + "\n", + "Écrivez `total_rayon(grille, r)` qui renvoie la somme des quantités du rayon `r`,\n", + "**tous étages confondus**.\n", + "\n", + "*Attention : le rayon `r` est une **colonne**, pas une ligne. Il faut parcourir les étages.*\n" + ] + }, + { + "cell_type": "code", + "id": "032922c5", + "metadata": {}, + "source": [ + "def total_rayon(grille, r):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "3fbf18ee", + "metadata": {}, + "source": [ + "assert total_rayon(stock, 0) == 24\n", + "assert total_rayon(stock, 1) == 14\n", + "assert total_rayon(stock, 2) == 13\n", + "print(\"OK. Inventaire par rayon opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "ebf325de", + "metadata": {}, + "source": [ + "## Production 4.4 : filtrer en une ligne\n", + "\n", + "Écrivez `au_dessus(valeurs, seuil)` qui renvoie la liste des valeurs strictement\n", + "supérieures à `seuil`, **en utilisant une compréhension de liste**.\n", + "\n", + "*Rappel de la forme : `[expression for element in liste if condition]`*\n" + ] + }, + { + "cell_type": "code", + "id": "edde708e", + "metadata": {}, + "source": [ + "def au_dessus(valeurs, seuil):\n", + " ... # à compléter (une seule ligne, avec une compréhension)\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "4f84985e", + "metadata": {}, + "source": [ + "assert au_dessus([12, 45, 7, 88, 23], 20) == [45, 88, 23]\n", + "assert au_dessus([1, 2, 3], 10) == []\n", + "assert au_dessus([], 0) == []\n", + "print(\"OK. Filtrage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "1cb952eb", + "metadata": {}, + "source": [ + "## Bonus 4.5 : fusionner deux listes triées\n", + "\n", + "Deux capteurs ont enregistré des relevés, chacun **déjà trié dans l'ordre croissant**.\n", + "Écrivez `fusion(a, b)` qui renvoie une seule liste triée contenant tous les éléments,\n", + "**sans utiliser `sorted()` ni `.sort()`**.\n", + "\n", + "*Méthode : deux indices qui avancent, on prend à chaque tour le plus petit des deux\n", + "éléments courants.*\n", + "\n", + "> Cet exercice est **exactement** la seconde moitié du tri fusion, que vous verrez\n", + "> dans le chapitre « diviser pour régner ».\n" + ] + }, + { + "cell_type": "code", + "id": "f90d480c", + "metadata": {}, + "source": [ + "def fusion(a, b):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "5c804e8a", + "metadata": {}, + "source": [ + "assert fusion([1, 4, 9], [2, 3, 10]) == [1, 2, 3, 4, 9, 10]\n", + "assert fusion([], [2, 3]) == [2, 3]\n", + "assert fusion([5], []) == [5]\n", + "assert fusion([1, 1], [1]) == [1, 1, 1]\n", + "print(\"OK. Fusion opérationnelle, vous venez d'écrire la moitié d'un tri fusion\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b8c12ccc", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 5 : L'ÉQUIPAGE\n", + "\n", + "### *Dictionnaires : clés, valeurs, parcours*\n", + "\n", + "> Reste le plus important : les **dossiers de l'équipage** et le **plan de la station**.\n", + "> Une liste ne suffit plus : on ne cherche pas « le 3ᵉ membre », on cherche « Kowalski ».\n", + "\n", + "*Ce module prépare : les graphes (un graphe se représente par un dictionnaire de\n", + "listes) et la mémoïsation en programmation dynamique (un dictionnaire de résultats\n", + "déjà calculés).*\n" + ] + }, + { + "cell_type": "markdown", + "id": "4bf29dc6", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 5" + ] + }, + { + "cell_type": "code", + "id": "3d3ad4e9", + "metadata": {}, + "source": [ + "equipage = {\n", + " \"Kowalski\": \"pilote\",\n", + " \"Ndiaye\": \"medecin\",\n", + " \"Reyes\": \"ingenieur\",\n", + " \"Sokolova\": \"pilote\",\n", + " \"Tanaka\": \"ingenieur\",\n", + "}\n", + "\n", + "# Plan de la station : à chaque module, la liste des modules directement accessibles\n", + "plan = {\n", + " \"Sas\": [\"Couloir\"],\n", + " \"Couloir\": [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"],\n", + " \"Cockpit\": [\"Couloir\"],\n", + " \"Soute\": [\"Couloir\", \"Reacteur\"],\n", + " \"Labo\": [\"Couloir\"],\n", + " \"Reacteur\": [\"Soute\"],\n", + "}\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "15df7fc9", + "metadata": {}, + "source": [ + "## Diagnostic 5.1 : accès et absence\n", + "\n", + "Prédisez le résultat des trois premières lignes. La quatrième est volontairement\n", + "commentée : dites ce qui se passerait si on la décommentait.\n" + ] + }, + { + "cell_type": "code", + "id": "2b5fd78b", + "metadata": {}, + "source": [ + "print(equipage[\"Reyes\"])\n", + "print(len(equipage))\n", + "print(\"Bowman\" in equipage)\n", + "# print(equipage[\"Bowman\"])\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "2b5ffaac", + "metadata": {}, + "source": [ + "## Réparation 5.2 : le poste qui n'existe pas\n", + "\n", + "Cette fonction plante dès qu'on lui donne un nom inconnu. Corrigez-la pour qu'elle\n", + "renvoie `\"inconnu\"` dans ce cas.\n" + ] + }, + { + "cell_type": "code", + "id": "a84b9a81", + "metadata": {}, + "source": [ + "def poste_de(registre, nom):\n", + " return registre[nom]\n", + "\n", + "print(poste_de(equipage, \"Tanaka\"))\n", + "print(poste_de(equipage, \"Bowman\")) # plante\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "fdbbebf4", + "metadata": {}, + "source": [ + "assert poste_de(equipage, \"Tanaka\") == \"ingenieur\"\n", + "assert poste_de(equipage, \"Bowman\") == \"inconnu\"\n", + "print(\"OK. Registre sécurisé\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "c7226646", + "metadata": {}, + "source": [ + "## Production 5.3 : compter les postes\n", + "\n", + "Écrivez `compter_postes(registre)` qui renvoie un **dictionnaire** associant à chaque\n", + "poste le nombre de membres qui l'occupent.\n", + "\n", + "Sur `equipage`, on doit obtenir : `{'pilote': 2, 'medecin': 1, 'ingenieur': 2}`\n", + "\n", + "*Méthode classique : on part d'un dictionnaire vide, et pour chaque valeur rencontrée,\n", + "soit la clé existe déjà et on l'incrémente, soit elle n'existe pas et on la crée à 1.*\n" + ] + }, + { + "cell_type": "code", + "id": "bfd2eb9b", + "metadata": {}, + "source": [ + "def compter_postes(registre):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "066e51a5", + "metadata": {}, + "source": [ + "resultat = compter_postes(equipage)\n", + "assert resultat == {\"pilote\": 2, \"medecin\": 1, \"ingenieur\": 2}\n", + "assert compter_postes({}) == {}\n", + "assert compter_postes({\"A\": \"pilote\"}) == {\"pilote\": 1}\n", + "print(\"OK. Recensement de l'équipage opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "156a9f11", + "metadata": {}, + "source": [ + "## Production 5.4 : lire le plan de la station\n", + "\n", + "`plan` est un dictionnaire dont **les valeurs sont des listes**.\n", + "\n", + "1. Écrivez `voisins(plan, module)` : la liste des modules directement accessibles depuis\n", + " `module`, ou `[]` si le module n'existe pas.\n", + "2. Écrivez `nb_liaisons(plan)` : le nombre **total** de liaisons listées dans le plan\n", + " (on compte simplement la longueur de toutes les listes).\n", + "\n", + "> Cette structure a un nom : c'est une **liste d'adjacence**. C'est celle que vous\n", + "> utiliserez pour représenter un graphe.\n" + ] + }, + { + "cell_type": "code", + "id": "471df698", + "metadata": {}, + "source": [ + "def voisins(plan, module):\n", + " ... # à compléter\n", + "\n", + "\n", + "def nb_liaisons(plan):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "97c73d89", + "metadata": {}, + "source": [ + "assert voisins(plan, \"Cockpit\") == [\"Couloir\"]\n", + "assert voisins(plan, \"Couloir\") == [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"]\n", + "assert voisins(plan, \"Hangar\") == []\n", + "assert nb_liaisons(plan) == 10\n", + "print(\"OK. Lecture du plan opérationnelle\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "cb36253a", + "metadata": {}, + "source": [ + "## Bonus 5.5 : regrouper par poste\n", + "\n", + "Écrivez `par_poste(registre)` qui renvoie un dictionnaire associant à chaque poste la\n", + "**liste des noms** qui l'occupent :\n", + "\n", + "```python\n", + "{'pilote': ['Kowalski', 'Sokolova'], 'medecin': ['Ndiaye'], 'ingenieur': ['Reyes', 'Tanaka']}\n", + "```\n" + ] + }, + { + "cell_type": "code", + "id": "3562ccb9", + "metadata": {}, + "source": [ + "def par_poste(registre):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "3b7e95fe", + "metadata": {}, + "source": [ + "attendu = {\"pilote\": [\"Kowalski\", \"Sokolova\"],\n", + " \"medecin\": [\"Ndiaye\"],\n", + " \"ingenieur\": [\"Reyes\", \"Tanaka\"]}\n", + "assert par_poste(equipage) == attendu\n", + "assert par_poste({}) == {}\n", + "print(\"OK. Regroupement opérationnel\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "5fb7dd42", + "metadata": {}, + "source": [ + "---\n", + "\n", + "# Module 6 : SORTIE D'ORBITE\n", + "\n", + "### *Synthèse, et retour à la récursivité*\n", + "\n", + "> Tous les systèmes répondent. Reste à vérifier qu'aucun module n'est resté isolé après\n", + "> les avaries : si un compartiment n'est plus relié au reste de la station, l'équipage\n", + "> qui s'y trouve est piégé.\n" + ] + }, + { + "cell_type": "markdown", + "id": "ce52365e", + "metadata": {}, + "source": [ + "### SECOURS : exécutez cette cellule pour démarrer le module 6" + ] + }, + { + "cell_type": "code", + "id": "03c291e7", + "metadata": {}, + "source": [ + "plan = {\n", + " \"Sas\": [\"Couloir\"],\n", + " \"Couloir\": [\"Sas\", \"Cockpit\", \"Soute\", \"Labo\"],\n", + " \"Cockpit\": [\"Couloir\"],\n", + " \"Soute\": [\"Couloir\", \"Reacteur\"],\n", + " \"Labo\": [\"Couloir\"],\n", + " \"Reacteur\": [\"Soute\"],\n", + " \"Serre\": [\"Cellule\"], # compartiment isolé du reste !\n", + " \"Cellule\": [\"Serre\"],\n", + "}\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "b0fc880b", + "metadata": {}, + "source": [ + "## Synthèse 6.1 : quels modules sont atteignables ?\n", + "\n", + "Écrivez `atteignables(plan, depart)` : la liste des modules que l'on peut rejoindre\n", + "depuis `depart`, en comptant `depart` lui-même.\n", + "\n", + "L'algorithme vous est donné, il ne reste qu'à le traduire :\n", + "\n", + "```\n", + "a_visiter ← liste contenant seulement depart\n", + "visites ← liste vide\n", + "tant que a_visiter n'est pas vide :\n", + " module ← on retire le DERNIER élément de a_visiter\n", + " si module n'est pas déjà dans visites :\n", + " ajouter module à visites\n", + " pour chaque v dans les voisins de module :\n", + " ajouter v à a_visiter\n", + "renvoyer visites\n", + "```\n", + "\n", + "*Indice : « retirer le dernier élément » se dit `liste.pop()` en Python.*\n", + "\n", + "> Ce que vous venez d'écrire est un **parcours en profondeur**. `a_visiter` est une\n", + "> **pile**. Vous retrouverez les deux, sous ces noms, dans les chapitres Pile/File et\n", + "> Graphes.\n" + ] + }, + { + "cell_type": "code", + "id": "10f0f01a", + "metadata": {}, + "source": [ + "def atteignables(plan, depart):\n", + " ... # à compléter\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "id": "cbb4d921", + "metadata": {}, + "source": [ + "r = atteignables(plan, \"Sas\")\n", + "assert sorted(r) == [\"Cockpit\", \"Couloir\", \"Labo\", \"Reacteur\", \"Sas\", \"Soute\"]\n", + "assert \"Serre\" not in r, \"La serre ne doit PAS être atteignable depuis le sas\"\n", + "assert sorted(atteignables(plan, \"Serre\")) == [\"Cellule\", \"Serre\"]\n", + "assert atteignables(plan, \"Hangar\") == [\"Hangar\"]\n", + "print(\"OK. Cartographie terminée, 2 compartiments isolés détectés : Serre et Cellule\")\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "8d802304", + "metadata": {}, + "source": [ + "## Diagnostic 6.2 : le compte à rebours\n", + "\n", + "Dernière étape avant le décollage. **Ne l'exécutez pas tout de suite** : écrivez d'abord,\n", + "sur votre feuille, l'ordre exact des affichages pour `compte_a_rebours(3)`.\n" + ] + }, + { + "cell_type": "code", + "id": "a7f1d995", + "metadata": {}, + "source": [ + "def compte_a_rebours(n):\n", + " if n == 0:\n", + " print(\"DECOLLAGE\")\n", + " else:\n", + " print(n)\n", + " compte_a_rebours(n - 1)\n", + " print(\"verif\", n)\n", + "\n", + "compte_a_rebours(3)\n", + "\n", + "# Ma prédiction :\n" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "id": "7f208932", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Fin du protocole\n", + "\n", + "**PERRIAND-7 est de nouveau opérationnelle.**\n", + "\n", + "Ce que vous venez de réutiliser, et où ça resservira :\n", + "\n", + "| Module | Notion | Chapitre de Terminale concerné |\n", + "|---|---|---|\n", + "| 1 · Réacteur | variables, mutabilité, aliasing | POO, listes chaînées |\n", + "| 2 · Protocoles | `return`, paramètres, portée | **Récursivité**, Modularité |\n", + "| 3 · Balayage | boucles, accumulateur | parcours d'arbres et de graphes |\n", + "| 4 · Soute | listes, listes de listes, compréhensions | structures linéaires, tri fusion |\n", + "| 5 · Équipage | dictionnaires, dict de listes | **graphes**, mémoïsation |\n", + "| 6 · Sortie d'orbite | pile, parcours, pile d'appels | Pile/File, **Récursivité** |\n", + "\n", + "---\n", + "*Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0*\n" + ] + } + ], + "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/README.md b/Revisions_Rentree/README.md new file mode 100644 index 0000000..cb2a6e5 --- /dev/null +++ b/Revisions_Rentree/README.md @@ -0,0 +1,34 @@ +# Révisions de rentrée : PERRIAND-7 + +Activité de reprise, à faire en début d'année de Terminale. + +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. + +## Ce qu'on révise + +| 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 | + +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. + +## Le notebook + +[PERRIAND-7_Reveil_des_systemes.ipynb](PERRIAND-7_Reveil_des_systemes.ipynb) + +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é. + +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. + +--- +*Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0* diff --git a/Revisions_Rentree/_generateur.py b/Revisions_Rentree/_generateur.py new file mode 100644 index 0000000..ae2e223 --- /dev/null +++ b/Revisions_Rentree/_generateur.py @@ -0,0 +1,1094 @@ +# -*- coding: utf-8 -*- +"""Génère les deux notebooks PERRIAND-7 (élève + corrigé).""" +import json, os, uuid + +CELLS = [] + + +def md(src): + CELLS.append(("markdown", src, src)) + + +def code(eleve, corrige=None): + CELLS.append(("code", eleve, corrige if corrige is not None else eleve)) + + +# ---------------------------------------------------------------- INTRO +md("""# PERRIAND-7 : protocole de réveil + +> **Journal de bord, jour 68.** +> Il y a soixante-huit jours, l'équipage de la station orbitale **PERRIAND-7** est entré +> en cryosommeil pour traverser la ceinture de radiations de Van Allen étendue. +> Vous venez d'ouvrir les yeux. +> +> La station, elle, s'est moins bien réveillée que vous. Les radiations ont provoqué des +> corruptions mémoire dans l'ordinateur de bord : plusieurs programmes affichent des +> valeurs absurdes, d'autres refusent tout simplement de s'exécuter. +> +> Vous êtes l'**ingénieur système** de bord. Cinq modules à réveiller, puis la sortie +> d'orbite. Dans l'ordre. + +--- + +## Comment travailler + +Chaque module contient quatre types d'exercices : + +| Type | Ce qu'on attend de vous | +|---|---| +| **DIAGNOSTIC** | Lire le code et **prédire** ce qu'il affiche **avant** de l'exécuter. Écrivez votre prédiction dans le commentaire prévu, puis exécutez pour vérifier. | +| **RÉPARATION** | Le code est cassé. Trouvez l'erreur et corrigez-la. | +| **PRODUCTION** | Écrivez le code demandé. | +| **BONUS** | Facultatif. À faire si vous avez terminé le module. | + +**La difficulté monte.** Dans chaque module, les derniers exercices sont volontairement +plus exigeants, et le module 6 l'est encore davantage. C'est le principe d'un sujet de +bac : les dernières questions servent à voir jusqu'où vont les meilleurs. Ne pas les +terminer n'est pas un échec ; les faire, c'est du bonus. + +**Auto-correction.** Chaque exercice de production est suivi d'une cellule de tests. +Exécutez-la : si `OK` s'affiche, c'est gagné. Si vous obtenez une `AssertionError`, +c'est que le code ne fait pas encore ce qui est demandé. + +**Bloqué ?** Chaque module commence par une cellule **SECOURS** qui remet en place +toutes les données dont il a besoin. Les modules sont **indépendants** : vous pouvez +passer au suivant sans avoir fini le précédent. + +--- + +*Objectif de la séance : redémarrer le moteur après deux mois d'arrêt. Rien de ce qui suit +n'est nouveau : tout a été vu en Première. Mais chaque notion révisée ici sert directement +un chapitre de Terminale.* +""") + +# ---------------------------------------------------------------- MODULE 1 +md("""--- + +# Module 1 : LE RÉACTEUR + +### *Variables, affectation, mutabilité* + +> Le réacteur tourne. C'est déjà ça. Mais les jauges du poste de pilotage affichent +> n'importe quoi : 4 % d'oxygène ici, 340 % là. Avant de réparer un seul programme, +> il faut comprendre **comment l'ordinateur de bord range ses valeurs en mémoire**. + +*Ce module prépare : POO (les attributs d'un objet sont des variables), et les listes +chaînées (où deux variables peuvent pointer vers le même maillon).* +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 1") +code("""# Relevés des capteurs au réveil +TAUX_O2_NOMINAL = 21 # en pourcentage +PRESSION_NOMINALE = 101 # en kilopascals +""") + +md("""## Diagnostic 1.1 : l'ordre des affectations + +L'ordinateur a exécuté ces quatre lignes. **Avant d'exécuter la cellule**, écrivez ce que +vous pensez qu'elle va afficher. + +*Rappel : une affectation `a = b` calcule d'abord la valeur de droite, puis la range dans +la variable de gauche. Ce qui est calculé est calculé, ça ne se recalcule pas tout seul.* +""") +code("""oxygene = 21 +oxygene = oxygene - 4 +pression = oxygene * 2 +oxygene = 21 + +print(oxygene, pression) + +# Ma prédiction : +""", +"""oxygene = 21 +oxygene = oxygene - 4 +pression = oxygene * 2 +oxygene = 21 + +print(oxygene, pression) + +# Ma prédiction : 21 34 +# La ligne 3 a utilisé la valeur d'oxygene à cet instant (17), pas sa valeur finale. +# pression vaut donc 34 et ne « suit » pas les changements ultérieurs d'oxygene. +""") + +md("""## Diagnostic 1.2 : deux noms, une seule liste + +Celui-ci piège tout le monde. Prédisez, puis exécutez. +""") +code("""reserve_principale = [120, 80, 50] +reserve_secours = reserve_principale +reserve_secours.append(0) + +print("principale :", reserve_principale) +print("secours :", reserve_secours) + +# Ma prédiction : +""", +"""reserve_principale = [120, 80, 50] +reserve_secours = reserve_principale +reserve_secours.append(0) + +print("principale :", reserve_principale) +print("secours :", reserve_secours) + +# Ma prédiction : les deux affichent [120, 80, 50, 0] +# `reserve_secours = reserve_principale` ne copie PAS la liste : elle donne un +# deuxième nom à la même liste en mémoire. Modifier l'une modifie l'autre. +# Pour obtenir une vraie copie : reserve_secours = list(reserve_principale) +""") + +md("""> **À retenir.** Une liste est **mutable** : on peut la modifier sur place. +> Un entier ou une chaîne ne le sont pas. C'est pour ça que le piège ci-dessus +> n'existe pas avec des entiers. +""") + +md("""## Réparation 1.3 : l'échange raté + +Le programme de bord doit **échanger** les valeurs de deux capteurs mal branchés. +Le technicien précédent a écrit ceci. Ça ne marche pas. + +**Corrigez** la cellule pour que l'échange fonctionne réellement. +""") +code("""temp_interne = 18 +temp_externe = -270 + +# --- échange (cassé) --- +temp_interne = temp_externe +temp_externe = temp_interne +# ----------------------- + +print(temp_interne, temp_externe) +""", +"""temp_interne = 18 +temp_externe = -270 + +# --- échange (corrigé) --- +temp_interne, temp_externe = temp_externe, temp_interne +# Variante sans affectation multiple, avec une variable tampon : +# tampon = temp_interne +# temp_interne = temp_externe +# temp_externe = tampon +# ------------------------- + +print(temp_interne, temp_externe) +""") +code("""assert temp_interne == -270 and temp_externe == 18 +print("OK. Échange réussi") +""") + +md("""## Production 1.4 : calcul de l'autonomie + +Il reste `litres_oxygene` litres d'oxygène dans les réserves. Chaque membre d'équipage +consomme `consommation_par_heure` litres par heure. + +Calculez dans une variable **`autonomie_h`** le nombre **entier** d'heures complètes +que l'équipage peut encore tenir. + +*Indice : `//` est la division entière, celle qui ne garde que le nombre de tours complets.* +""") +code("""litres_oxygene = 480 +consommation_par_heure = 3 +nb_membres = 4 + +autonomie_h = ... # à compléter + +print("Autonomie :", autonomie_h, "heures") +""", +"""litres_oxygene = 480 +consommation_par_heure = 3 +nb_membres = 4 + +autonomie_h = litres_oxygene // (consommation_par_heure * nb_membres) + +print("Autonomie :", autonomie_h, "heures") +""") +code("""assert autonomie_h == 40, "Attendu 40, obtenu " + str(autonomie_h) +print("OK. Autonomie correcte : 40 h") +""") + +md("""## Bonus 1.5 : copie indépendante + +Créez `sauvegarde` : une copie de `releves` qui **ne bouge pas** quand on modifie `releves`. +""") +code("""releves = [21, 101, 18] + +sauvegarde = ... # à compléter + +releves.append(999) +print("releves :", releves) +print("sauvegarde:", sauvegarde) +""", +"""releves = [21, 101, 18] + +sauvegarde = list(releves) # ou releves[:] , ou releves.copy() + +releves.append(999) +print("releves :", releves) +print("sauvegarde:", sauvegarde) +""") +code("""assert sauvegarde == [21, 101, 18] +assert releves == [21, 101, 18, 999] +print("OK. La sauvegarde est bien indépendante") +""") + +# ---------------------------------------------------------------- MODULE 2 +md("""--- + +# Module 2 : LES PROTOCOLES + +### *Fonctions : paramètres, `return`, portée* + +> Les protocoles de sécurité de la station sont des **fonctions**. On les appelle, elles +> répondent. Sauf que depuis les radiations, plusieurs d'entre elles ne répondent plus +> rien du tout : elles se contentent de parler dans le vide. + +*Ce module prépare : la **récursivité** (une fonction qui s'appelle elle-même n'a de +sens que si elle **renvoie** quelque chose) et la Modularité.* +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 2") +code("""SEUIL_CRITIQUE = 80 +SEUIL_ALERTE = 95 +""") + +md("""## Diagnostic 2.1 : `print` n'est pas `return` + +Prédisez les **deux** lignes affichées. +""") +code("""def niveau_pression(pression): + if pression < 80: + print("CRITIQUE") + else: + print("NOMINAL") + +resultat = niveau_pression(45) +print("resultat vaut :", resultat) + +# Ma prédiction : +""", +"""def niveau_pression(pression): + if pression < 80: + print("CRITIQUE") + else: + print("NOMINAL") + +resultat = niveau_pression(45) +print("resultat vaut :", resultat) + +# Ma prédiction : CRITIQUE puis resultat vaut : None +# La fonction AFFICHE mais ne RENVOIE rien. Une fonction sans return renvoie None. +# On ne peut donc rien faire du résultat : ni le tester, ni le stocker, ni le réutiliser. +""") + +md("""> **À retenir.** `print` **montre** à l'humain. `return` **rend** une valeur au +> programme. Une fonction qui ne fait que `print` est un cul-de-sac : on ne peut pas +> construire quoi que ce soit par-dessus. +""") + +md("""## Réparation 2.2 : rendre le protocole utilisable + +Réécrivez `niveau_pression` pour qu'elle **renvoie** la chaîne au lieu de l'afficher. + +- pression strictement inférieure à 80 : renvoie `"CRITIQUE"` +- pression entre 80 (inclus) et 95 (exclu) : renvoie `"ALERTE"` +- pression supérieure ou égale à 95 : renvoie `"NOMINAL"` +""") +code("""def niveau_pression(pression): + ... # à compléter +""", +"""def niveau_pression(pression): + if pression < 80: + return "CRITIQUE" + elif pression < 95: + return "ALERTE" + else: + return "NOMINAL" +""") +code("""assert niveau_pression(45) == "CRITIQUE" +assert niveau_pression(79) == "CRITIQUE" +assert niveau_pression(80) == "ALERTE" +assert niveau_pression(94) == "ALERTE" +assert niveau_pression(95) == "NOMINAL" +assert niveau_pression(120) == "NOMINAL" +print("OK. Protocole de pression opérationnel") +""") + +md("""## Diagnostic 2.3 : la variable qui n'existe pas + +Ce code **plante**. Avant de l'exécuter, essayez de dire **pourquoi**. +""") +code("""compteur_alertes = 0 + +def signaler_alerte(): + compteur_alertes = compteur_alertes + 1 + return compteur_alertes + +print(signaler_alerte()) + +# Ma prédiction (quelle erreur, et pourquoi ?) : +""", +"""compteur_alertes = 0 + +def signaler_alerte(): + compteur_alertes = compteur_alertes + 1 + return compteur_alertes + +print(signaler_alerte()) + +# Ma prédiction : UnboundLocalError. +# Comme `compteur_alertes` reçoit une affectation DANS la fonction, Python la considère +# comme une variable LOCALE. Au moment du calcul `compteur_alertes + 1`, cette variable +# locale n'a encore aucune valeur : erreur. +# La bonne solution n'est PAS `global` : c'est de passer la valeur en PARAMÈTRE +# et de renvoyer la nouvelle (voir cellule suivante). +""") + +md("""**Corrigez** en transformant `signaler_alerte` en une fonction qui prend le compteur +actuel en paramètre et renvoie le compteur augmenté de 1. +""") +code("""def signaler_alerte(compteur): + ... # à compléter + +compteur_alertes = 0 +compteur_alertes = signaler_alerte(compteur_alertes) +compteur_alertes = signaler_alerte(compteur_alertes) +print(compteur_alertes) +""", +"""def signaler_alerte(compteur): + return compteur + 1 + +compteur_alertes = 0 +compteur_alertes = signaler_alerte(compteur_alertes) +compteur_alertes = signaler_alerte(compteur_alertes) +print(compteur_alertes) +""") +code("""assert signaler_alerte(0) == 1 +assert signaler_alerte(41) == 42 +assert compteur_alertes == 2 +print("OK. Compteur d'alertes réparé") +""") + +md("""## Production 2.4 : une fonction qui en appelle une autre + +Écrivez `diagnostic(pression, temperature)` qui renvoie une chaîne de la forme : + +``` +"Pression NOMINAL / Temperature 18" +``` + +Votre fonction **doit appeler** `niveau_pression` : ne recopiez pas les tests de seuil. + +*Indice : pour coller des morceaux de texte, `+` fonctionne, mais il faut convertir les +nombres avec `str(...)`.* +""") +code("""def diagnostic(pression, temperature): + ... # à compléter +""", +"""def diagnostic(pression, temperature): + return "Pression " + niveau_pression(pression) + " / Temperature " + str(temperature) +""") +code("""assert diagnostic(101, 18) == "Pression NOMINAL / Temperature 18" +assert diagnostic(45, -3) == "Pression CRITIQUE / Temperature -3" +assert diagnostic(88, 0) == "Pression ALERTE / Temperature 0" +print("OK. Diagnostic complet opérationnel") +""") + +md("""## Bonus 2.5 : paramètre par défaut + +Écrivez `verrouiller(module, code_acces="0000")` qui renvoie : +- `"MODULE VERROUILLE"` si le code fourni est `"0000"` +- `"CODE REFUSE"` sinon +""") +code("""def verrouiller(module, code_acces="0000"): + ... # à compléter +""", +"""def verrouiller(module, code_acces="0000"): + if code_acces == "0000": + return "MODULE VERROUILLE" + else: + return "CODE REFUSE" +""") +code("""assert verrouiller("Sas") == "MODULE VERROUILLE" +assert verrouiller("Sas", "0000") == "MODULE VERROUILLE" +assert verrouiller("Sas", "1234") == "CODE REFUSE" +print("OK. Verrouillage opérationnel") +""") + +# ---------------------------------------------------------------- MODULE 3 +md("""--- + +# Module 3 : LE BALAYAGE + +### *Boucles : `for`, `while`, accumulateur* + +> Il faut maintenant **balayer** les 5 000 relevés de capteurs accumulés pendant +> l'hibernation, pour repérer les anomalies. Personne ne fera ça à la main. + +*Ce module prépare : le parcours des arbres et des graphes, où l'on visite un à un +tous les éléments d'une structure sans savoir à l'avance combien il y en a.* +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 3") +code("""mesures = [12, 45, 7, 88, 23, 91, 4] +""") + +md("""## Diagnostic 3.1 : combien de tours ? + +Prédisez ce qui s'affiche, **et combien de lignes**. +""") +code("""i = 0 +while i < 5: + print(i) + i = i + 2 + +print("fin, i vaut", i) + +# Ma prédiction : +""", +"""i = 0 +while i < 5: + print(i) + i = i + 2 + +print("fin, i vaut", i) + +# Ma prédiction : 0, 2, 4 (trois lignes), puis « fin, i vaut 6 ». +# Le test est réévalué AVANT chaque tour. Après le tour où i valait 4, i passe à 6 : +# 6 < 5 est faux, la boucle s'arrête. i vaut donc 6 à la sortie, pas 5. +""") + +md("""## Diagnostic 3.2 : NE PAS EXÉCUTER + +Cette cellule est en mode texte : **ne l'exécutez pas**, elle ne s'arrêterait jamais. + +```python +carburant = 100 +while carburant > 0: + print("Purge en cours...") + carburant = 100 +``` + +**Question (répondez dans la cellule ci-dessous) :** pourquoi cette boucle ne se +termine-t-elle jamais, et quelle est la plus petite modification qui la ferait terminer ? +""") +code("""# Ma réponse : +""", +"""# Ma réponse : +# La condition `carburant > 0` reste vraie éternellement parce que le corps de la boucle +# remet carburant à 100 au lieu de le faire décroître. Rien ne rapproche le programme de +# la condition d'arrêt. +# Plus petite correction : remplacer `carburant = 100` par une décrémentation, +# par exemple `carburant = carburant - 10`. +# +# Règle : dans un while, une variable au moins doit évoluer VERS la condition d'arrêt. +""") + +md("""## Réparation 3.3 : le capteur oublié + +Ce code doit afficher **toutes** les mesures. Il en rate une. Trouvez laquelle, et corrigez. +""") +code("""for i in range(1, len(mesures)): + print("capteur", i, ":", mesures[i]) +""", +"""for i in range(len(mesures)): # range(len(...)) commence à 0, pas à 1 + print("capteur", i, ":", mesures[i]) + +# La version initiale commençait à l'indice 1 et sautait donc mesures[0] (la valeur 12). +# En Python le premier indice d'une liste est 0. +""") + +md("""## Production 3.4 : le maximum, à la main + +Écrivez `maximum(valeurs)` qui renvoie la plus grande valeur d'une liste **non vide**, +**sans utiliser `max()`**. + +*Méthode de l'accumulateur : on retient un « meilleur pour l'instant », et on le met à +jour à chaque tour.* +""") +code("""def maximum(valeurs): + ... # à compléter +""", +"""def maximum(valeurs): + plus_grand = valeurs[0] # on part du premier élément, pas de 0 ! + for v in valeurs: + if v > plus_grand: + plus_grand = v + return plus_grand +""") +code("""assert maximum([12, 45, 7, 88, 23, 91, 4]) == 91 +assert maximum([5]) == 5 +assert maximum([3, 3, 3]) == 3 +assert maximum([-10, -3, -50]) == -3, "attention aux valeurs négatives : ne partez pas de 0" +print("OK. Détection du maximum opérationnelle") +""") + +md("""## Production 3.5 : le premier capteur défaillant + +Écrivez `premier_defaillant(valeurs, seuil)` qui renvoie **l'indice** de la première +mesure strictement inférieure à `seuil`, et `-1` si aucune ne l'est. + +*Indice : ici on a besoin de l'indice, donc de `range(len(valeurs))`. Et on peut sortir +de la fonction dès qu'on a trouvé.* +""") +code("""def premier_defaillant(valeurs, seuil): + ... # à compléter +""", +"""def premier_defaillant(valeurs, seuil): + for i in range(len(valeurs)): + if valeurs[i] < seuil: + return i # return sort immédiatement de la fonction + return -1 # atteint seulement si la boucle s'est terminée +""") +code("""assert premier_defaillant([12, 45, 7, 88], 20) == 0 +assert premier_defaillant([45, 88, 7, 4], 10) == 2 +assert premier_defaillant([45, 88, 91], 10) == -1 +assert premier_defaillant([], 10) == -1 +print("OK. Détection de défaillance opérationnelle") +""") + +md("""## Bonus 3.6 : la sentinelle + +Le capteur d'altitude envoie ses relevés en continu et termine sa transmission par la +valeur `-1`. Écrivez `somme_jusqu_a_sentinelle(flux)` qui additionne les valeurs +**jusqu'au premier `-1` exclu**. Les valeurs après le `-1` sont du bruit : on les ignore. + +*Indice : un `while` avec deux conditions est plus naturel qu'un `for` ici.* +""") +code("""def somme_jusqu_a_sentinelle(flux): + ... # à compléter +""", +"""def somme_jusqu_a_sentinelle(flux): + total = 0 + i = 0 + while i < len(flux) and flux[i] != -1: + total = total + flux[i] + i = i + 1 + return total +""") +code("""assert somme_jusqu_a_sentinelle([10, 20, 30, -1, 999, 999]) == 60 +assert somme_jusqu_a_sentinelle([-1, 5, 5]) == 0 +assert somme_jusqu_a_sentinelle([1, 2, 3]) == 6 +assert somme_jusqu_a_sentinelle([]) == 0 +print("OK. Lecture du flux opérationnelle") +""") + +# ---------------------------------------------------------------- MODULE 4 +md("""--- + +# Module 4 : LA SOUTE + +### *Listes : indices, tranches, listes de listes* + +> La soute est organisée en rayonnages. L'inventaire numérique a survécu, mais les +> programmes qui l'exploitent, non. + +*Ce module prépare : les structures linéaires (pile, file, listes chaînées) et le +tri fusion du chapitre « diviser pour régner ».* +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 4") +code("""soute = ["eau", "vivres", "outils", "piles", "filtres"] + +# Chaque sous-liste est un rayonnage : [rayon_0, rayon_1, rayon_2] +# Chaque ligne = un étage de la soute. +stock = [[12, 5, 0], + [3, 8, 7], + [0, 0, 4], + [9, 1, 2]] +""") + +md("""## Diagnostic 4.1 : indices et tranches + +Prédisez les quatre affichages. +""") +code("""print(soute[0]) +print(soute[-1]) +print(soute[1:3]) +print(len(soute[1:3])) + +# Ma prédiction : +""", +"""print(soute[0]) +print(soute[-1]) +print(soute[1:3]) +print(len(soute[1:3])) + +# Ma prédiction : +# eau +# filtres +# ['vivres', 'outils'] +# 2 +# Une tranche [a:b] va de l'indice a INCLUS à l'indice b EXCLU : elle contient b - a +# éléments. L'indice -1 désigne le dernier élément. +""") + +md("""## Réparation 4.2 : le bug du technicien + +Ce code doit retirer de `soute` tout ce qui commence par la lettre `"p"`. +Il en oublie. **Exécutez-le d'abord** pour constater le problème, puis corrigez-le. + +*Indice : modifier une liste pendant qu'on la parcourt décale les indices sous les pieds +de la boucle. La solution habituelle est de **construire une nouvelle liste** plutôt que +de retirer dans l'ancienne.* +""") +code("""soute_test = ["piles", "pinces", "eau", "outils"] + +for objet in soute_test: + if objet[0] == "p": + soute_test.remove(objet) + +print(soute_test) # devrait afficher ['eau', 'outils'] +""", +"""soute_test = ["piles", "pinces", "eau", "outils"] + +# Version corrigée : on construit une nouvelle liste au lieu de modifier celle qu'on parcourt +soute_gardee = [] +for objet in soute_test: + if objet[0] != "p": + soute_gardee.append(objet) +soute_test = soute_gardee + +print(soute_test) # ['eau', 'outils'] + +# Pourquoi la version initiale échouait : après avoir retiré "piles" (indice 0), tous les +# éléments se décalent d'un cran. La boucle passe alors à l'indice 1, qui contient +# désormais "eau" : "pinces" a été SAUTÉ. +""") +code("""assert soute_test == ["eau", "outils"] +print("OK. Purge de la soute correcte") +""") + +md("""## Production 4.3 : total d'un rayonnage + +`stock` est une **liste de listes** : `stock[i][j]` est la quantité stockée à l'étage `i`, +rayon `j`. + +Écrivez `total_rayon(grille, r)` qui renvoie la somme des quantités du rayon `r`, +**tous étages confondus**. + +*Attention : le rayon `r` est une **colonne**, pas une ligne. Il faut parcourir les étages.* +""") +code("""def total_rayon(grille, r): + ... # à compléter +""", +"""def total_rayon(grille, r): + total = 0 + for etage in grille: # etage est une liste : [rayon_0, rayon_1, rayon_2] + total = total + etage[r] + return total +""") +code("""assert total_rayon(stock, 0) == 24 +assert total_rayon(stock, 1) == 14 +assert total_rayon(stock, 2) == 13 +print("OK. Inventaire par rayon opérationnel") +""") + +md("""## Production 4.4 : filtrer en une ligne + +Écrivez `au_dessus(valeurs, seuil)` qui renvoie la liste des valeurs strictement +supérieures à `seuil`, **en utilisant une compréhension de liste**. + +*Rappel de la forme : `[expression for element in liste if condition]`* +""") +code("""def au_dessus(valeurs, seuil): + ... # à compléter (une seule ligne, avec une compréhension) +""", +"""def au_dessus(valeurs, seuil): + return [v for v in valeurs if v > seuil] +""") +code("""assert au_dessus([12, 45, 7, 88, 23], 20) == [45, 88, 23] +assert au_dessus([1, 2, 3], 10) == [] +assert au_dessus([], 0) == [] +print("OK. Filtrage opérationnel") +""") + +md("""## Bonus 4.5 : fusionner deux listes triées + +Deux capteurs ont enregistré des relevés, chacun **déjà trié dans l'ordre croissant**. +Écrivez `fusion(a, b)` qui renvoie une seule liste triée contenant tous les éléments, +**sans utiliser `sorted()` ni `.sort()`**. + +*Méthode : deux indices qui avancent, on prend à chaque tour le plus petit des deux +éléments courants.* + +> Cet exercice est **exactement** la seconde moitié du tri fusion, que vous verrez +> dans le chapitre « diviser pour régner ». +""") +code("""def fusion(a, b): + ... # à compléter +""", +"""def fusion(a, b): + resultat = [] + i = 0 + j = 0 + while i < len(a) and j < len(b): + if a[i] <= b[j]: + resultat.append(a[i]) + i = i + 1 + else: + resultat.append(b[j]) + j = j + 1 + # une des deux listes est épuisée : on recopie ce qui reste de l'autre + while i < len(a): + resultat.append(a[i]) + i = i + 1 + while j < len(b): + resultat.append(b[j]) + j = j + 1 + return resultat +""") +code("""assert fusion([1, 4, 9], [2, 3, 10]) == [1, 2, 3, 4, 9, 10] +assert fusion([], [2, 3]) == [2, 3] +assert fusion([5], []) == [5] +assert fusion([1, 1], [1]) == [1, 1, 1] +print("OK. Fusion opérationnelle, vous venez d'écrire la moitié d'un tri fusion") +""") + +# ---------------------------------------------------------------- MODULE 5 +md("""--- + +# Module 5 : L'ÉQUIPAGE + +### *Dictionnaires : clés, valeurs, parcours* + +> Reste le plus important : les **dossiers de l'équipage** et le **plan de la station**. +> Une liste ne suffit plus : on ne cherche pas « le 3ᵉ membre », on cherche « Kowalski ». + +*Ce module prépare : les graphes (un graphe se représente par un dictionnaire de +listes) et la mémoïsation en programmation dynamique (un dictionnaire de résultats +déjà calculés).* +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 5") +code("""equipage = { + "Kowalski": "pilote", + "Ndiaye": "medecin", + "Reyes": "ingenieur", + "Sokolova": "pilote", + "Tanaka": "ingenieur", +} + +# Plan de la station : à chaque module, la liste des modules directement accessibles +plan = { + "Sas": ["Couloir"], + "Couloir": ["Sas", "Cockpit", "Soute", "Labo"], + "Cockpit": ["Couloir"], + "Soute": ["Couloir", "Reacteur"], + "Labo": ["Couloir"], + "Reacteur": ["Soute"], +} +""") + +md("""## Diagnostic 5.1 : accès et absence + +Prédisez le résultat des trois premières lignes. La quatrième est volontairement +commentée : dites ce qui se passerait si on la décommentait. +""") +code("""print(equipage["Reyes"]) +print(len(equipage)) +print("Bowman" in equipage) +# print(equipage["Bowman"]) + +# Ma prédiction : +""", +"""print(equipage["Reyes"]) +print(len(equipage)) +print("Bowman" in equipage) +# print(equipage["Bowman"]) + +# Ma prédiction : +# ingenieur +# 5 (len d'un dictionnaire = son nombre de clés) +# False +# La 4e ligne lèverait une KeyError : accéder à une clé absente est une ERREUR, +# ce n'est pas None. Pour éviter ça : equipage.get("Bowman", "inconnu"). +""") + +md("""## Réparation 5.2 : le poste qui n'existe pas + +Cette fonction plante dès qu'on lui donne un nom inconnu. Corrigez-la pour qu'elle +renvoie `"inconnu"` dans ce cas. +""") +code("""def poste_de(registre, nom): + return registre[nom] + +print(poste_de(equipage, "Tanaka")) +print(poste_de(equipage, "Bowman")) # plante +""", +"""def poste_de(registre, nom): + return registre.get(nom, "inconnu") + +# Variante explicite, tout aussi correcte : +# def poste_de(registre, nom): +# if nom in registre: +# return registre[nom] +# return "inconnu" + +print(poste_de(equipage, "Tanaka")) +print(poste_de(equipage, "Bowman")) +""") +code("""assert poste_de(equipage, "Tanaka") == "ingenieur" +assert poste_de(equipage, "Bowman") == "inconnu" +print("OK. Registre sécurisé") +""") + +md("""## Production 5.3 : compter les postes + +Écrivez `compter_postes(registre)` qui renvoie un **dictionnaire** associant à chaque +poste le nombre de membres qui l'occupent. + +Sur `equipage`, on doit obtenir : `{'pilote': 2, 'medecin': 1, 'ingenieur': 2}` + +*Méthode classique : on part d'un dictionnaire vide, et pour chaque valeur rencontrée, +soit la clé existe déjà et on l'incrémente, soit elle n'existe pas et on la crée à 1.* +""") +code("""def compter_postes(registre): + ... # à compléter +""", +"""def compter_postes(registre): + comptes = {} + for poste in registre.values(): + if poste in comptes: + comptes[poste] = comptes[poste] + 1 + else: + comptes[poste] = 1 + return comptes +""") +code("""resultat = compter_postes(equipage) +assert resultat == {"pilote": 2, "medecin": 1, "ingenieur": 2} +assert compter_postes({}) == {} +assert compter_postes({"A": "pilote"}) == {"pilote": 1} +print("OK. Recensement de l'équipage opérationnel") +""") + +md("""## Production 5.4 : lire le plan de la station + +`plan` est un dictionnaire dont **les valeurs sont des listes**. + +1. Écrivez `voisins(plan, module)` : la liste des modules directement accessibles depuis + `module`, ou `[]` si le module n'existe pas. +2. Écrivez `nb_liaisons(plan)` : le nombre **total** de liaisons listées dans le plan + (on compte simplement la longueur de toutes les listes). + +> Cette structure a un nom : c'est une **liste d'adjacence**. C'est celle que vous +> utiliserez pour représenter un graphe. +""") +code("""def voisins(plan, module): + ... # à compléter + + +def nb_liaisons(plan): + ... # à compléter +""", +"""def voisins(plan, module): + return plan.get(module, []) + + +def nb_liaisons(plan): + total = 0 + for liste_voisins in plan.values(): + total = total + len(liste_voisins) + return total +""") +code("""assert voisins(plan, "Cockpit") == ["Couloir"] +assert voisins(plan, "Couloir") == ["Sas", "Cockpit", "Soute", "Labo"] +assert voisins(plan, "Hangar") == [] +assert nb_liaisons(plan) == 10 +print("OK. Lecture du plan opérationnelle") +""") + +md("""## Bonus 5.5 : regrouper par poste + +Écrivez `par_poste(registre)` qui renvoie un dictionnaire associant à chaque poste la +**liste des noms** qui l'occupent : + +```python +{'pilote': ['Kowalski', 'Sokolova'], 'medecin': ['Ndiaye'], 'ingenieur': ['Reyes', 'Tanaka']} +``` +""") +code("""def par_poste(registre): + ... # à compléter +""", +"""def par_poste(registre): + groupes = {} + for nom, poste in registre.items(): + if poste not in groupes: + groupes[poste] = [] + groupes[poste].append(nom) + return groupes +""") +code("""attendu = {"pilote": ["Kowalski", "Sokolova"], + "medecin": ["Ndiaye"], + "ingenieur": ["Reyes", "Tanaka"]} +assert par_poste(equipage) == attendu +assert par_poste({}) == {} +print("OK. Regroupement opérationnel") +""") + +# ---------------------------------------------------------------- MODULE 6 +md("""--- + +# Module 6 : SORTIE D'ORBITE + +### *Synthèse, et retour à la récursivité* + +> Tous les systèmes répondent. Reste à vérifier qu'aucun module n'est resté isolé après +> les avaries : si un compartiment n'est plus relié au reste de la station, l'équipage +> qui s'y trouve est piégé. +""") + +md("### SECOURS : exécutez cette cellule pour démarrer le module 6") +code("""plan = { + "Sas": ["Couloir"], + "Couloir": ["Sas", "Cockpit", "Soute", "Labo"], + "Cockpit": ["Couloir"], + "Soute": ["Couloir", "Reacteur"], + "Labo": ["Couloir"], + "Reacteur": ["Soute"], + "Serre": ["Cellule"], # compartiment isolé du reste ! + "Cellule": ["Serre"], +} +""") + +md("""## Synthèse 6.1 : quels modules sont atteignables ? + +Écrivez `atteignables(plan, depart)` : la liste des modules que l'on peut rejoindre +depuis `depart`, en comptant `depart` lui-même. + +L'algorithme vous est donné, il ne reste qu'à le traduire : + +``` +a_visiter ← liste contenant seulement depart +visites ← liste vide +tant que a_visiter n'est pas vide : + module ← on retire le DERNIER élément de a_visiter + si module n'est pas déjà dans visites : + ajouter module à visites + pour chaque v dans les voisins de module : + ajouter v à a_visiter +renvoyer visites +``` + +*Indice : « retirer le dernier élément » se dit `liste.pop()` en Python.* + +> Ce que vous venez d'écrire est un **parcours en profondeur**. `a_visiter` est une +> **pile**. Vous retrouverez les deux, sous ces noms, dans les chapitres Pile/File et +> Graphes. +""") +code("""def atteignables(plan, depart): + ... # à compléter +""", +"""def atteignables(plan, depart): + a_visiter = [depart] + visites = [] + while len(a_visiter) > 0: + module = a_visiter.pop() # retire et renvoie le dernier élément + if module not in visites: + visites.append(module) + for v in plan.get(module, []): + a_visiter.append(v) + return visites +""") +code("""r = atteignables(plan, "Sas") +assert sorted(r) == ["Cockpit", "Couloir", "Labo", "Reacteur", "Sas", "Soute"] +assert "Serre" not in r, "La serre ne doit PAS être atteignable depuis le sas" +assert sorted(atteignables(plan, "Serre")) == ["Cellule", "Serre"] +assert atteignables(plan, "Hangar") == ["Hangar"] +print("OK. Cartographie terminée, 2 compartiments isolés détectés : Serre et Cellule") +""") + +md("""## Diagnostic 6.2 : le compte à rebours + +Dernière étape avant le décollage. **Ne l'exécutez pas tout de suite** : écrivez d'abord, +sur votre feuille, l'ordre exact des affichages pour `compte_a_rebours(3)`. +""") +code("""def compte_a_rebours(n): + if n == 0: + print("DECOLLAGE") + else: + print(n) + compte_a_rebours(n - 1) + print("verif", n) + +compte_a_rebours(3) + +# Ma prédiction : +""", +"""def compte_a_rebours(n): + if n == 0: + print("DECOLLAGE") + else: + print(n) + compte_a_rebours(n - 1) + print("verif", n) + +compte_a_rebours(3) + +# Ma prédiction : +# 3 +# 2 +# 1 +# DECOLLAGE +# verif 1 +# verif 2 +# verif 3 +# +# Les « verif » sortent dans l'ORDRE INVERSE : chaque appel est mis en attente pendant +# que l'appel suivant se déroule, puis reprend là où il s'était arrêté. C'est exactement +# la PILE D'APPELS du chapitre en cours. +""") + +md("""--- + +## Fin du protocole + +**PERRIAND-7 est de nouveau opérationnelle.** + +Ce que vous venez de réutiliser, et où ça resservira : + +| Module | Notion | Chapitre de Terminale concerné | +|---|---|---| +| 1 · Réacteur | variables, mutabilité, aliasing | POO, listes chaînées | +| 2 · Protocoles | `return`, paramètres, portée | **Récursivité**, Modularité | +| 3 · Balayage | boucles, accumulateur | parcours d'arbres et de graphes | +| 4 · Soute | listes, listes de listes, compréhensions | structures linéaires, tri fusion | +| 5 · Équipage | dictionnaires, dict de listes | **graphes**, mémoïsation | +| 6 · Sortie d'orbite | pile, parcours, pile d'appels | Pile/File, **Récursivité** | + +--- +*Lycée Charlotte Perriand, NSI Terminale, Licence CC BY-SA 4.0* +""") + + +# ---------------------------------------------------------------- WRITE +def build(index): + cells = [] + for kind, eleve, corrige in CELLS: + src = (eleve, corrige)[index] + lines = src.splitlines(keepends=True) + cells.append({ + "cell_type": kind, + "id": uuid.uuid4().hex[:8], + "metadata": {}, + "source": lines, + **({"execution_count": None, "outputs": []} if kind == "code" else {}), + }) + 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, + } + + +DEST = os.path.expanduser("~/Documents/Cours/NSI/Terminale/Revisions_Rentree") +os.makedirs(DEST, exist_ok=True) + +for idx, name in ((0, "PERRIAND-7_Reveil_des_systemes.ipynb"), + (1, "PERRIAND-7_CORRIGE.ipynb")): + with open(os.path.join(DEST, name), "w", encoding="utf-8") as f: + json.dump(build(idx), f, ensure_ascii=False, indent=1) + print("écrit :", name, len(CELLS), "cellules")