Épreuve écrite · 2025 · Jour 1

Bac NSI 2025 centres étrangers groupe 1 jour 1

Ce sujet confronte trois manières de chercher : suivre des balises selon une règle locale, explorer des chaînes de cartes par longueur croissante et descendre dans un arbre de sondes ordonné. Les corrections expliquent ce que chaque méthode garantit, reconstruisent les données et vérifient les programmes sur des cas limites.

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

Courses d’orientation et choix glouton sur un graphe

Les participants poinçonnent les balises de La forêt des chênes. Trois parcours partent de la balise 1 : vert facile, rouge moyen et noir difficile. Vert et noir terminent en 12 ; rouge termine en 8. Dans le dessin original, triangle signifie vert, carré rouge et pentagone noir. Une balise peut appartenir à plusieurs parcours. Le tableau donne ces mêmes informations sans dépendre des formes.

123456789101112
Figure 1 : liaisons de La forêt des chênes
Lire les connexions du schéma
  • 1 relié à 2
  • 1 relié à 3
  • 2 relié à 4
  • 3 relié à 6
  • 4 relié à 5
  • 4 relié à 6
  • 5 relié à 10
  • 6 relié à 7
  • 6 relié à 11
  • 7 relié à 10
  • 8 relié à 9
  • 9 relié à 11
  • 9 relié à 12
  • 10 relié à 12
BaliseItinéraires
1vert, rouge, noir
2rouge
3vert, noir
4rouge, noir
5noir
6vert, rouge, noir
7vert
8rouge
9rouge
10vert, noir
11rouge
12vert, noir

Chaque Balise possède num_balise, couleurs_balise, voisines (objets Balise) et visitee (booléen initialementFalse). L’annexe fournie est reproduite intégralement :

class Balise:
    def __init__(self, numero, couleurs):
        self.num_balise = numero
        self.couleurs_balise = couleurs
        self.voisines = []
        self.visitee = False
    def methode1(self):
        return [b.num_balise for b in self.voisines]
    def methode2(self, couleur):
        self.couleurs_balise = [c for c in self.couleurs_balise if c != couleur]
    def methode3(self, couleur):
        self.couleurs_balise.append(couleur)
balise1 = Balise(1, ['vert', 'rouge', 'noir'])
balise2 = Balise(2, ['rouge'])
balise3 = Balise(3, ['vert', 'noir'])
balise4 = Balise(4, ['rouge', 'noir'])
balise5 = Balise(5, ['noir'])
balise6 = Balise(6, ['vert', 'rouge', 'noir'])
balise7 = Balise(7, ['vert'])
balise8 = Balise(8, ['rouge'])
balise9 = Balise(9, ['rouge'])
balise10 = Balise(10, ['vert', 'noir'])
balise11 = Balise(11, ['rouge'])
balise12 = ...
balise1.voisines = [balise2, balise3]
balise2.voisines = [balise1, balise4]
balise3.voisines = [balise1, balise6]
balise4.voisines = [balise2, balise5, balise6]
balise5.voisines = [balise4, balise10]
balise6.voisines = [balise3, balise4, balise7, balise11]
balise7.voisines = [balise6, balise10]
balise8.voisines = [balise9]
# Ligne38 : à compléter
balise10.voisines = [balise5, balise7, balise12]
balise11.voisines = [balise6, balise9]
balise12.voisines = [balise9, balise10]

Question 1

#

Compléter la ligne 28 pour instancier la balise 12.

Indice

Lire les deux symboles de la balise 12 dans la légende.

Comprendre la correction
balise12 = Balise(12, ['vert', 'noir'])

La figure place 12 sur les parcours vert et noir, pas sur le rouge. Le constructeur reçoit le numéro puis la liste de couleurs ; il initialise lui-même voisines et visitee. L’ordre des deux couleurs n’affecte pas les tests d’appartenance.

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

Question 2

#

Écrire la ligne 38 manquante pour compléter le graphe.

Indice

Une liste de voisines contient les instances, pas leurs numéros.

Comprendre la correction
balise9.voisines = [balise8, balise11, balise12]

La balise 9 est reliée à 8,11 et 12. Les autres affectations de l’annexe ont déjà enregistré les liaisons dans l’autre sens. Les éléments sont des objets Balise ; mettre les entiers 8,11,12 empêcherait ensuite d’accéder à leurs attributs.

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

Question 3

#

Que renvoie balise4.methode1() ?

Indice

Appliquez l’expression b.num_balise à chaque élément.

Comprendre la correction
[2, 5, 6]

La compréhension parcourt les trois objets voisins dans l’ordre de la liste et extrait leur num_balise. Elle renvoie une nouvelle liste d’entiers sans modifier le graphe.

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

Question 4

#

Après balise 4.methode2(rouge), puis methode3(vert), que vaut couleurs_balise ?

Indice

La première méthode filtre, la seconde ajoute en fin de liste.

Comprendre la correction
['noir', 'vert']

methode2 reconstruit la liste en supprimant les occurrences de rouge ; il reste noir. methode3 ajoute vert à la fin. On ne trie pas les couleurs et l’ajout n’est pas un remplacement. Comme indiqué dans l’énoncé, on rétablit ensuite la balise 4 originale pour traiter la suite.

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

Les extrémités doivent porter la couleur demandée. Le squelette fourni contient les blancs suivants :

def itineraire(balise_debut, balise_fin, couleur):
    assert couleur in balise_debut.couleurs_balise
    assert couleur in balise_fin.couleurs_balise
    balise = balise_debut
    chemin = [balise]
    while balise.num_balise != ...:
        for b in balise.voisines:
            if couleur in ... and b not in ...:
                balise = ...
                chemin.append(balise)
    return [b.num_balise for b in chemin]

Question 5

#

Compléter itineraire pour relier les balises de début et de fin en suivant la couleur donnée.

Indice

Le test de couleur porte sur le voisin ; le test de répétition porte sur le chemin.

Comprendre la correction
def itineraire(balise_debut, balise_fin, couleur):
    assert couleur in balise_debut.couleurs_balise
    assert couleur in balise_fin.couleurs_balise
    balise = balise_debut
    chemin = [balise]
    while balise.num_balise != balise_fin.num_balise:
        for b in balise.voisines:
            if couleur in b.couleurs_balise and b not in chemin:
                balise = b
                chemin.append(balise)
    return [b.num_balise for b in chemin]

Les compléments sont balise_fin.num_balise, b.couleurs_balise, chemin et b. Le chemin stocke des objets afin de tester qu’un voisin n’a pas déjà été choisi ; la compréhension finale transforme ces objets en numéros. Pour vert, on suit 1,3,6,7,10,12. Pour noir, on suit 1,3,6,4,5,10,12.

Ce squelette fonctionne pour les parcours colorés de ce graphe, qui fournissent la continuation attendue. Il ne constitue pas une recherche de chemin générale : dans un autre graphe, plusieurs voisins admissibles ou une impasse pourraient provoquer un mauvais choix ou une boucle sans progrès. Une version générale devrait prévoir un échec et éventuellement un retour en arrière.

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

Question 6

#

Donner l’ordre d’un parcours en profondeur depuis 1, en choisissant toujours le plus petit numéro parmi les voisins non visités. Les couleurs ne comptent plus.

Indice

À une impasse, revenez au dernier sommet ayant encore un voisin non découvert.

Comprendre la correction
[1, 2, 4, 5, 10, 7, 6, 3, 11, 9, 8, 12]

On s’enfonce jusqu’à 3, qui ne possède plus de voisin non découvert. On revient alors à 6 pour visiter 11, puis 9,8 et 12. La liste donne l’ordre des premières découvertes ; les retours physiques par des balises déjà poinçonnées ne sont pas ajoutés comme de nouvelles visites. Marquer les sommets évite de tourner dans les cycles du graphe.

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

PartieB : les couleurs sont ignorées. Chaque liaison porte désormais un temps moyen en minutes. Les listes de voisines deviennent des listes de tuples ; par exemple balise 1.voisines=[(balise 2,5),(balise 3,12)].

5121311211561081291647123456789101112
Figure 2 : temps moyens entre balises en minutes
Lire les connexions du schéma
  • 1 relié à 2 : 5
  • 1 relié à 3 : 12
  • 2 relié à 4 : 13
  • 3 relié à 6 : 11
  • 4 relié à 5 : 21
  • 4 relié à 6 : 15
  • 5 relié à 10 : 6
  • 6 relié à 7 : 10
  • 6 relié à 11 : 8
  • 7 relié à 10 : 12
  • 8 relié à 9 : 9
  • 9 relié à 11 : 16
  • 9 relié à 12 : 4
  • 10 relié à 12 : 7

Question 7

#

Remplacer les voisines de balise 4 par les tuples (balise, temps) de la figure 2.

Indice

Lire les poids des trois arêtes incidentes à 4.

Comprendre la correction
balise4.voisines = [(balise2, 13), (balise5, 21), (balise6, 15)]

La première composante reste une instance Balise ; la seconde est un temps moyen en minutes. Les chemins sont plats et les arêtes non orientées : les temps sont les mêmes dans les deux sens.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
def mystere(balise):
    meilleure_balise = None
    mini = -1
    for b, t in balise.voisines:
        if not b.visitee and (mini == -1 or t < mini):
            meilleure_balise, mini = b, t
    return meilleure_balise

Question 8

#

Toutes les balises sont non visitées. Que renvoie mystere(balise 10).num_balise ?

Indice

Comparer les temps, pas les numéros des balises.

Comprendre la correction

Le résultat est 5. Les voisines 5,7 et 12 sont accessibles en 6,12 et 7 minutes ; la durée minimale est 6. mystere renvoie l’objet balise 5, puis l’accès à num_balise fournit l’entier 5. Si aucun voisin n’était disponible, mystere renverraitNone et cet accès à num_balise ne serait plus possible.

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

Question 9

#

Depuis 1 vers 12, donner les balises suivies en choisissant à chaque étape le voisin non visité le plus proche en temps.

Indice

Barrez les voisines déjà visitées avant chaque comparaison.

Comprendre la correction
[1, 2, 4, 6, 11, 9, 12]
DépartChoixTemps
125
2413
4615
6118
11916
9124

Le total de ces décisions locales est61 minutes. À4, on préfère 6 à 5 ; à 6, on préfère 11 à 7 et 3 ; à 9, on préfère 12 à 8. Aucun retour vers une balise visitée n’est autorisé. Ce trajet ne prétend pas être le plus rapide globalement :1-3-6-11-9-12 coûte51 minutes.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
def itineraire_trail(balise_debut, balise_fin):
    balise_debut.visitee = True
    balise = balise_debut
    chemin = [balise]
    while balise_fin not in chemin:
        prochaine = ...
        if prochaine != None:
            ...
        else:
            return None
    return [b.num_balise for b in chemin]

Question 10

#

Compléter itineraire_trail. Si le choix glouton ne permet plus de continuer, renvoyerNone. Avant l’appel, toutes les balises ont visitee=False.

Indice

Trois mises à jour accompagnent chaque voisin retenu : marquage, position, chemin.

Comprendre la correction
def itineraire_trail(balise_debut, balise_fin):
    balise_debut.visitee = True
    balise = balise_debut
    chemin = [balise]
    while balise_fin not in chemin:
        prochaine = mystere(balise)
        if prochaine is not None:
            prochaine.visitee = True
            balise = prochaine
            chemin.append(balise)
        else:
            return None
    return [b.num_balise for b in chemin]

mystere choisit le prochain objet. Il faut alors le marquer, mettre à jour la balise courante et ajouter l’objet au chemin. Oublier le marquage permettrait des retours ; oublier la mise à jour de balise répéterait les choix depuis le même sommet. Le casNone correspond à une impasse pour cette stratégie. Il ne prouve pas qu’aucun autre trajet n’existe dans le graphe.

Le marquage modifie les objets partagés. Il faut donc les réinitialiser avant une nouvelle simulation, conformément à l’hypothèse du sujet. Lorsque départ et arrivée sont identiques, la boucle ne s’exécute pas et la fonction renvoie la liste contenant ce seul numéro.

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

Question 12

#

Donner un avantage et un inconvénient de cette famille d’algorithmes.

Indice

Comparez le temps du parcours glouton à un autre trajet valide.

Comprendre la correction

Avantage : chaque décision locale est simple et n’exige pas d’énumérer tous les itinéraires, ce qui réduit souvent le travail de recherche. Inconvénient : la succession de meilleurs choix locaux ne garantit pas une solution optimale globale, ni même de trouver une solution lorsqu’il en existe une. Ici, le parcours de 61 minutes est plus long que celui de 51 minutes indiqué précédemment. Un exemple concret montre mieux la limite qu’une affirmation générale.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
Une décision locale peut-elle changer toute la course ?Un atelier pour expérimenter

Faites varier le temps du chemin 4-5. Le sportif part de 1 et applique exactement la règle gloutonne sans revisiter une balise. Le tableau expose chaque choix et le temps cumulé.

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

1 → 2 → 4 → 6 → 11 → 9 → 12 :arrivée atteinte.

Les balises déjà visitées sont exclues. Modifier un seul coût peut changer le choix local à4 et toute la suite du trajet.

DepuisVersDuréeCumul
1255
241318
461533
611841
1191657
912461

Une stratégie locale peut être rapide à exécuter sans optimiser le résultat global.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Soupe de nombres : trouver une chaîne minimale avec une file

Le jeu Soupe de nombres de Javier Dominguez Cruz est adapté ici en n’utilisant que l’addition. Douze cartes forment trois lignes et quatre colonnes. On veut obtenir une cible en additionnant une chaîne de cartes voisines horizontalement, verticalement ou diagonalement, sans utiliser deux fois la même carte. La meilleure chaîne possède le moins de cartes ; une seule peut suffire. Deux cartes de même valeur restent deux positions distinctes.

6711681467129815
Plateau de l’énoncé ; cible 27

Trois exemples de la figure sont reconstruits ci-dessous. Les deux premiers ont trois cartes, la longueur minimale pour 27.

6711681467129815
27=6+7+14 ; positions(0,0),(0,1),(1,1)
6711681467129815
27=15+6+6 ; positions(2,3),(1,2),(0,3)
6711681467129815
27=6+7+6+8 ; quatre cartes

12+15 n’est pas une chaîne : ces positions ne sont pas voisines.9+9+8 réutilise la seule carte 9 et est donc interdit, indépendamment de sa somme. Le plateau est une liste de lignes ; on étend ensuite le programme à des dimensions quelconques.

plateau = [[6,7,11,6], [8,14,6,7], [12,9,8,15]]

Question 1

#

Que vaut v après v=plateau[1][2] ?

Indice

Lire d’abord la ligne, ensuite la colonne.

Comprendre la correction

v vaut 6 : la ligne d’indice 1 est [8,14,6,7], puis l’indice 2 y désigne le troisième élément. Les deux indices commencent à 0.

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

Le squelette impose la compréhension [cartes[i+j*n] for j in range(m)] ; seules l’initialisation du plateau, la boucle externe et le return sont à compléter.

Question 2

#

Compléter plateau_init(n, m, cartes), avec n, m positifs et au moins n×m cartes. shuffle mélange la liste en place ; les cartes sont choisies sans remise.

Indice

La boucle externe crée n lignes et la compréhension m cases par ligne.

Comprendre la correction
def plateau_init(n, m, cartes):
    shuffle(cartes)
    plateau = []
    for i in range(n):
        plateau.append([cartes[i + j*n] for j in range(m)])
    return plateau

Les indices utilisés sont i+j×n, pour 0≤i< n et 0≤j< m. Ils couvrent une seule fois les n×m premières positions, en remplissant les colonnes par paquets de n. C’est une disposition différente d’un parcours ligne par ligne, mais elle respecte les dimensions et le tirage sans remise. Deux cartes peuvent porter la même valeur : sans remise signifie que l’on ne réutilise pas une même position de la liste de cartes.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
def cartes_voisines(n, m, i, j):
    voisines = []
    for i2 in range(i-1, i+2):
        for j2 in ...:
            if (i2,j2) != (i,j) and i2 in range(n) and ...:
                voisines.append(...)
    return ...

Question 3

#

Compléter cartes_voisines(n, m, i, j), qui renvoie les coordonnées des voisines de(i, j), diagonales comprises.

Indice

Le carré 3×3 contient aussi le centre et parfois des cases extérieures.

Comprendre la correction
def cartes_voisines(n, m, i, j):
    voisines = []
    for i2 in range(i - 1, i + 2):
        for j2 in range(j - 1, j + 2):
            if (i2, j2) != (i, j) and i2 in range(n) and j2 in range(m):
                voisines.append((i2, j2))
    return voisines

Les deux boucles explorent le carré 3×3 centré sur la carte. Trois filtres écartent la carte elle-même, les lignes hors plateau et les colonnes hors plateau. Un coin d’un plateau assez grand possède trois voisines ; une carte intérieure en possède huit. Le tuple(i 2, j 2) conserve ensemble ligne et colonne.

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

Question 4

#

Donner la valeur de e 1=[(2,1),(1,0),(0,0),(0,1)], e 2=[(1,1),(1,2),(2,0)] et e 3=[(0,2),(0,3),(1,4)], lorsque la chaîne existe.

Indice

Une chaîne doit respecter à la fois les limites et la proximité de chaque paire consécutive.

Comprendre la correction

e 1 est valide et vaut 9+8+6+7=30. e 2 n’est pas valide : de(1,2) à(2,0), on se déplace de deux colonnes, donc les cartes ne sont pas voisines. e 3 n’est pas valide : la colonne 4 n’existe pas dans un plateau dont les colonnes sont indexées 0 à 3. Il ne faut pas simplement additionner les valeurs sans vérifier les règles de déplacement.

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

Question 5

#

Écrire chaine_evalue(plateau, chaine), qui renvoie la somme d’une chaîne. La chaîne[(0,0),(0,1),(1,2),(2,2)] donne 27.

Indice

La chaîne contient des coordonnées, pas directement les valeurs à additionner.

Comprendre la correction
def chaine_evalue(plateau, chaine):
    total = 0
    for i, j in chaine:
        total += plateau[i][j]
    return total

Le déballage for i,j in chaine rend les coordonnées explicites ; on ajoute la valeur de chaque case. La fonction suppose une chaîne valide comme son contrat le demande. Elle ne vérifie ni les voisinages ni les répétitions : ces propriétés sont garanties par la construction dans explore. La chaîne vide donnerait 0, même si les solutions du jeu commencent par une carte.

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

Question 6

#

Pourquoi utiliser un parcours en largeur plutôt qu’en profondeur pour obtenir la chaîne la plus courte ?

Indice

Quelle longueur ont les éléments retirés de la file avant de passer au niveau suivant ?

Comprendre la correction

La file traite d’abord toutes les chaînes d’une carte, puis de deux cartes, puis de trois, etc. La première chaîne dont la somme atteint la cible possède donc une longueur minimale. Une exploration en profondeur peut s’enfoncer dans une chaîne longue et trouver une solution avant d’avoir essayé une autre branche courte. La largeur ne minimise pas la somme : elle organise les candidats par nombre de cartes.

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

L’interface fournie est :file_init() crée une file vide ;file_ajoute(f, x) enfile ;file_retire(f) retire le plus ancien et lève ValueError si la file est vide ;file_est_vide(f) teste son état. Les blancs concernent l’initialisation, la condition de boucle, la cible comparée et le chemin contrôlé.

Question 7

#

Compléter explore à l’aide de file_init, file_ajoute, file_retire et file_est_vide. La fonction doit renvoyer une chaîne minimale ouNone.

PythonTrouver la somme avec le moins de cartesÉcrivez votre solution et mettez-la à l’épreuve

Écrivez explore(plateau, cible). Une chaîne commence sur n’importe quelle case, progresse vers une des huit voisines (diagonales comprises), sans revisiter une case. Renvoyez une liste de coordonnées dont la somme vaut cible, avec le moins de cases possible, ou None si aucune chaîne ne convient. Les helpers de file et de voisinage sont fournis. Plusieurs réponses minimales peuvent être correctes. Les petites grilles de test sont des exemples complémentaires.

def explore(plateau, cible):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Une carte suffit ailleurs sur le plateau : Tous les départs doivent être explorés avant leurs prolongements. La case 4 donne une chaîne de longueur 1.
  • La diagonale est un vrai voisinage : Les cases 1 et 4 se touchent en diagonale et suffisent pour faire 5.
  • Trois cases, aucune répétition : Les trois cases d’une ligne donnent 10. Le chemin doit respecter les adjacences.
  • La cible est impossible : Aucune chaîne parmi les cases 2 et 4 ne totalise 3 ; une file épuisée doit mener à None.
  • Ne pas réutiliser une carte : Faire 2 + 3 + 2 serait possible seulement en reprenant la première case, ce qui est interdit.
  • Les branches gardent leur propre historique : 11 demande 1 + 2 + 8. Le tableau ne change pas et une visite dans un candidat ne condamne pas la case pour tous les autres.
Indice

La file contient des chemins ; le test de répétition porte sur le chemin courant.

Comprendre la correction
def explore(plateau, cible):
    n, m = len(plateau), len(plateau[0])
    a_visiter = file_init()
    for i in range(n):
        for j in range(m):
            file_ajoute(a_visiter, [(i, j)])
    while not file_est_vide(a_visiter):
        chemin = file_retire(a_visiter)
        if chaine_evalue(plateau, chemin) == cible:
            return chemin
        i0, j0 = chemin[-1]
        for i, j in cartes_voisines(n, m, i0, j0):
            if (i, j) not in chemin:
                file_ajoute(a_visiter, chemin + [(i, j)])
    return None

Toutes les positions de départ sont mises en file avant la boucle. Chaque élément de la file est un chemin complet ; on le prolonge par chaque voisine non encore dans ce chemin. Un marquage global des cases serait incorrect ici : la même case peut appartenir à plusieurs candidats avec des sommes et des historiques différents. Seule sa répétition dans une même chaîne est interdite.

L’expression chemin + [(i,j)] crée une nouvelle liste pour chaque branche. Modifier chemin en place ajouterait successivement plusieurs voisines au même candidat et mélangerait les branches. La file FIFO garantit l’ordre des longueurs. La terminaison vient du nombre fini de chemins sans répétition, mais leur quantité peut être très grande ; ce code ne promet pas une recherche rapide pour tous les plateaux.

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

Question 8

#

Sans écrire de code, expliquer comment modifier explore pour renvoyer la liste de tous les chemins solutions.

Indice

Remplacer le retour immédiat par une mémorisation, puis épuiser la recherche.

Comprendre la correction

Créer une liste de solutions vide avant la boucle. Lorsqu’un chemin atteint la cible, l’ajouter à cette liste au lieu de le retourner immédiatement. Continuer le traitement de la file jusqu’à son épuisement, puis retourner la liste, éventuellement vide. On conserve la contrainte de non-répétition dans chaque chemin.

La question demande tous les chemins solutions, pas seulement les plus courts. Sur des cartes strictement positives comme le plateau donné, prolonger une somme déjà égale à la cible ne peut plus fournir une autre solution, mais cette coupure doit être justifiée par la positivité. L’extension parle d’entiers : avec des zéros ou des négatifs, il peut être nécessaire de continuer à prolonger une solution pour en trouver une plus longue. Ne pas appliquer un élagage sans vérifier les valeurs autorisées.

Voir la question dans le sujet PDF, p. 11 (nouvel onglet)
Une cible, quelle chaîne minimale ?Un atelier pour expérimenter

Changez la cible sur le plateau réel. Le moteur explore les chaînes par longueur croissante ; il peut écarter une somme trop grande ici parce que toutes les cartes sont strictement positives.

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

Solution minimale en3 cartes.

Les voisins incluent les diagonales. Une même position ne peut pas réapparaître ; deux positions portant le même nombre restent distinctes.

LigneColonneCarteSomme
0066
01713
111427

La file garantit la minimalité en longueur ; les règles de voisinage garantissent la validité de la chaîne.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Radiosondes : nettoyer les données, chercher dans un ABR et interroger SQL

Un club de radioamateurs retrouve des radiosondes après leur vol et répertorie leurs positions. Chaque mesure est horodatée et géolocalisée. Le contexte cite sondehub.org comme site de suivi, mais aucune connexion externe n’est nécessaire pour résoudre les questions. Les données ci-dessous sont celles du sujet ; le dictionnaire enregistrement sert aux premières questions.

enregistrement = {'num_serie':623, 'altitude':1150.0,
    'datetime':'2024-06-27T23:36:01.000000Z',
    'latitude':38.38825, 'longitude':27.09004}

Question 1

#

Écrire l’instruction console qui affiche 38.38825 depuis enregistrement.

Indice

Repérez la clé associée à cette valeur.

Comprendre la correction
print(enregistrement['latitude'])

Le nom de clé est une chaîne entre guillemets. L’accès utilise le dictionnaire, sans supposer une position numérique des champs.

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

La chaîne ISO8601 est formée de la date, du séparateurT, de l’heure et de l’indication de fuseauZ. La fonction fournie est :

def nettoyage_datetime(chaine):
    date = ''
    horaire = ''
    for i in range(10):
        date += chaine[i]
    for i in range(8):
        horaire += chaine[i + 11]
    return date, horaire

Question 2

#

Expliquer ce que renvoie nettoyage_datetime sur 2024-06-27T23:36:01.000000Z.

Indice

Le second parcours commence onze caractères après le début.

Comprendre la correction
('2024-06-27', '23:36:01')

La première boucle copie les indices 0 à 9, soit la date. La seconde copie les indices 11 à 18, en sautant le séparateurT à l’indice 10. Le résultat est un tuple de deux chaînes ; les fractions de seconde et l’indicationZ ne sont pas conservées. Le code extrait un format fixe, sans convertir de fuseau horaire.

Voir la question dans le sujet PDF, p. 13 (nouvel onglet)
frames = [
  {
    "num_serie": 623,
    "altitude": 620.1,
    "datetime": "2024-07-01T01:58:55.000Z",
    "latitude": 43.20223,
    "longitude": -72.05708
  },
  {
    "num_serie": 500,
    "altitude": 6375.75,
    "datetime": "2024-07-01T23:35:32.000Z",
    "latitude": -20.8759,
    "longitude": 55.58805
  },
  {
    "num_serie": 623,
    "altitude": 622.6,
    "datetime": "2024-07-01T01:59:01.000Z",
    "latitude": 43.20224,
    "longitude": -72.05711
  },
  {
    "num_serie": 700,
    "altitude": 60000,
    "datetime": "2024-07-01T11:51:23.000000Z",
    "latitude": 40.87873,
    "longitude": 29.09587
  }
]

Question 3

#

Donner len(frames) et len(frames[1]) sur l’extrait.

Indice

Les deux appels ne portent pas sur le même type de conteneur.

Comprendre la correction

len(frames) vaut 4 : il y a quatre enregistrements. len(frames[1]) vaut 5 : le second enregistrement est un dictionnaire possédant cinq clés. La longueur d’un dictionnaire compte ses associations, pas le nombre de caractères de ses valeurs.

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

Question 4

#

Écrire detecter_anomalie : renvoyer si l’altitude est négative ou strictement supérieure à35000m.

Indice

Tester explicitement les deux bornes et les valeurs juste à l’extérieur.

Comprendre la correction
def detecter_anomalie(enregistrement):
    return enregistrement['altitude'] < 0 or enregistrement['altitude'] > 35000

Les deux anomalies sont alternatives, d’où or. Les valeurs 0 et 35000 restent acceptées : les comparaisons sont strictes. L’exemple de la sonde 700 à60000m renvoieTrue. Utiliser and rendrait la condition impossible, puisqu’une même altitude ne peut pas être à la fois négative et supérieure à 35000.

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

Question 5

#

Écrire liste_num_serie qui renvoie chaque numéro de sonde une seule fois, dans l’ordre de première rencontre. Sur frames :[623,500,700].

Indice

Tester l’appartenance avant append.

Comprendre la correction
def liste_num_serie(frames):
    numeros = []
    for enregistrement in frames:
        numero = enregistrement['num_serie']
        if numero not in numeros:
            numeros.append(numero)
    return numeros

La liste numeros mémorise les identifiants déjà rencontrés. La seconde mesure de 623 n’ajoute rien, tandis que 500 et 700 créent de nouvelles entrées. On ne modifie pas frames. Un ensemble éliminerait aussi les doublons, mais le retour demandé conserve l’ordre des premières apparitions ; cette version le rend explicite.

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

Les coordonnées sont des tuples(latitude, longitude). Exemple :distance_haversine((46.815,6.943),(47.049,7.52828)) donne 51.5 km. Les déplacements de référence sont :

[(46.8125,6.9433),(46.91498,7.23039),(47.00661,7.48054)]
[(46.8125,6.9433),(46.91498,7.23039),(47.00661,7.48054),(47.2512,7.5201)]
def distance_totale(dep):
    total = 0
    for i in range(...):
        ...
    return total

Question 6

#

Compléter distance_totale(dep) en additionnant les distances entre positions successives. distance_haversine(point 1, point 2) est fournie et renvoie des kilomètres.

Indice

Comptez les intervalles entre les points, pas les points.

Comprendre la correction
def distance_totale(dep):
    total = 0
    for i in range(len(dep) - 1):
        total += distance_haversine(dep[i], dep[i + 1])
    return total

Une trajectoire de n positions possède n−1 segments consécutifs. Le dernier indice utilisé dans la boucle vaut n−2, de sorte que i+1 reste au plus n−1. On ne relie pas directement chaque point au premier : cela mesurerait autre chose que le chemin parcouru. Les trajectoires vides ou d’une seule position donnent naturellement 0.

Avec les exemples fournis, les trois premières positions donnent 46,1 km ; l’ajout de la quatrième donne 73,5 km. La fonction ne doit pas reconvertir cette unité ni arrondir chaque segment sans que le contrat de distance_haversine le demande.

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

Chaque Sonde représente un nœud d’ABR, ordonné par num_serie. Les attributs gauche et droit référencent des sous-arbres ; l’arbre vide est un objet Sonde dont tous les attributs valentNone. Le code utilise le nom droit, même si une phrase de l’énoncé emploie droite.

class Sonde:
    def __init__(self, num_serie, latitude, longitude, date, gauche, droit):
        self.num_serie = num_serie
        self.latitude = latitude
        self.longitude = longitude
        self.date = date
        self.gauche = gauche
        self.droit = droit
    def est_vide(self):
        return self.num_serie is None

Question 7

#

Créer sonde 623 avec numéro 623, latitude 38.38825, longitude 27.09004 et date 2024-06-27.

Indice

Distinguez None et un objet représentant un arbre vide.

Comprendre la correction
sonde623 = Sonde(623,38.38825,27.09004,'2024-06-27',
    Sonde(None,None,None,None,None,None),
    Sonde(None,None,None,None,None,None))

Les enfants sont des arbres vides représentés par des instances Sonde, conformément à la convention annoncée. Mettre directement None comme enfants d’un nœud non vide ferait ensuite échouer les appels récursifs à rechercher sur une branche absente. Les deux instances vides ont leurs six attributs à None.

Voir la question dans le sujet PDF, p. 15, 16 (nouvel onglet)
gauchedroitdroit623500700575
Figure 2 : arbre initial des sondes
Lire les connexions du schéma
  • 623 relié à 500 : gauche
  • 623 relié à 700 : droit
  • 500 relié à 575 : droit

Question 8

#

Insérer successivement 900,650 puis 300 dans l’arbre de la figure 2.

Indice

Comparer à la racine, puis recommencer dans le sous-arbre choisi.

Comprendre la correction
gauchedroitdroitgauchegauchedroit623500700575300650900
Arbre après les insertions 900,650,300
Lire les connexions du schéma
  • 623 relié à 500 : gauche
  • 623 relié à 700 : droit
  • 500 relié à 575 : droit
  • 500 relié à 300 : gauche
  • 700 relié à 650 : gauche
  • 700 relié à 900 : droit

900 est supérieur à 623 puis 700 : il devient le fils droit de 700.650 est supérieur à 623 mais inférieur à 700 : il devient le fils gauche de 700.300 est inférieur à 623 puis 500 : il devient le fils gauche de 500. L’ordre d’insertion ne déplace pas les nœuds existants, en particulier 575 reste le fils droit de 500.

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

Question 9

#

Quel parcours lit les numéros dans l’ordre croissant : préfixe, infixe, suffixe ou largeur ?

Indice

La racine doit être lue entre les valeurs inférieures et supérieures.

Comprendre la correction

Le parcours infixe : sous-arbre gauche, racine, sous-arbre droit. Dans un ABR, tous les numéros du sous-arbre gauche sont plus petits que la racine et ceux du droit plus grands. L’ordre obtenu après les insertions est 300,500,575,623,650,700,900.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
def rechercher(..., numero):
    if self.est_vide():
        return ...
    if numero == ...:
        return self
    elif numero < self.num_serie:
        return self.....rechercher(numero)
    else:
        return self.droit.rechercher(numero)

Question 10

#

Compléter rechercher(self, numero), qui renvoie l’instance Sonde correspondante ouNone si elle n’existe pas.

Indice

Choisir gauche lorsque numero est inférieur à la racine.

Comprendre la correction
def rechercher(self, numero):
    if self.est_vide():
        return None
    if numero == self.num_serie:
        return self
    elif numero < self.num_serie:
        return self.gauche.rechercher(numero)
    else:
        return self.droit.rechercher(numero)

Le cas vide doit précéder toute comparaison de numéro, car un arbre vide porte num_serie=None. Une égalité renvoie l’objet courant, pas son seul numéro. Sinon, la propriété de l’ABR permet d’écarter un sous-arbre entier. Pour chercher 650, on compare à 623 puis 700, puis on trouve 650 à gauche. Pour un numéro absent, la descente atteint un objet vide et renvoieNone.

La recherche prend un nombre d’étapes proportionnel à la hauteur de l’arbre. Elle est rapide sur un arbre équilibré, mais peut devenir linéaire dans un arbre en chaîne : la structure d’ABR ne garantit pas à elle seule l’équilibrage.

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

Question 11

#

Pourquoi rechercher est-elle récursive ?

Indice

Repérez les appels de la méthode à elle-même sur un autre objet.

Comprendre la correction

La méthode appelle rechercher sur l’un de ses sous-arbres. Le problème est de même nature, mais porte sur une partie strictement plus petite de l’arbre. Les cas de base sont l’arbre vide et le numéro trouvé. La descente termine sur un arbre fini sans cycle.

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

La base InventaireSondesRetrouvees comporte les trois relations ci-dessous. On emploiera les noms exacts affichés dans les tableaux : abonne, sonde,info_recuperation. Les consignes utilisent parfois d’autres graphies ; elles seront signalées lorsque cela affecte l’exécution.

abonne.id_abonnenomprenomemail
32DétoileDianeD. Detoile@nsi.fr
15PetitClaireP. Claire@nsi.fr
24GirardAntoineA. Girard@nsi.fr
16LemoineMartinM. Lemoine@huit.fr
sonde.num_seriemodeleconstructeurdate_lancement
623RS41Vaisala2024-07-10
500M10Météomodem2024-07-05
900M20Météomodem2024-07-17
480RS41Vaisala2024-06-20
810WxRWeathex2024-06-05
info_recuperation.refnum_serieid_abonnedate_recuplatitudelongitude
10900152024-08-0547.9992.549
11500242024-07-0647.1593.151
12623152024-07-1847.25711.974
13810322024-06-1044.37422.448

Question 12

#

Proposer et justifier une clé primaire pour abonne.

Indice

Choisir l’identifiant, puis expliquer les deux contraintes.

Comprendre la correction

id_abonne convient : il identifie chaque membre du club par une valeur unique et non nulle. Les noms et prénoms peuvent être partagés par plusieurs personnes ; leur apparente unicité dans l’extrait ne constitue pas une garantie générale. Une contrainte de clé primaire formalise cette identification dans la base.

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

Question 13

#

Citer les clés étrangères de la relation de récupération.

Indice

Deux relations sont reliées à chaque ligne de récupération.

Comprendre la correction

num_serie référence sonde.num_serie ; id_abonne référence abonne.id_abonne. Elles relient chaque récupération à une sonde et à la personne qui l’a retrouvée. ref identifie la récupération elle-même et joue le rôle de clé primaire ; ce n’est pas la même fonction.

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

Question 14

#

Donner le résultat de SELECT nom, prenom FROM Abonnes WHERE id_abonne>20 sur les extraits.

Indice

Appliquer le filtre aux identifiants tout en contrôlant le nom réel de la table.

Comprendre la correction

Les lignes visées sont celles d’identifiants 32 et 24 : Détoile, Diane et Girard, Antoine. Leur ordre n’est pas garanti sans ORDER BY.

nomprenom
DétoileDiane
GirardAntoine

Attention au nom de table : l’extrait la nomme abonne, tandis que la requête écrit Abonnes au pluriel. Sur une base créée exactement avec le schéma reproduit, cette requête échouerait faute de table correspondante. La requête cohérente est :

SELECT nom, prenom FROM abonne WHERE id_abonne > 20;
Voir la question dans le sujet PDF, p. 18 (nouvel onglet)

Question 15

#

Insérer la récupération de la sonde 480 par Antoine Girard le 2024-07-10, latitude 47.230, longitude 12.244, avec ref=14.

Indice

Retrouver l’identifiant du membre dans la table abonne.

Comprendre la correction
INSERT INTO info_recuperation (ref,num_serie,id_abonne,date_recup,latitude,longitude)
VALUES (14,480,24,'2024-07-10',47.230,12.244);

Antoine Girard porte l’identifiant 24 dans abonne. Les clés 480 et 24 existent déjà dans leurs tables, ce qui permet la création de cette récupération. Les noms de colonnes protègent contre une inversion de latitude et longitude. Le nom InfosRecuperation employé dans la consigne est remplacé par celui du schéma reproduit.

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

Question 16

#

Écrire la requête affichant pour chaque sonde récupérée son numéro de série, le nom de l’abonné et la date de récupération.

SQLRelier les radiosondes récupérées aux abonnésÉcrivez votre solution et mettez-la à l’épreuve

Pour chaque récupération, affichez le numéro de série de la sonde, le nom de l’abonné et la date de récupération. Une sonde sans récupération ne doit pas apparaître.

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

Les cas de test proposés :

  • Récupérations de l’extrait officiel : Les quatre récupérations et les lignes nécessaires sont reprises du sujet. La sonde 480 ne possède aucune récupération dans cet extrait.
  • Cas complémentaire : plusieurs récupérations pour une sonde : Jeu complémentaire pédagogique, distinct des données officielles. La clé de récupération est ref : une sonde peut apparaître dans deux récupérations. Les deux événements et les bons abonnés sont conservés.
Indice

Les deux clés étrangères de la récupération donnent les conditions de jointure.

Comprendre la correction
SELECT s.num_serie, a.nom, r.date_recup
FROM sonde AS s
JOIN info_recuperation AS r ON r.num_serie = s.num_serie
JOIN abonne AS a ON a.id_abonne = r.id_abonne;

info_recuperation sert de relation de liaison : elle désigne à la fois la sonde et l’abonné. Les alias abrègent les noms sans changer les tables. Une sonde sans récupération n’apparaît pas dans cette jointure interne ; cela correspond à l’objectif de référencer les radiosondes retrouvées. Plusieurs récupérations éventuelles produiraient plusieurs lignes, sauf contrainte supplémentaire que le sujet ne fournit pas.

Voir la question dans le sujet PDF, p. 18 (nouvel onglet)
Suivre la recherche d’une sondeUn atelier pour expérimenter

Choisissez un numéro recherché dans l’ABR après les trois insertions. Observez chaque comparaison et le sous-arbre éliminé jusqu’au résultat.

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

Sonde 650 trouvée.

Chaque comparaison écarte un sous-arbre entier. L’absence est constatée dans un objet représentant un arbre vide.

Racine examinéeComparaisonAction
623Cible plus grandeAller à droite
700Cible plus petiteAller à gauche
650ÉgalitéRenvoyer cette instance

La recherche exploite l’ordre de l’ABR ; sa correction dépend aussi de la représentation des arbres vides.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Préciser l’objectif de chaque parcours avant de choisir sa structure de données.
  • Distinguer les objets stockés et les identifiants affichés.
  • Contrôler les noms et les conventions du schéma avant d’exécuter une requête.

Retrouver ces notions dans d’autres sujets

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

Énoncé : sujet 25-NSIJ1G11 (PDF) · Publication d’origine (nouvel onglet). Corrigé et explications pédagogiques proposés par Sofien.