{ "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 }