Épreuve écrite · 2025 · Jour 1

Bac NSI 2025 Nouvelle-Calédonie jour 1

Comment des routeurs finissent-ils par trouver les bons chemins ? Comment relier des données de randonnées ? Comment transformer un jeu de calcul en recherche de masques binaires ? Ce sujet fait travailler la construction progressive d’une solution : les petites fonctions et les conventions de départ deviennent les briques des dernières questions.

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

Faire converger les tables de routage avec RIP

Cet exercice porte sur les graphes, les protocoles réseaux et la programmation orientée objet. Les sommets du graphe sont des routeurs ; ses arêtes sont les liaisons entre eux. On souhaite d’abord parcourir ce graphe en largeur depuis A, puis simuler les échanges de tables de routage.

Partie A : du graphe aux routes

BACEDF
Figure 1 : routeurs A à F et leurs liaisons
Lire les connexions du schéma
  • A relié à B
  • A relié à C
  • A relié à E
  • C relié à D
  • C relié à E
  • D relié à E
  • D relié à F

Question 1

#

Dire lequel des parcours suivants est un parcours en largeur depuis A, en justifiant : ABCDEF, ABCEDF, ABCDFE.

Indice

Commencez par encercler tous les voisins directs de A.

Comprendre la correction

ABCEDF convient. Les sommets à distance 1 de A sont B, C et E ; ils doivent tous être visités avant D, qui est à distance 2. F est ensuite à distance 3. Dans les deux autres propositions, D apparaît avant E alors que E est un voisin direct de A.

Un parcours en largeur explore par couches de distance. L’ordre de B, C et E pourrait varier avec l’ordre des voisins ; parmi les trois séquences proposées, une seule respecte les couches.

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

Le sujet résume RIP en trois règles. a. Initialisation : chaque routeur ajoute ses voisins directs, accessibles en un saut, sans routeur intermédiaire. b. Transmission : chaque routeur envoie régulièrement sa table à ses voisins. c. Mise à jour : une route inconnue est ajoutée ; une route plus longue est ignorée ; une route strictement plus courte remplace l’ancienne. Une égalité ne déclenche pas de remplacement dans cette règle.

Une itération correspond à la réception des tables de tous les voisins, puis à la mise à jour. Après stabilisation, les tables ne changent plus. On exclut les pannes de routeur et les coupures de liaison. La table initiale de A est :

DestinationNombre de sautsProchain routeur
B1-
C1-
E1-

Question 2

#

Donner la table de routage de F à l’initialisation du protocole RIP.

Indice

Ne mettez dans la table que les voisins directs visibles sur le graphe.

Comprendre la correction
DestinationNombre de sautsProchain routeur
D1-

F n’a qu’un voisin : D. Au départ, il ne connaît donc aucune route vers A, B, C ou E. Le tiret signifie qu’aucun routeur intermédiaire n’est nécessaire ; dans la classe de la partie B, cette convention sera représentée par None.

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

Question 3

#

Donner la table de routage de A après une première itération de RIP. Deux réponses sont possibles.

Indice

Une annonce reçue d’un voisin coûte un saut de plus que ce que ce voisin annonce.

Comprendre la correction
DestinationNombre de sautsProchain routeur
B1-
C1-
E1-
D2C ou E

A reçoit de C une annonce « D est à 1 saut » et ajoute son propre saut vers C : D est à 2 sauts via C. L’annonce d’E donne le même coût. Comme le sujet ne remplace une route que par une route strictement plus courte, la première des deux annonces retenue décide du prochain routeur.

À ce premier échange synchrone, C et E n’annoncent encore que leurs voisins directs. Aucun des deux n’est relié directement à F : A ne connaît donc pas encore de route vers F. N’ajoutez pas un chemin que vous voyez sur le dessin avant que l’information ait pu se propager.

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

Question 4

#

Donner le numéro de l’itération de RIP à partir duquel les tables des routeurs du réseau ne varient plus.

Indice

Cherchez la plus grande distance minimale entre deux routeurs, pas le plus long chemin avec des détours.

Comprendre la correction

Les routes définitives sont acquises à l’issue de la troisième itération. Le trajet le plus court le plus long relie B à F : B → A → C → D → F, soit 4 sauts (on peut passer par E plutôt que C). L’initialisation connaît déjà les routes à un saut ; chaque échange synchrone gagne un saut : 2 sauts après l’itération 1, 3 après l’itération 2 et 4 après l’itération 3.

Pour lever toute ambiguïté de comptage : la troisième itération peut encore modifier une table ; la quatrième est la première itération qui constate qu’aucune modification n’est nécessaire. « Stabilisé après trois échanges » et « premier échange sans modification : le quatrième » décrivent donc le même déroulement.

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

Question 5

#

On relie maintenant les routeurs E et F. Donner la nouvelle table de routage de A après stabilisation de RIP. Deux réponses sont possibles.

Indice

Comparez le trajet vers F avant et après l’ajout de E-F.

Comprendre la correction
DestinationNombre de sautsProchain routeur
B1-
C1-
E1-
D2C ou E
F2E

La nouvelle arête crée A → E → F, de coût 2. D reste à deux sauts via C ou E ; les trois routes directes restent de coût 1. La nouvelle liaison n’est pas un routeur supplémentaire : on compte toujours les arêtes traversées.

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

Partie B : des objets qui échangent des routes

Chaque objet Routeur possède nom, une chaîne qui l’identifie, et voisins, une liste d’objets Routeur directement connectés. nb_sauts associe à chaque routeur accessible sa distance en sauts. prochain associe à chaque destination le premier routeur d’un chemin minimal ; pour une liaison directe, la valeur choisie par le sujet est None. Les routeurs sont initialement déconnectés :

class Routeur:
    def __init__(self, nom):
        self.nom = nom
        self.voisins = []
        self.nb_sauts = {}
        self.prochain = {}
A = Routeur('A')
B = Routeur('B')
C = Routeur('C')
D = Routeur('D')
E = Routeur('E')
F = Routeur('F')
liste_routeurs = [A, B, C, D, E, F]

La méthode relie deux routeurs et ne fait rien s’ils sont déjà connectés. Les lignes à compléter sont :

def relie(self, autre):
    if autre not in self.voisins:
        self.voisins.append(...)
        self.nb_sauts[autre] = ...
        self.prochain[autre] = ...
        if self not in autre.voisins:
            autre.relie(...)

Question 6

#

Recopier et compléter le code de la méthode relie.

Indice

Les deux extrémités doivent connaître la liaison, avec un coût direct de 1.

Comprendre la correction
def relie(self, autre):
    if autre not in self.voisins:
        self.voisins.append(autre)
        self.nb_sauts[autre] = 1
        self.prochain[autre] = None
        if self not in autre.voisins:
            autre.relie(self)

Le voisin est l’objet autre, pas son nom. Il devient aussi une clé des dictionnaires : sa distance vaut 1 et son prochain routeur intermédiaire vaut None. Le dernier appel construit la liaison dans l’autre sens.

Pourquoi la récursion s’arrête-t-elle ? Avant l’appel réciproque, autre a déjà été ajouté à self.voisins. Lorsque les rôles sont inversés, le test final voit que la première moitié de la liaison existe. Il ne déclenche pas une troisième construction. Le premier test empêche également les doublons lors d’un nouvel appel identique.

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

Question 7

#

Écrire la méthode relie_liste de la classe Routeur qui prend en paramètre une liste lst et relie self à chacun de ses routeurs. Par exemple, A.relie_liste([B, C, E]) crée aussi les liaisons en sens inverse.

Indice

La méthode écrite à la question précédente réalise déjà toutes les mises à jour utiles.

Comprendre la correction
def relie_liste(self, lst):
    for routeur in lst:
        self.relie(routeur)

La boucle parcourt les objets de la liste et délègue chaque connexion à relie. Il serait fragile de ne modifier que voisins : on oublierait les deux dictionnaires et la connexion inverse. Réutiliser une méthode dont le contrat est déjà établi évite cette duplication.

Si lst est vide, la boucle n’effectue aucun appel. Si un routeur y figure deux fois, relie empêche les voisins en double.

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

Question 8

#

Après A.relie_liste([B, C, E]), écrire les instructions manquantes pour obtenir le graphe de la figure 1.

Indice

Cochez les trois arêtes déjà créées, puis parcourez les quatre restantes.

Comprendre la correction
C.relie_liste([D, E])
D.relie_liste([E, F])

A-B, A-C et A-E existent déjà. Il reste C-D, C-E, D-E et D-F. Ces deux appels les créent dans les deux sens ; aucun appel concernant B seul n’est nécessaire. D’autres regroupements sont possibles si et seulement si les sept arêtes du graphe sont présentes, sans arête supplémentaire.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)
def met_a_jour_table(self, autre):
    for r in autre.nb_sauts:
        if r != self:
            if (r not in self.nb_sauts or
                    self.nb_sauts[r] > ...):
                self.nb_sauts[r] = ...
                self.prochain[r] = ...

Question 9

#

Recopier et compléter le code de la méthode met_a_jour_table qui applique la règle c.

Indice

Le prochain routeur vu par self est son voisin autre, pas le prochain routeur annoncé par autre.

Comprendre la correction
def met_a_jour_table(self, autre):
    for r in autre.nb_sauts:
        if r != self:
            if (r not in self.nb_sauts or
                    self.nb_sauts[r] > autre.nb_sauts[r] + 1):
                self.nb_sauts[r] = autre.nb_sauts[r] + 1
                self.prochain[r] = autre

Pour atteindre r en passant par autre, on paie un saut jusqu’à ce voisin, puis les autre.nb_sauts[r] sauts qu’il annonce. Les trois compléments sont donc autre.nb_sauts[r] + 1, la même expression, puis autre.

Le test r != self évite d’ajouter une route vers soi via un détour. Le or est ici utile : si la destination est inconnue, Python n’évalue pas l’accès self.nb_sauts[r] qui provoquerait une erreur. Pour une destination connue, la comparaison doit être stricte afin de ne pas changer de prochain routeur à coût égal.

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

Question 10

#

Écrire la méthode itere_rip qui met à jour la table de self à partir de celle de chacun de ses voisins.

Indice

Un voisin est un objet de self.voisins ; sa table est traitée par met_a_jour_table.

Comprendre la correction
def itere_rip(self):
    for voisin in self.voisins:
        self.met_a_jour_table(voisin)

On reçoit une annonce de chaque objet contenu dans self.voisins. La méthode précédente sait comparer les routes ; cette méthode organise seulement la réception de toutes les annonces. Il faut parcourir les voisins, et non toutes les destinations déjà présentes dans la table, qui ne sont pas nécessairement directement connectées.

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

Question 11

#

Écrire une fonction qui prend une liste de routeurs l_routeurs et réalise une itération de RIP pour tous ses routeurs.

Indice

Parcourez la liste et appelez une fois la méthode de chaque objet.

Comprendre la correction
def iteration_rip(l_routeurs):
    for routeur in l_routeurs:
        routeur.itere_rip()

Il s’agit d’une fonction indépendante de la classe : elle ne reçoit pas de paramètre self. Chaque objet exécute sa propre méthode de réception.

Cette traduction met les tables à jour en place et dans l’ordre de la liste. Un routeur traité plus tard peut lire une table déjà améliorée durant ce même passage. Le nombre de passages du programme peut donc différer du nombre d’échanges synchrones de la partie A ; les routes finales restent celles de plus petit nombre de sauts.

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

Question 12

#

On suppose désormais que met_a_jour_table renvoie True si elle a modifié la table, et False sinon. Modifier itere_rip pour qu’elle renvoie True si au moins une réception a changé sa table.

Indice

Cumulez les changements avec un drapeau qui ne redevient pas faux au milieu de la boucle.

Comprendre la correction
def itere_rip(self):
    modifie = False
    for voisin in self.voisins:
        if self.met_a_jour_table(voisin):
            modifie = True
    return modifie

Le drapeau part de False. Dès qu’un voisin provoque une amélioration, il devient True et doit le rester jusqu’au retour. Les voisins suivants sont néanmoins tous traités. Une réception sans changement ne doit pas effacer le changement précédent.

Attention au court-circuit : modifie = modifie or self.met_a_jour_table(voisin) cesse d’appeler la méthode dès que modifie vaut True. Le bloc if proposé garantit au contraire que chaque annonce est réellement examinée.

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

Le programme principal a créé A, B, C, D, E, F, la liste liste_routeurs et les connexions de la figure 1. On utilise les versions des méthodes qui renvoient un booléen.

Question 13

#

Compléter le programme principal pour mettre à jour tous les routeurs jusqu’à ce qu’aucune table n’ait besoin de changer. Il n’est pas demandé de réécrire les connexions.

Indice

Un passage supplémentaire permet de constater que la stabilisation est atteinte.

Comprendre la correction
modifie = True
while modifie:
    modifie = False
    for routeur in liste_routeurs:
        if routeur.itere_rip():
            modifie = True

Le premier True force un premier passage. Au début de chaque passage, on remet le drapeau à False ; un seul changement suffit à programmer un passage supplémentaire. La boucle s’arrête uniquement après avoir testé tous les routeurs sans obtenir de modification.

Dans ce réseau fini et sans panne, les mises à jour ajoutent des destinations ou diminuent strictement un nombre entier de sauts. On ne peut donc pas améliorer indéfiniment les tables. N’écrivez pas un return au milieu de ce programme principal et ne réinitialisez pas le drapeau pour chaque routeur.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
Voir une route se propager de voisin en voisinUn atelier pour expérimenter

Choisissez un routeur, puis le nombre d’échanges synchrones depuis l’initialisation. Ajoutez la liaison E-F pour voir quand une route plus courte apparaît. Les égalités sont départagées par l’ordre alphabétique des voisins dans cette simulation.

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

1 destinations connues depuis B.

À l’initialisation, seuls les voisins directs sont connus.

DestinationSautsProchain routeur
A1-
CInconnu-
DInconnu-
EInconnu-
FInconnu-

Le graphe montre tous les chemins à l’observateur ; un routeur, lui, ne connaît que ce que ses voisins lui ont déjà annoncé.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Randonnées : relier des parkings aux lacs avec SQL et Python

Susie recense les randonnées allant d’un parking à un lac. Les altitudes sont des entiers exprimés en mètres. La relation rando contient deux clés étrangères : depart référence parking.idP et arrivee référence lac.idL. Les clés primaires sont idP, idR et idL.

parkingidP : clé primairecommunealtitudecoord_GPSrandoidR : clé primairedepartarriveelacidL : clé primairenomaltitude
Les trois relations du sujet. Les liens relient les clés étrangères de rando aux identifiants de parking et lac.

Les clauses SQL utilisables comprennent SELECT, FROM, WHERE avec AND et OR, JOIN ... ON, UPDATE, INSERT, DELETE, DISTINCT et ORDER BY. Voici les extraits fournis :

parking

idPcommunealtitudecoord_GPS
1Chamonix1026(45.98;6.89)
2Argentiere1429(45.99;6.92)
3Passy600(45.92;6.72)
4Passy1181(45.95;6.71)
5Nevache2022(45.05;6.52)

rando

idRdepartarrivee
111
221
312
433

lac

idLnomaltitude
1Lac Blanc2354
2Lacs Noirs2564
3Lac Vert1266
4Lac Rouge2585

Question 1

#

Indiquer ce que renvoie cette requête lorsqu’on l’applique aux extraits :

SELECT nom
FROM lac
WHERE altitude <= 2000;
Indice

La clause SELECT détermine ce que l’on affiche ; WHERE détermine les lignes retenues.

Comprendre la correction
nom
Lac Vert

On conserve les lignes dont l’altitude est au plus 2 000 m, puis on ne projette que la colonne nom. Le Lac Vert est à 1 266 m ; les trois autres lacs de l’extrait dépassent 2 000 m. Le résultat est une table d’une colonne et d’une ligne, pas une altitude.

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

Question 2

#

Indiquer les noms des lacs qu’on peut atteindre depuis le parking de Chamonix d’après cette base de données.

Indice

Suivez successivement parking.idP, rando.depart, rando.arrivee et lac.idL.

Comprendre la correction

Lac Blanc et Lacs Noirs. Le parking de Chamonix porte l’identifiant 1. Dans rando, les lignes de départ 1 mènent aux lacs 1 et 2. Dans lac, ces identifiants correspondent aux deux noms demandés.

Le numéro de la randonnée et celui du lac jouent des rôles différents : la randonnée 3 mène au lac 2. Suivre directement idR jusqu’à idL donnerait une association incorrecte.

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

À partir de cette question, les requêtes portent sur la totalité de la base, pas seulement sur les extraits affichés.

Question 3

#

Donner une requête obtenant les coordonnées GPS des parkings de Passy situés à une altitude strictement comprise entre 800 et 1 000 mètres.

SQLTrouver les bons parkings de PassyÉcrivez votre solution et mettez-la à l’épreuve

Écrivez la requête donnant les coordonnées GPS des parkings de Passy dont l’altitude est strictement comprise entre 800 et 1 000 mètres. Chaque résultat doit correspondre à un parking qui satisfait les trois conditions.

SELECT ...
FROM parking
WHERE ...;

Les cas de test proposés :

  • Extrait du sujet : aucun parking ne convient : Les cinq parkings proviennent de l’extrait officiel. Les deux parkings de Passy sont hors de l’intervalle demandé : une requête correcte renvoie ici une table vide.
  • Cas complémentaire : les deux bornes sont exclues : Jeu complémentaire pédagogique, distinct des données officielles. Les coordonnées sont des repères de test. Seul le parking à 900 m à Passy satisfait les trois conditions ; 800 et 1 000 sont exclus.
  • Cas complémentaire : conserver tous les résultats : Jeu complémentaire pédagogique. Les deux altitudes situées juste à l’intérieur sont retenues ; il ne faut ni renvoyer seulement le premier parking ni remplacer les inégalités par des égalités.
Indice

La question demande une condition ouverte aux deux extrémités.

Comprendre la correction
SELECT coord_GPS
FROM parking
WHERE commune = 'Passy'
  AND altitude > 800
  AND altitude < 1000;

Les trois conditions doivent être vraies simultanément. Les comparaisons sont strictes : un parking situé exactement à 800 m ou 1 000 m ne serait pas retenu. Utiliser BETWEEN 800 AND 1000 serait ici incorrect, car cette expression inclut ses deux bornes.

Aucun des deux parkings de Passy affichés dans l’extrait ne convient, mais on travaille désormais sur la base entière. Cela n’autorise pas à conclure que la requête complète renverrait nécessairement une table vide.

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

Question 4

#

Donner une requête obtenant les noms des lacs atteignables depuis le parking situé à 1 300 mètres d’altitude dans la commune de Cordon. Ce parking existe dans la base.

Indice

Il faut traverser rando : parking et lac ne possèdent pas de clé les reliant directement.

Comprendre la correction
SELECT DISTINCT l.nom
FROM parking AS p
JOIN rando AS r ON r.depart = p.idP
JOIN lac AS l ON l.idL = r.arrivee
WHERE p.commune = 'Cordon'
  AND p.altitude = 1300;

La première jointure relie le parking aux randonnées qui en partent ; la seconde donne les noms des lacs d’arrivée. Les alias p, r et l rendent explicite l’origine de chaque colonne, notamment altitude, qui existe dans deux relations.

DISTINCT évite d’afficher deux fois un même nom si plusieurs randonnées conduisent au même lac. Le filtre d’altitude doit porter sur le parking, pas sur le lac : confondre ces deux altitudes changerait le sens de la recherche.

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

Question 5

#

Ajouter le lac d’Anterne, à 2 059 m, et une randonnée qui part du parking d’identifiant 3 pour y arriver. Le parking existe déjà ; le lac et la randonnée n’existent pas encore. On peut utiliser 42 pour le lac et 100 pour la randonnée.

Indice

Insérez d’abord la ligne qui sera référencée par la clé étrangère.

Comprendre la correction
INSERT INTO lac (idL, nom, altitude)
VALUES (42, 'Lac d''Anterne', 2059);

INSERT INTO rando (idR, depart, arrivee)
VALUES (100, 3, 42);

On crée le lac avant de le référencer dans une randonnée. Dans le cas contraire, arrivee = 42 désignerait une clé inexistante et violerait la contrainte d’intégrité référentielle. Le parking 3 est déjà présent : aucune insertion dans parking n’est demandée.

L’apostrophe du nom est doublée à l’intérieur de la chaîne SQL : d''Anterne. Ce sont deux apostrophes dans la requête pour une seule dans la valeur enregistrée. Expliciter les noms des colonnes évite aussi de dépendre d’un ordre supposé.

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

Question 6

#

Susie a saisi le nom Lc d Anterne par erreur. Donner la requête qui le corrige.

Indice

Mettez à jour une colonne de la ligne identifiée par 42.

Comprendre la correction
UPDATE lac
SET nom = 'Lac d''Anterne'
WHERE idL = 42;

La clé primaire cible exactement le lac ajouté. Sans WHERE, tous les lacs recevraient le même nom. Il ne faut ni supprimer la randonnée ni changer son arrivée : l’identifiant 42 reste le même, seul le nom est modifié.

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

Question 7

#

Le parking d’identifiant 28 a été remplacé par un parc. Donner les requêtes qui suppriment ce parking de la base.

Indice

Repérez la relation qui possède une clé étrangère vers parking.

Comprendre la correction
DELETE FROM rando
WHERE depart = 28;

DELETE FROM parking
WHERE idP = 28;

On supprime d’abord les randonnées qui référencent ce parking, puis le parking lui-même. Cela évite de laisser un depart pointant vers une ligne disparue. Le sujet ne prévoit pas de suppression en cascade automatique : les deux requêtes rendent l’ordre explicite.

Ne supprimez pas les lacs d’arrivée de ces randonnées : ils continuent d’exister et peuvent rester accessibles depuis d’autres parkings. Une suppression doit se limiter à ce que demande le problème.

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

Susie veut compter les départs en Python. La table rando est représentée par une liste d’objets de cette classe :

class Rando:
    def __init__(self, idR, depart, arrivee):
        self.idR = idR
        self.depart = depart
        self.arrivee = arrivee

Question 8

#

Compléter les lignes 3 et 5 de get_parking pour obtenir la liste des identifiants des parkings de départ, sans doublon :

def get_parking(randos):
    parkings = []
    for ...:
        if rando.depart not in parkings:
            ...
    return parkings

Avec les randonnées Rando(1, 1, 1), Rando(2, 2, 1) et Rando(3, 1, 2), la fonction doit renvoyer [1, 2].

Indice

Le test porte sur l’attribut depart ; c’est aussi cet attribut qu’il faut ajouter.

Comprendre la correction
def get_parking(randos):
    parkings = []
    for rando in randos:
        if rando.depart not in parkings:
            parkings.append(rando.depart)
    return parkings

Le parcours fournit successivement un objet Rando. Son attribut depart est l’identifiant à ajouter. Dans l’exemple : on ajoute 1, puis 2, puis on ignore le second départ 1, déjà présent. La fonction garde donc l’ordre de première apparition.

Ajouter l’objet entier rando serait une erreur de type de résultat. La liste vide constitue un bon cas limite : le résultat doit être [] sans accès à un premier élément qui n’existe pas.

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

Question 9

#

Compléter la ligne 4 de get_nb_rando, qui compte les départs depuis le parking demandé :

def get_nb_rando(parking, randos):
    nb = 0
    for rando in randos:
        if ...:
            nb = nb + 1
    return nb

Avec le parking 1 et les trois randonnées de la question précédente, le résultat doit être 2.

Indice

La question porte sur le départ, pas sur idR ni arrivee.

Comprendre la correction
def get_nb_rando(parking, randos):
    nb = 0
    for rando in randos:
        if rando.depart == parking:
            nb = nb + 1
    return nb

On compare un identifiant à un identifiant : rando.depart == parking. Une randonnée d’arrivée 1 mais de départ 2 ne doit pas être comptée pour le parking 1. Le compteur est augmenté seulement si le prédicat est vrai, puis renvoyé après avoir examiné toutes les randonnées.

Si le parking n’apparaît jamais ou si la liste est vide, le compteur reste à 0. Ne placez pas return nb dans la boucle : cela arrêterait le comptage après le premier objet.

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

Question 10

#

Écrire nb_rando_par_parking, qui prend une liste de randonnées et renvoie le dictionnaire associant à chaque parking de départ son nombre de randonnées. Pour l’exemple précédent, on attend {1: 2, 2: 1}.

Indice

Pour chaque objet, sa clé est depart et sa contribution au compteur vaut 1.

Comprendre la correction
def nb_rando_par_parking(randos):
    resultat = {}
    for parking in get_parking(randos):
        resultat[parking] = get_nb_rando(parking, randos)
    return resultat

On réutilise les deux contrats précédents : get_parking fournit les clés sans doublon, puis get_nb_rando calcule leur valeur. Cette décomposition facilite une première solution correcte.

On peut aussi compter en un seul parcours :

def nb_rando_par_parking(randos):
    resultat = {}
    for rando in randos:
        parking = rando.depart
        if parking not in resultat:
            resultat[parking] = 0
        resultat[parking] += 1
    return resultat

Avant chaque incrément, la clé existe. Sur les départs 1, 2, 1, le dictionnaire devient successivement {1: 1}, {1: 1, 2: 1}, puis {1: 2, 2: 1}. La liste des randonnées seule ne permet pas de connaître les parkings de la base qui n’ont aucun départ : ils ne figurent pas dans ce résultat.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
Suivre une randonnée à travers les trois relationsUn atelier pour expérimenter

Choisissez un parking de l’extrait et une altitude maximale pour le lac. Le tableau expose chaque étape de la jointure : départ, randonnée et arrivée. Le filtre d’altitude porte ici sur le lac, contrairement à la question 3 qui filtre les parkings.

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

1 randonnée(s) répondent aux critères.

On relie depart à idP et arrivee à idL. Aucun produit cartésien n’est nécessaire : seuls les identifiants qui se correspondent sont associés.

ParkingRandonnéeLacNomAltitude
111Lac Blanc2354

Une jointure donne du sens aux identifiants : elle relie les lignes qui représentent la même randonnée, sans confondre leurs rôles.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Objectif somme : résoudre une grille avec des masques binaires

Le jeu Objectif somme possède un plateau de 5 × 5 cases, contenant initialement des chiffres de 1 à 9. On retire certaines cases pour que la somme restante de chaque ligne et de chaque colonne soit égale à sa cible. Il faut conserver au moins un chiffre par ligne. Les lignes sont notées L0 à L4, les colonnes C0 à C4.

Partie A : modélisation du problème

Figure 1 : plateau initial et cibles
Ligne / cibleC0 : 15C1 : 13C2 : 5C3 : 2C4 : 9
L0 : 1379232
L1 : 986351
L2 : 1277327
L3 : 664582
L4 : 486884
Figure 1 : solution proposée ; un point représente une case vidée
Ligne / cibleC0 : 15C1 : 13C2 : 5C3 : 2C4 : 9
L0 : 13·92·2
L1 : 98···1
L2 : 127·32·
L3 : 6·4··2
L4 : 4····4

L1 contient initialement [8, 6, 3, 5, 1] et C3 contient [3, 5, 2, 8, 8]. Dans la suite, les cibles sont supposées être des entiers entre 1 et 45.

Question 1

#

Expliquer pourquoi les cibles sont supposées être des entiers entre 1 et 45.

Indice

Combien de chiffres peut-on conserver au maximum, et quelle est leur plus grande valeur ?

Comprendre la correction

Une somme de chiffres entiers est un entier. Cinq cases contenant au plus 9 chacune donnent au maximum 5 × 9 = 45. Garder au moins un chiffre non nul sur une ligne donne une somme au moins égale à 1.

Le sujet choisit aussi des cibles de colonnes positives : il exclut donc les colonnes entièrement vidées. Cette exclusion des colonnes vides vient du choix des cibles ; la seule obligation de garder un chiffre par ligne ne suffirait pas à l’imposer. Il ne faut pas confondre ces deux conditions.

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

Question 2

#

Donner la plus petite et la plus grande cible que la ligne [6, 4, 5, 8, 2] peut atteindre.

Indice

Conserver un seul chiffre permet de minimiser ; tous les chiffres sont positifs.

Comprendre la correction

La plus petite cible est 2, en conservant uniquement le dernier chiffre. La plus grande est 25, en conservant tous les chiffres : 6 + 4 + 5 + 8 + 2 = 25. Vider toute la ligne donnerait 0, mais c’est interdit.

Ces bornes ne signifient pas que tous les entiers intermédiaires sont nécessairement atteignables pour n’importe quelle ligne. Les valeurs accessibles dépendent des sommes de sous-ensembles.

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

En Python, plateau est une liste de cinq listes de cinq entiers entre 0 et 9. Zéro représente désormais une case vide. Un jeu ajoute deux listes de cibles :

plateau_ex = [[7, 9, 2, 3, 2],
              [8, 6, 3, 5, 1],
              [7, 7, 3, 2, 7],
              [6, 4, 5, 8, 2],
              [8, 6, 8, 8, 4]]
ciblesLignes_ex = [13, 9, 12, 6, 4]
ciblesColonnes_ex = [15, 13, 5, 2, 9]

Question 3

#

Écrire extraireLigne, qui reçoit un plateau et un indice i entre 0 et 4 inclus, et renvoie la ligne Li. Pour l’exemple, l’indice 0 donne [7, 9, 2, 3, 2].

Indice

Le premier indice sélectionne la sous-liste correspondant à une ligne.

Comprendre la correction
def extraireLigne(plateau, i):
    return plateau[i]

Le plateau est une liste dont chaque élément est déjà une ligne. Un seul accès suffit donc. Cette version renvoie la liste qui se trouve dans le plateau, pas une copie : modifier un élément de la ligne retournée modifierait aussi le plateau. Pour une copie indépendante, on pourrait écrire return plateau[i][:], mais le sujet ne l’exige pas.

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

Question 4

#

Écrire extraireColonne, qui reçoit un plateau et un indice i de 0 à 4 et renvoie la colonne Ci. Avec l’indice 1, l’exemple donne [9, 6, 7, 4, 6].

Indice

Pour une colonne, l’indice de colonne reste fixe ; on traverse toutes les lignes.

Comprendre la correction
def extraireColonne(plateau, i):
    colonne = []
    for ligne in plateau:
        colonne.append(ligne[i])
    return colonne

Cette fois, la colonne n’existe pas comme une sous-liste déjà rangée dans le plateau. On prend l’élément d’indice i dans chaque ligne et on construit une nouvelle liste. Il faut garder i fixe pendant que la ligne change.

Une compréhension est équivalente : return [ligne[i] for ligne in plateau]. Confondre plateau[i][j] et plateau[j][i] échange les rôles des axes : les exemples du sujet servent justement à repérer cette inversion.

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

Partie B : simplifier le problème

Vider une case revient à remplacer son chiffre par 0. Deux règles permettent d’éliminer des valeurs impossibles sans chercher toutes les combinaisons.

Question 5

#

Donner en Python le plateau correspondant à la solution de la figure 1.

Indice

Transcrivez les lignes une par une en remplaçant chaque case vide par zéro.

Comprendre la correction
[[0, 9, 2, 0, 2],
 [8, 0, 0, 0, 1],
 [7, 0, 3, 2, 0],
 [0, 4, 0, 0, 2],
 [0, 0, 0, 0, 4]]

Chaque ligne doit garder cinq positions : une case vidée devient 0, elle ne disparaît pas de la liste. La première ligne totalise 9 + 2 + 2 = 13 et la première colonne 8 + 7 = 15. Ces vérifications rapides confirment que les positions ont été conservées.

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

Règle 1 : toute valeur d’une ligne ou colonne qui dépasse sa cible peut être éliminée. Par exemple L4, de cible 4, devient [0, 0, 0, 0, 4]. Cette règle s’appuie sur la positivité des chiffres : aucun autre chiffre conservé ne pourrait compenser une valeur déjà trop grande.

Question 6

#

Appliquer la règle 1 à chaque ligne du plateau initial et donner le plateau obtenu en Python.

Indice

Comparez chaque chiffre à la cible de sa ligne, pas à la somme entière de cette ligne.

Comprendre la correction
[[7, 9, 2, 3, 2],
 [8, 6, 3, 5, 1],
 [7, 7, 3, 2, 7],
 [6, 4, 5, 0, 2],
 [0, 0, 0, 0, 4]]

Les trois premières cibles de lignes sont 13, 9 et 12 : aucun chiffre de leurs lignes ne les dépasse. Pour L3, la cible 6 élimine le 8. Pour L4, la cible 4 élimine 8, 6, 8 et 8.

La consigne de cette question ne demande que le traitement des lignes. N’anticipez pas celui des colonnes : par exemple le 3 en position L0-C3 est encore présent ici, même si la cible de C3 vaut 2.

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

Question 7

#

Compléter les lignes 4 et 10 de regle1, qui applique la règle aux lignes puis aux colonnes :

def regle1(plateau, ciblesLignes, ciblesColonnes):
    for i in range(5):
        tab = extraireLigne(plateau, i)
        cible = ...
        for j in range(5):
            if tab[j] > cible:
                plateau[i][j] = 0
    for j in range(5):
        tab = extraireColonne(plateau, j)
        cible = ...
        for i in range(5):
            if tab[i] > cible:
                plateau[i][j] = 0
Indice

Associez chaque indice à la liste de cibles correspondante.

Comprendre la correction
def regle1(plateau, ciblesLignes, ciblesColonnes):
    for i in range(5):
        tab = extraireLigne(plateau, i)
        cible = ciblesLignes[i]
        for j in range(5):
            if tab[j] > cible:
                plateau[i][j] = 0
    for j in range(5):
        tab = extraireColonne(plateau, j)
        cible = ciblesColonnes[j]
        for i in range(5):
            if tab[i] > cible:
                plateau[i][j] = 0

La cible de la ligne i est ciblesLignes[i]. Celle de la colonne j est ciblesColonnes[j]. Les variables de parcours gardent donc leur rôle : i pour la ligne, j pour la colonne.

La fonction modifie le plateau en place. Elle ne renvoie pas un nouveau plateau. Un appel doit s’écrire regle1(plateau, ciblesLignes, ciblesColonnes), sans affecter son résultat à plateau, faute de quoi on remplacerait celui-ci par None.

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

Règle 2 : si une ligne ou colonne de cible paire ne contient qu’un seul chiffre impair, ce chiffre doit être éliminé. Sinon, la somme serait impaire quel que soit le choix des chiffres pairs. Dans L3 de cible 6, le seul impair est 5 : on peut donc l’enlever.

Question 8

#

Écrire unImpair, qui prend une liste d’entiers et renvoie True si elle contient exactement un entier impair, et False sinon.

Indice

Comptez les valeurs qui ont pour reste 1 dans la division par 2.

Comprendre la correction
def unImpair(tab):
    nb = 0
    for valeur in tab:
        if valeur % 2 == 1:
            nb += 1
    return nb == 1

Le reste modulo 2 repère chaque valeur impaire. On compte ces valeurs, puis on teste si le compteur vaut exactement 1. Trois valeurs impaires donnent donc False, même si leur nombre est impair : la propriété demandée n’est pas « un nombre impair de valeurs impaires ».

assert unImpair([6, 4, 5, 8, 2]) is True
assert unImpair([0, 2, 4]) is False
assert unImpair([1, 3, 5]) is False
assert unImpair([]) is False

Les zéros représentant les cases vides sont pairs. Ils ne doivent pas augmenter le compteur.

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

Question 9

#

Compléter les lignes 4, 5, 11 et 12 de regle2 :

def regle2(plateau, ciblesLignes, ciblesColonnes):
    for i in range(5):
        ligne = extraireLigne(plateau, i)
        ...
        if ...:
            for j in range(5):
                if plateau[i][j] % 2 == 1:
                    plateau[i][j] = 0
    for j in range(5):
        colonne = extraireColonne(plateau, j)
        ...
        if ...:
            for i in range(5):
                if plateau[i][j] % 2 == 1:
                    plateau[i][j] = 0
Indice

Traduisez littéralement les deux hypothèses de la règle avec and.

Comprendre la correction
def regle2(plateau, ciblesLignes, ciblesColonnes):
    for i in range(5):
        ligne = extraireLigne(plateau, i)
        cible = ciblesLignes[i]
        if cible % 2 == 0 and unImpair(ligne):
            for j in range(5):
                if plateau[i][j] % 2 == 1:
                    plateau[i][j] = 0
    for j in range(5):
        colonne = extraireColonne(plateau, j)
        cible = ciblesColonnes[j]
        if cible % 2 == 0 and unImpair(colonne):
            for i in range(5):
                if plateau[i][j] % 2 == 1:
                    plateau[i][j] = 0

Les deux conditions sont nécessaires : une cible paire et un seul impair encore présent. Si la cible est impaire, cet impair peut au contraire être indispensable. Si deux impairs sont présents, leur somme peut être paire : en éliminer un arbitrairement serait injustifié.

Les colonnes sont extraites après les modifications des lignes. Le test doit donc porter sur leur état actuel. La fonction conserve la même convention que regle1 : modification du plateau en place.

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

Les deux règles ne résolvent pas tous les plateaux. On étudie maintenant une seule ligne de cinq entiers : quels chiffres conserver pour atteindre une cible ? Pour [6, 4, 5, 8, 2] de cible 6, on peut garder le 6 seul, masque [1, 0, 0, 0, 0], ou garder 4 et 2, masque [0, 1, 0, 0, 1]. Un 1 signifie « conserver cette position », un 0 « ne pas la conserver ».

Partie C : énumérer les masques

Question 10

#

Expliquer pourquoi [1, 1, 0, 0, 1] résout la ligne [1, 2, 3, 5, 2] avec la cible 5. Donner tous les autres masques solutions.

Indice

Classez les cas selon que le 5 est conservé, puis selon que le 3 est conservé.

Comprendre la correction

Le masque donné conserve les positions 0, 1 et 4, donc 1 + 2 + 2 = 5. Les autres masques sont :

[0, 1, 1, 0, 0]  # 2 + 3
[0, 0, 1, 0, 1]  # 3 + 2
[0, 0, 0, 1, 0]  # 5

Les deux chiffres 2 sont égaux mais occupent des positions différentes ; ils produisent deux masques distincts lorsqu’on les associe au 3. Pour vérifier l’exhaustivité : soit on garde le 5 seul, soit on utilise le 3 avec l’un des deux 2, soit on n’utilise ni 3 ni 5 et il faut alors garder 1 et les deux 2.

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

Question 11

#

Écrire somme, qui prend une liste de cinq entiers et un masque de cinq bits, puis renvoie la somme des chiffres sélectionnés. L’appel somme([1, 5, 3, 4, 8], [0, 1, 1, 0, 1]) doit donner 16.

Indice

Un masque permet de sélectionner par multiplication par 0 ou 1.

Comprendre la correction
def somme(tab, masque):
    total = 0
    for i in range(5):
        total += tab[i] * masque[i]
    return total

Chaque produit vaut soit la valeur de la case, si le bit vaut 1, soit zéro. On additionne les cinq contributions. Dans l’exemple, on obtient 0 + 5 + 3 + 0 + 8 = 16. Une version avec if masque[i] == 1 est également correcte.

La liste et le masque ne sont pas modifiés : ils pourront être réutilisés pour d’autres tests. Vérifiez le masque tout nul, qui donne 0, et le masque tout à 1, qui donne la somme de la liste entière.

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

On interprète chaque masque comme un entier binaire sur cinq bits. Par exemple [0, 1, 0, 0, 1] représente 0 × 16 + 1 × 8 + 0 × 4 + 0 × 2 + 1 × 1 = 9.

Question 12

#

Donner l’écriture binaire sur cinq bits de 26 sous la forme d’une liste.

Indice

Décomposez 26 en puissances de deux de 16 à 1.

Comprendre la correction
[1, 1, 0, 1, 0]

On décompose 26 = 16 + 8 + 2. Avec les poids 16, 8, 4, 2, 1 de gauche à droite, cela donne 1, 1, 0, 1, 0. L’ordre des bits est le même que celui des positions du masque.

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

Question 13

#

Expliquer pourquoi cinq bits ne représentent que les entiers de 0 à 31.

Indice

Le comptage inclut zéro.

Comprendre la correction

Chaque bit possède deux valeurs possibles. Il existe donc 2⁵ = 32 configurations. En représentation non signée, elles vont de 00000, soit 0, à 11111, soit 16 + 8 + 4 + 2 + 1 = 31.

Il faut distinguer le nombre de valeurs représentables, 32, de la plus grande valeur, 31. Cet écart d’une unité reviendra dans la borne de range à la question 15.

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

Question 14

#

Écrire dec2bin, qui prend un entier de 0 à 31 et renvoie la liste de ses cinq bits. Par exemple, dec2bin(9) doit donner [0, 1, 0, 0, 1].

Indice

Écrivez les restes de 9 // 2 puis recommencez avec le quotient.

Comprendre la correction
def dec2bin(n):
    bits = [0, 0, 0, 0, 0]
    for i in range(4, -1, -1):
        bits[i] = n % 2
        n = n // 2
    return bits

Les restes des divisions successives par 2 donnent d’abord les bits de poids faible. On remplit donc la liste de droite à gauche. Pour 9, les restes successifs sont 1, 0, 0, 1, 0 ; placés aux indices 4, 3, 2, 1, 0, ils donnent la liste demandée.

range(4, -1, -1) inclut l’indice 0 et réalise toujours cinq tours, ce qui conserve les zéros en tête. Le quotient entier // est indispensable : une division réelle introduirait des nombres flottants.

assert dec2bin(0) == [0, 0, 0, 0, 0]
assert dec2bin(9) == [0, 1, 0, 0, 1]
assert dec2bin(31) == [1, 1, 1, 1, 1]
Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

Le sujet rappelle que append ajoute un élément en fin de liste :

solutions = []
solutions.append(1)
solutions.append(2)
# solutions vaut maintenant [1, 2]

Question 15

#

Écrire masques_solutions, qui reçoit une liste de cinq entiers et une cible, puis renvoie la liste de tous les masques solutions. On peut utiliser dec2bin, somme et append.

PythonTrouver toutes les façons d’atteindre une cibleÉcrivez votre solution et mettez-la à l’épreuve

Écrivez masques_solutions(tab, cible) pour une liste de cinq entiers. Un masque est une liste de cinq bits : 1 conserve la valeur de même position et 0 l’écarte. Renvoyez tous les masques dont la somme sélectionnée vaut cible. dec2bin et somme sont disponibles. Les tests acceptent tout ordre de résultats, mais chaque masque doit apparaître une seule fois.

def masques_solutions(tab, cible):
    # À vous de jouer
    pass

Les cas de test proposés :

  • La combinaison vide compte aussi : Le masque 00000 réalise zéro et doit être examiné.
  • Le dernier masque ne doit pas être oublié : 31 doit être inclus dans l’intervalle des entiers parcourus.
  • Des valeurs égales ne sont pas la même case : Il existe cinq sélections distinctes : les positions comptent, même si les valeurs sont identiques.
  • Une cible impossible : Une somme de nombres pairs ne peut pas donner 3.
  • Plusieurs solutions indépendantes : 3 seul et 1 + 2 conviennent. Les résultats ne doivent pas partager une même liste de bits modifiée en boucle.
Indice

Une boucle énumère les entiers, une condition vérifie la somme et un append conserve les réussites.

Comprendre la correction
def masques_solutions(tab, cible):
    solutions = []
    for n in range(32):
        masque = dec2bin(n)
        if somme(tab, masque) == cible:
            solutions.append(masque)
    return solutions

On examine exhaustivement les 32 configurations, convertit chacune en masque, puis conserve celles dont la somme vaut la cible. range(32) inclut 31 : range(31) oublierait le masque qui garde tous les chiffres.

Il faut placer le retour après la boucle et conserver toutes les solutions, même si une première a déjà été trouvée. Chaque appel à dec2bin crée une liste indépendante : les masques enregistrés ne sont pas plusieurs références vers une unique liste modifiée au tour suivant.

Pour cinq cases, cela fait seulement 32 masques. Une généralisation à n cases examinerait 2ⁿ possibilités : cette recherche exhaustive devient rapidement coûteuse. Ce constat explique l’intérêt des règles de simplification, sans les confondre avec une preuve qu’elles résolvent tout.

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

Partie D : retour au jeu complet

Un masque valable pour une ligne ne suffit pas à résoudre le jeu : les colonnes doivent être respectées elles aussi.

Question 16

#

Écrire teste_solution, qui reçoit un plateau, les cibles de lignes et celles des colonnes, et renvoie True si toutes les sommes correspondent, False sinon.

Indice

Rejeter au premier écart ; accepter seulement après le dernier contrôle.

Comprendre la correction
def teste_solution(plateau, ciblesLignes, ciblesColonnes):
    for i in range(5):
        if sum(extraireLigne(plateau, i)) != ciblesLignes[i]:
            return False
    for j in range(5):
        if sum(extraireColonne(plateau, j)) != ciblesColonnes[j]:
            return False
    return True

Une seule somme incorrecte suffit à rejeter le plateau : le retour False peut donc être immédiat. Au contraire, on ne renvoie True qu’après avoir vérifié les cinq lignes et les cinq colonnes. Des lignes correctes ne garantissent pas des colonnes correctes.

Les cibles étant positives, une ligne entièrement vide échouerait automatiquement. Cette fonction vérifie exactement les sommes demandées. Elle ne peut pas vérifier que chaque case conservée vient du plateau d’origine : ce plateau n’est pas un paramètre de la fonction.

assert teste_solution(solution, ciblesLignes_ex, ciblesColonnes_ex)
assert not teste_solution(plateau_ex, ciblesLignes_ex, ciblesColonnes_ex)
Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
À vous de résoudre le plateau : cinq masques, dix ciblesUn atelier pour expérimenter

Chaque nombre de 0 à 31 est un masque de cinq bits pour une ligne. Un bit à 1 conserve la case, un bit à 0 la vide. Modifiez les cinq masques : une ligne peut être correcte alors qu’une colonne ne l’est pas encore. La table compare les dix sommes aux cibles du sujet.

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

0 cibles atteintes sur 10.

Les bits se lisent dans le même ordre que les colonnes, avec le poids 16 à gauche. Une valeur 0 dans la grille désigne une case vidée, jamais un déplacement des autres cases.

Ligne ou colonneMasque sur 5 bitsSommeCibleÉtat
L0111112313À ajuster
L111111239À ajuster
L2111112612À ajuster
L311111256À ajuster
L411111344À ajuster
C0-3615À ajuster
C1-3213À ajuster
C2-215À ajuster
C3-262À ajuster
C4-169À ajuster

Résoudre chaque ligne séparément produit des candidats. Seule la vérification croisée des lignes et colonnes confirme une solution complète.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Pour RIP, distinguez l’initialisation, les échanges synchrones et les mises à jour en place du programme.
  • En SQL, suivez les clés entre les relations et vérifiez l’ordre des mutations avant de lancer les requêtes.
  • Dans Objectif somme, gardez les positions des cases et testez les colonnes aussi soigneusement que les lignes.
  • Refaites les questions 12-13 de l’exercice 1 et 14-16 de l’exercice 3 : elles concentrent le raisonnement sur les boucles, leurs bornes et leur arrêt.

Retrouver ces notions dans d’autres sujets

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

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