1095 lines
34 KiB
Python
1095 lines
34 KiB
Python
# -*- 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")
|