Épreuve écrite · 2025 · Jour 2

Bac NSI 2025 Polynésie jour 2

Le jeu « 6 qui prend » introduit la programmation objet, une base de champignons fait travailler SQL, puis un jeu de transformation de mots conduit à un parcours en largeur. Les difficultés centrales sont les règles qui se cumulent, les intervalles de valeurs et les structures qui conservent l’ordre d’un chemin.

Le sujet comprend 30 questions dans trois exercices indépendants, notés 6, 6 et 8 points. L’épreuve dure 3 h 30 sans calculatrice.

Dans ce sujet

Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.

Exercice 1 · 6 points

Modéliser les cartes et distribuer « 6 qui prend »

Marc programme « 6 qui prend » en Python, sans interface graphique. Le jeu possède 104 cartes numérotées de 1 à 104. Chaque carte porte des têtes de bœuf, notées TdB, qui sont des pénalités ; le but est d’en avoir le moins possible à la fin.

Partie A : classe Carte

Le constructeur reçoit une valeur entière de 1 à 104, stockée dans valeur. L’attribut entier TdB est calculé avec calcul_TdB, méthode qui n’exige aucun argument explicite autre que l’instance.

Question 1

#

Écrire le constructeur de Carte.

Comprendre la correction
class Carte:
    def __init__(self, valeur):
        self.valeur = valeur
        self.TdB = self.calcul_TdB()

On stocke la valeur avant d’appeler le calcul, car la méthode en a besoin. calcul_TdB() renvoie le nombre de pénalités, que le constructeur affecte à l’attribut. Le sujet parle de « méthode de classe », mais l’interface décrite ici est une méthode d’instance utilisant self.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)
ConditionTdB ajoutées
Valeur divisible par 115
Dernier chiffre 03
Dernier chiffre 52
Aucune des trois conditions1

Les trois premières règles se cumulent ; par exemple 55 porte 7 TdB.

Question 2

#

Écrire calcul_TdB pour calculer les pénalités selon les règles cumulatives.

Indice 1

Initialisez à 0, puis cumulez les critères indépendants.

Indice 2

La valeur 1 ne sert que si aucune règle spéciale ne s’applique.

Comprendre la correction
def calcul_TdB(self):
    total = 0
    if self.valeur % 11 == 0:
        total += 5
    if self.valeur % 10 == 0:
        total += 3
    if self.valeur % 10 == 5:
        total += 2
    if total == 0:
        total = 1
    return total

Les tests sont indépendants : 55 vérifie la divisibilité par 11 et la terminaison par 5, donc reçoit 5 + 2 = 7 pénalités. Une chaîne de elif ignorerait le second critère. La pénalité de base 1 n’est attribuée que si aucun critère n’a contribué, et ne s’ajoute pas aux autres.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)

Question 3

#

Écrire est_superieure_a(self, autre), qui compare strictement les valeurs de deux cartes.

Comprendre la correction
def est_superieure_a(self, autre):
    return self.valeur > autre.valeur

On compare les attributs numériques, pas les objets eux-mêmes. Deux valeurs égales donnent faux puisque la comparaison est stricte. La méthode fournie difference(autre) renverra quant à elle la valeur absolue de leur différence.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)

Partie B : classe Paquet

Le constructeur reçoit une liste de cartes et la stocke dans contenu. afficher affiche leurs valeurs ; ajouter_carte ajoute une carte.

class Paquet:
    def __init__(self, L):
        self.contenu = L
    def afficher(self):
        ...
        ...
    def ajouter_carte(self, carte):
        ...

Question 4

#

Compléter afficher et ajouter_carte de la classe Paquet.

Comprendre la correction
class Paquet:
    def __init__(self, L):
        self.contenu = L

    def afficher(self):
        for carte in self.contenu:
            print(carte.valeur)

    def ajouter_carte(self, carte):
        self.contenu.append(carte)

Le paquet contient des objets Carte, donc on affiche leur attribut valeur. append ajoute l’objet carte en fin de liste ; il ne faut pas ajouter seulement sa valeur, sinon le paquet mélangerait objets et entiers.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 5

#

Écrire nombre_TdB, qui renvoie le total des pénalités des cartes d’un paquet.

Comprendre la correction
def nombre_TdB(self):
    total = 0
    for carte in self.contenu:
        total += carte.TdB
    return total

On additionne les pénalités déjà calculées sur les objets Carte. Le retour reste après la boucle. Pour un paquet vide, le total est 0 ; pour les cartes 5, 10 et 55, il vaut 2 + 3 + 7 = 12.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Question 6

#

Écrire distribuer(nbr), qui distribue 10 cartes par joueur, à tour de rôle, et renvoie les nbr paquets. La première carte du paquet va au premier joueur. Les cartes sont retirées du paquet source ; nbr est au maximum 10.

Indice

Un tour donne une carte à chaque joueur, et il faut dix tours.

Comprendre la correction
def distribuer(self, nbr):
    paquets = [Paquet([]) for _ in range(nbr)]
    for tour in range(10):
        for joueur in range(nbr):
            carte = self.contenu.pop(0)
            paquets[joueur].ajouter_carte(carte)
    return paquets

La boucle extérieure fait les dix tours et l’intérieure sert successivement tous les joueurs. pop(0) enlève la première carte du paquet source. Avec deux joueurs et un paquet initial ordonné, le premier reçoit 1, 3, 5…19, et le second 2, 4, 6…20.

La compréhension crée un objet Paquet différent pour chaque joueur. [Paquet([])] * nbr serait incorrect : tous les joueurs partageraient le même paquet. La fonction suppose, comme la distribution du jeu, au moins 10 * nbr cartes disponibles ; un vrai service peut vérifier cette précondition avant de commencer.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Paquet propose aussi prendre_carte, qui demande une valeur jusqu’à choisir une carte présente, la retire et la renvoie ; trier classe les cartes par valeur croissante.

Partie C : joueurs et initialisation du plateau

class Joueur:
    def __init__(self, nom, main):
        self.nom = nom
        self.main = main
        self.cartes_ramassees = Paquet([])
        self.penalite = 0

ramasser_paquet ajoute un paquet aux cartes ramassées du joueur et augmente sa pénalité du total de TdB correspondant.

Question 7

#

Instancier le joueur nommé « Joueur 1 », de main L[0], et affecter l’objet à J1.

Comprendre la correction
J1 = Joueur('Joueur 1', L[0])

L[0] est déjà un Paquet : on le transmet directement. La chaîne « Joueur 1 » est le nom affichable, tandis que J1 est le nom de la variable Python.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)
from random import *
# Créer les104cartes par compréhension
jeu = [... for i in range(..., ...)]
shuffle(jeu)
jeu_initial = ...
distri = ...
Ordi = Joueur("Ordi", distri[0])
J1 = Joueur("J1", distri[1])

Question 8

#

Compléter les lignes 3, 7 et 9 de l’initialisation du jeu pour deux joueurs.

Comprendre la correction
from random import *
jeu = [Carte(i) for i in range(1, 105)]
shuffle(jeu)
jeu_initial = Paquet(jeu)
distri = jeu_initial.distribuer(2)
Ordi = Joueur("Ordi", distri[0])
J1 = Joueur("J1", distri[1])

La borne 105 est exclue, donc les 104 valeurs de 1 à 104 sont créées. shuffle mélange la liste en place ; on ne doit pas affecter son retour, qui vaut None. Après distribution, 20 cartes ont quitté le paquet source, où il en reste 84.

Voir la question dans le sujet PDF, p. 4, 5 (nouvel onglet)
Quelle carte coûte le plus cher ?Un atelier pour expérimenter

Faites varier une carte et le nombre de joueurs. Les pénalités sont décomposées règle par règle ; la distribution montre les dix tours d’un paquet ordonné pour rendre son fonctionnement visible.

Lire le résultat de l’expérience initiale

Carte55 : 7 tête(s) de bœuf

Les règles spéciales se cumulent. Le point de base apparaît seulement si aucune règle ne s’applique. La distribution ci-dessous est volontairement sans mélange pour montrer le tour de rôle.

TourJoueur1Joueur2
112
234
356
478
5910
61112
71314
81516
91718
101920

Des règles cumulatives demandent des if indépendants. Une distribution à tour de rôle demande une boucle par tour puis une boucle par joueur, avec des objets de main distincts.

Revoir les notions de cet exercice

Exercice 2 · 6 points

SQL sur une collection de champignons et recherche dans une chaîne

Les deux parties sont indépendantes. Les caractéristiques, toxicités et recettes ci-dessous sont les données du sujet, à manipuler comme un modèle informatique. Elles ne doivent pas servir à identifier ni à décider de consommer un champignon.

Partie A : base relationnelle

Voici l’extrait fourni par SELECT * FROM champignon. Nom, lamelle et couleur sont des chaînes ; les tailles sont numériques et exprimées en centimètres.

idnomid_ordrelamellecouleurchapeau_minchapeau_maxpied_minpied_max
1champignon de Paris1ouiblanc41025
2champignon noir2nonnoir21000
3coprin chevelu1ouiblanc5201040
4bolet à pied rouge3nonjaune719515
5amanite des Césars4ouiorange820815

Question 1

#

Mathilde cherche les noms des champignons ayant des lamelles et une couleur orange. Écrire la requête.

Comprendre la correction
SELECT nom
FROM champignon
WHERE lamelle = 'oui' AND couleur = 'orange';

Les deux conditions sont nécessaires simultanément. On conserve la casse des valeurs de l’extrait : oui est une chaîne et non un booléen SQL.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 2

#

Romain cherche un champignon sans pied et dont le chapeau mesure 15 cm. Écrire la requête donnant les noms possibles.

SQLTester une mesure dans l’intervalle du modèleÉcrivez votre solution et mettez-la à l’épreuve

Renvoyez les noms des lignes du modèle décrivant un champignon sans pied et dont la plage de diamètre du chapeau contient 15 cm. Ces données scolaires ne doivent pas servir à identifier ou consommer un champignon réel.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Extrait officiel des cinq champignons : Les lignes reprennent le tableau officiel. Le seul champignon sans pied de l’extrait a un chapeau de 2 à 10 cm ; aucune ligne ne convient pour 15 cm.
  • Cas complémentaire : les bornes font partie de la plage : Jeu complémentaire pédagogique, distinct des données officielles. Noms artificiels de test.15 peut être égal au minimum ou au maximum. Un intervalle de pied 0 à 2 ne signifie pas une absence de pied.
Indice

Une mesure appartient à un intervalle fermé si elle satisfait ses deux bornes.

Comprendre la correction
SELECT nom
FROM champignon
WHERE pied_min = 0 AND pied_max = 0
AND chapeau_min <= 15 AND chapeau_max >= 15;

Une dimension mesurée doit appartenir à l’intervalle stocké : on ne cherche pas seulement un maximum ou un minimum égal à 15. « Sans pied » est ici représenté par un intervalle de longueur nulle 0 à 0. Dans l’extrait, le champignon noir est sans pied, mais son chapeau ne dépasse pas 10 cm : aucune des cinq lignes ne satisfait l’ensemble.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
ordre.idnomclasse
1agaricalesagaricomycètes
2trémellalesphragmabasidiomycètes
3bolétalesagaricomycètes
4amanitalesagaricomycètes
5cantharellalesagaricomycètes
6polyporalesbasidiomycètes
7clavarialeshomobasidiomycètes
8tricholomatalesagaricomycètes

Question 4

#

Écrire la requête donnant les noms des champignons de la classe agaricomycètes.

Comprendre la correction
SELECT champignon.nom
FROM champignon JOIN ordre ON champignon.id_ordre = ordre.id
WHERE ordre.classe = 'agaricomycètes';

La classe appartient à la table ordre, tandis que le nom recherché appartient à champignon. La jointure suit l’identifiant d’ordre ; on qualifie le nom de champignon pour éviter l’ambiguïté avec ordre.nom.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Question 5

#

Ajouter avec l’identifiant 56 l’amanite solitaire : blanc, ordre amanitales, avec lamelles, chapeau de 6 à 20 cm et pied de 4 à 10 cm.

Comprendre la correction
INSERT INTO champignon
(id, nom, id_ordre, lamelle, couleur, chapeau_min, chapeau_max, pied_min, pied_max)
VALUES (56, 'amanite solitaire', 4, 'oui', 'blanc', 6, 20, 4, 10);

On utilise l’identifiant 4 de l’ordre amanitales, et non son nom dans la colonne étrangère. Nommer les colonnes explicite l’ordre des valeurs et évite d’inverser les bornes du chapeau et du pied.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

On ajoute l’attribut id_toxicite à champignon et la relation toxicite :

id_toxtypeeffets (données du sujet)
1très toxiqueEntraînant la mort
2toxiqueEntraînant des problèmes digestifs ou nerveux
3à rejeterChampignons suspects ou ayant un mauvais goût
4comestibleChampignons pouvant être consommés

Question 6

#

Écrire le schéma relationnel à trois tables avec clés primaires soulignées et clés étrangères précédées de #.

Comprendre la correction

champignon(id, nom, #id_ordre, lamelle, couleur, chapeau_min, chapeau_max, pied_min, pied_max, #id_toxicite)

ordre(id, nom, classe)

toxicite(id_tox, type, effets)

id_ordre référence ordre.id et id_toxicite référence toxicite.id_tox. Une clé étrangère n’est pas automatiquement unique : plusieurs champignons peuvent partager la même toxicité ou le même ordre.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)

Le PDF indique : « Une erreur s’est glissée dans cette table : le champignon de nom amanite citrine est très toxique car son ingestion peut entraîner la mort. » Cette assertion est une donnée à traiter pour la question SQL, et non une règle d’identification ou de consommation.

Question 7

#

Le sujet demande de classer la ligne « amanite citrine » dans la toxicité « très toxique ». Écrire la requête corrigeant sa valeur dans champignon.

Comprendre la correction
UPDATE champignon
SET id_toxicite = 1
WHERE nom = 'amanite citrine';

La valeur 1 référence la catégorie demandée dans la table toxicite. On modifie la référence de cette ligne, pas le libellé de toute la catégorie. Cette instruction applique la donnée de l’exercice ; elle ne constitue pas une affirmation validée de toxicologie.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 8

#

Écrire la requête donnant les noms des champignons d’ordre amanitales et de toxicité très toxique.

Comprendre la correction
SELECT champignon.nom
FROM champignon
JOIN ordre ON champignon.id_ordre = ordre.id
JOIN toxicite ON champignon.id_toxicite = toxicite.id_tox
WHERE ordre.nom = 'amanitales' AND toxicite.type = 'très toxique';

Les deux jointures rendent les libellés disponibles. Le filtre exige à la fois l’ordre et la toxicité ; on ne remplace pas leurs noms par des identifiants supposés constants lorsque le schéma permet de les chercher.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Partie B : objets et recherche textuelle

Quentin associe au lactaire délicieux : localisation sous les pins, saison été, recette « lactaires grillés à l’huile d’olive », cuisson «12 minutes à feu moyen ». Il stocke ces informations ainsi :

class Champignon:
    def __init__(self, nom, lieu, saison, recette, cuisson):
        self.nom = nom
        self.localisation = lieu
        self.saison = saison
        self.recette = recette
        self.cuisson = cuisson
champignon1 = Champignon(
    "Lactaire délicieux", "Sous les pins", "été",
    "Lactaires grillés à l’huile d’olive", "12 minutes à feu moyen"
)

Toutes les instances sont placées dans la liste liste_champi.

for e in liste_champi:
    if ... == 'été':
        print(...)

Question 9

#

Compléter le programme qui affiche les noms des objets de liste_champi dont la saison est été.

Comprendre la correction
for e in liste_champi:
    if e.saison == 'été':
        print(e.nom)

Chaque élément est un objet, donc ses champs s’accèdent avec le point. La comparaison porte sur la saison, tandis que l’affichage porte sur le nom.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
for c in liste_champi:
    if c.nom == 'Lactaire délicieux':
        return c.cuisson == 'feu moyen'

Question 10

#

Pourquoi le programme ne donne-t-il pas le résultat attendu pour une cuisson à feu moyen ?

Comprendre la correction

La valeur de c.cuisson est '12 minutes à feu moyen', pas exactement 'feu moyen'. L’égalité compare les chaînes entières et donne donc faux, même si la seconde est incluse dans la première.

Le fragment imprimé utilise aussi return hors de toute fonction : copié seul au niveau du module, il provoque une erreur de syntaxe. L’explication de la comparaison suppose qu’il était placé dans une fonction, comme le laisse entendre le commentaire du sujet.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

La fonction fournie vérifie par exemple recherche_textuelle('dans une forêt', 'forêt') == True et recherche_textuelle('près de la mer', 'mare') == False.

Question 11

#

Corriger le programme avec recherche_textuelle(texte, mot), qui indique si le texte contient le mot ou l’expression cherchée.

Comprendre la correction
for c in liste_champi:
    if c.nom == 'Lactaire délicieux':
        print(recherche_textuelle(c.cuisson, 'feu moyen'))

La cuisson complète est le texte à parcourir ; « feu moyen » est l’expression recherchée. Cette version de script affiche le booléen. Si l’on place le fragment dans une fonction, on peut retourner ce même appel au lieu de l’afficher.

Voir la question dans le sujet PDF, p. 9 (nouvel onglet)
Une mesure entre deux bornesUn atelier pour expérimenter

Faites varier le diamètre de chapeau et la présence d’un pied. L’atelier filtre uniquement les cinq lignes de l’extrait pour montrer comment une requête compare un intervalle. Il ne sert pas à identifier un champignon réel.

Lire le résultat de l’expérience initiale

0 ligne(s) correspondante(s) dans l’extrait

La mesure doit être supérieure ou égale au minimum et inférieure ou égale au maximum. Un résultat vide signifie seulement qu’aucune ligne de cet extrait ne satisfait les filtres.

Nom du modèleIntervalle chapeauIntervalle piedSatisfait les deux filtres
champignon de Paris4 à 102 à 5Non
champignon noir2 à 100 à 0Non
coprin chevelu5 à 2010 à 40Non
bolet à pied rouge7 à 195 à 15Non
amanite des Césars8 à 208 à 15Non

Un test d’appartenance à un intervalle combine deux inégalités. Les valeurs sélectionnées restent celles d’un jeu de données limité, pas une identification du monde réel.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Transformer des mots avec un parcours en largeur

On dispose de mots de quatre lettres : par exemple gars, grue, mars, mors, ours, purs, durs. Deux mots sont voisins si l’on peut changer exactement une lettre pour passer de l’un à l’autre, sans tenir compte de l’ordre des lettres. Mars et mors sont voisins ; mors et ours aussi ; grue et ours nécessitent deux remplacements.

On veut relier un mot de départ à un mot final avec le minimum de voisins intermédiaires. Exemple : mars, mors, ours. Le sujet suppose toujours qu’un chemin existe. Un graphe représente les mots comme sommets et les voisinages comme arêtes.

Partie A : structures adaptées

Question 1

#

Décrire le fonctionnement d’une pile.

Comprendre la correction

Une pile suit le principe LIFO, dernier entré, premier sorti. Empiler ajoute au sommet ; dépiler retire et renvoie le sommet. Si on empile A puis B puis C, on dépile C, puis B, puis A : cette inversion d’ordre servira à reconstruire le chemin.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Question 2

#

Décrire le fonctionnement d’une file.

Comprendre la correction

Une file suit FIFO, premier entré, premier sorti. Enfiler ajoute en queue ; défiler retire la tête. A, B et C enfilés dans cet ordre ressortent A, B, C. Cela permet au parcours en largeur d’explorer les sommets dans l’ordre de leur découverte.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Question 3

#

Pourquoi un graphe non orienté convient-il ?

Comprendre la correction

Le remplacement de lettre est réversible : si A peut devenir B en un changement, B peut devenir A en rétablissant la lettre. Le voisinage est donc symétrique ; une arête sans orientation décrit cette relation.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Question 4

#

Dessiner le graphe pour gars, mars, mors, ours et purs.

Comprendre la correction
garsmarsmorsourspurs
Le graphe des cinq mots : une chaîne non orientée
Lire les connexions du schéma
  • gars relié à mars
  • mars relié à mors
  • mors relié à ours
  • ours relié à purs

Il comporte les quatre arêtes gars-mars, mars-mors, mors-ours et ours-purs. Toute autre paire exige au moins deux changements, donc n’a pas d’arête directe. Un graphe doit conserver aussi les sommets, pas seulement une liste de transitions.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)

Partie B : calculer le voisinage

La distance est le minimum de lettres à remplacer sans imposer leur position. Deux mots sont voisins si leur distance vaut 1. TAB_MOTS est une variable globale contenant des mots de quatre caractères.

def chaine_vers_tab(mot):
    tab_lettres = ...
    for ... in ...:
        tab_lettres....
    return tab_lettres

Question 5

#

Compléter chaine_vers_tab(mot), qui convertit un mot de quatre caractères en liste de lettres.

Comprendre la correction
def chaine_vers_tab(mot):
    tab_lettres = []
    for lettre in mot:
        tab_lettres.append(lettre)
    return tab_lettres

La boucle conserve les caractères dans leur ordre initial et leurs répétitions. Pour ours, on obtient ['o', 'u', 'r', 's']. Même si le calcul de distance ignore ensuite l’ordre, il doit encore conserver le nombre d’occurrences de chaque lettre.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Question 6

#

Expliquer pourquoi la fonction donnée calcule la distance entre deux mots de quatre caractères.

Indice

remove supprime une occurrence, ce qui conserve la multiplicité des lettres.

Comprendre la correction
def distance(mot1, mot2):
    tab = chaine_vers_tab(mot1)
    for lettre in mot2:
        if lettre in tab:
            tab.remove(lettre)
    return len(tab)

La liste commence avec toutes les occurrences des lettres du premier mot. Chaque lettre du second retire au plus une occurrence identique encore disponible. Après ce couplage, les lettres restantes du premier mot n’ont pas d’équivalent : il faut les remplacer. Comme les mots ont la même longueur, le nombre de lettres manquantes de l’autre côté est identique.

Pour grue et ours, on apparie r et u ; g et e restent, donc la distance vaut 2. Avec des répétitions, remove ne retire qu’une occurrence, ce qui est essentiel. Un ensemble de lettres donnerait des réponses fausses. Deux anagrammes ont ici une distance 0, même si leurs positions diffèrent.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
def renvoie_voisins(mot):
    tab_voisins = ...
    for voisin_possible in ...:
        if ...:
            tab_voisins....
    return ...

Question 7

#

Compléter renvoie_voisins(mot), qui liste les mots de TAB_MOTS voisins du mot donné.

Comprendre la correction
def renvoie_voisins(mot):
    tab_voisins = []
    for voisin_possible in TAB_MOTS:
        if distance(mot, voisin_possible) == 1:
            tab_voisins.append(voisin_possible)
    return tab_voisins

On teste chaque candidat avec la distance égale à 1. Une distance 0 n’est pas admise : le mot lui-même, ainsi que ses anagrammes selon cette définition, ne sont pas voisins par un remplacement exactement. Le résultat est une nouvelle liste, dans l’ordre de TAB_MOTS.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

Partie C : chercher un plus court chemin

File propose est_vide, defiler et enfiler. Pile propose est_vide, depiler et empiler. Le programme fourni est :

def dic_parent(mot_depart, mot_final):
    file_voisins = File()
    parent = {mot_depart: None}
    mot = mot_depart
    file_voisins.enfiler(mot)
    while not file_voisins.est_vide() and not mot == mot_final:
        mot = file_voisins.defiler()
        for voisin in renvoie_voisins(mot):
            if not voisin in parent:
                parent[voisin] = mot
                file_voisins.enfiler(voisin)
    return parent

Voisins donnés : mars → gars, mors ; gars → mars ; mors → mars, ours ; ours → mors, purs. Initialement parent vaut {'mars': None} et la file contient mars. Au premier tour, mars est retiré, gars et mors sont ajoutés avec parent mars ; la file contient gars puis mors. Au deuxième tour, gars est retiré, mars déjà connu est ignoré ; seule mors reste en file.

Question 8

#

Poursuivre la trace de dic_parent('mars', 'ours') après les deux premiers tours, jusqu’à la fin.

Indice

Le test d’arrêt se fait avant le tour suivant, après le traitement des voisins.

Comprendre la correction
TourMot défiléMise à jour de parentFile après traitement
3morsAjout ours : mors[ours]
4oursAjout purs : ours[purs]

Au tour 3, mars est déjà connu ; seul ours est découvert. Le tour 4 défile ours, puis traite quand même ses voisins avant de retester la condition de boucle : mors est connu et purs est ajouté. Le test suivant voit mot == mot_final et arrête la boucle.

{'mars': None, 'gars': 'mars', 'mors': 'mars',
 'ours': 'mors', 'purs': 'ours'}

La file conserve donc purs à la fin. Le dictionnaire contient des sommets explorés qui ne sont pas sur le chemin final, comme gars et purs. Il ne faut pas le confondre avec la seule liste mars-mors-ours.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
def renvoie_pile(parent, mot_final):
    ma_pile = Pile()
    mot = mot_final
    while mot != ...:
        ma_pile....
        mot = ...
    return ma_pile

Question 9

#

Compléter renvoie_pile(parent, mot_final). Le mot final est empilé d’abord, et le sommet final de la pile doit être le mot de départ.

PythonRemonter un chemin sans perdre le mot de départÉcrivez votre solution et mettez-la à l’épreuve

Écrivez renvoie_pile(parent, mot_final). Le dictionnaire parent provient du parcours en largeur : il associe chaque mot découvert à son prédécesseur, et le mot de départ à None. Le mot final a été atteint. Empilez d’abord ce mot final, puis ses prédécesseurs jusqu’au départ inclus. Renvoyez l’objet Pile ; quand on le dépilera, on doit lire le chemin du départ à l’arrivée. Ne modifiez pas parent.

def renvoie_pile(parent, mot_final):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Le chemin de l’exemple : L’ordre d’empilement est inversé par l’ordre de dépilement.
  • Départ et arrivée confondus : Le départ lui-même doit être empilé avant de lire son parent None.
  • Un seul lien : Deux sommets demandent deux éléments dans la pile, pas seulement l’arrivée.
  • Ignorer les branches qui ne mènent pas au but : On suit une chaîne de parents ; parcourir toutes les clés ajouterait à tort murs.
  • Le dictionnaire reste disponible : D’autres reconstructions doivent pouvoir réutiliser la même exploration.
Indice

Le départ a pour parentNone.

Comprendre la correction
def renvoie_pile(parent, mot_final):
    ma_pile = Pile()
    mot = mot_final
    while mot is not None:
        ma_pile.empiler(mot)
        mot = parent[mot]
    return ma_pile

On remonte le parent de chaque mot jusqu’à None, qui marque le prédécesseur du départ. Pour ours, on empile ours, puis mors, puis mars. Ne pas arrêter avant d’empiler le départ : il doit figurer dans le chemin.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
def construit_chemin(ma_pile):
    tab = ...
    while ...:
        mot = ...
        tab....
    return tab

Question 10

#

Compléter construit_chemin(ma_pile), qui renvoie la liste des mots dans le bon ordre.

Comprendre la correction
def construit_chemin(ma_pile):
    tab = []
    while not ma_pile.est_vide():
        mot = ma_pile.depiler()
        tab.append(mot)
    return tab

Le sommet est le départ. Dépiler fournit donc mars, mors, ours et remet la chaîne de parents dans l’ordre du trajet. La pile est consommée par cette fonction ; le tableau obtenu garde les mots.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Question 11

#

Coder chercher_chemin(mot_depart, mot_final) à l’aide des fonctions précédentes.

Comprendre la correction
def chercher_chemin(mot_depart, mot_final):
    parent = dic_parent(mot_depart, mot_final)
    ma_pile = renvoie_pile(parent, mot_final)
    return construit_chemin(ma_pile)

Chaque fonction a une responsabilité : explorer, remonter les parents, remettre le chemin dans l’ordre. Le parcours en largeur découvre les sommets à distance croissante, donc le premier parent enregistré pour le but donne un plus court chemin dans ce graphe non pondéré.

Si départ et arrivée sont identiques, la pile contient ce seul mot et le résultat est une liste de longueur 1. Si aucun chemin n’existait, il faudrait tester la présence du but dans parent avant la reconstruction ; le sujet garantit son existence.

Voir la question dans le sujet PDF, p. 14 (nouvel onglet)
Suivez la file et reconstruisez le cheminUn atelier pour expérimenter

Choisissez départ et arrivée parmi les cinq mots, puis le nombre d’étapes du parcours. La simulation suit exactement le code fourni, y compris le traitement des voisins du mot final au dernier tour.

Lire le résultat de l’expérience initiale

mars → mors → ours

Le dictionnaire sert aussi de marqueur de découverte : un sommet connu n’est plus enfilé. La file conserve l’ordre des distances, la pile remet ensuite les parents dans le sens du trajet.

TourMot défiléNouveaux parentsFile après le tour
1marsgars ← mars, mors ← marsgars, mors
2garsAucunmors
3morsours ← morsours
4ourspurs ← ourspurs

La file garantit l’exploration par distance ; le dictionnaire mémorise une seule découverte par sommet ; la pile inverse la remontée des parents pour produire le chemin.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Pour les règles de cartes, testez 55 : cet exemple distingue immédiatement une somme de critères d’une alternative exclusive.
  • Pour SQL, traduisez chaque intervalle par deux inégalités et chaque lien entre tables par une référence.
  • Dans la trace BFS, respectez le moment où la condition d’arrêt est évaluée : le mot final peut encore ajouter un voisin avant la fin.

Retrouver ces notions dans d’autres sujets

Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

Énoncé : sujet 25-NSIJ2PO1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.