Épreuve écrite · 2025 · Remplacement jour2 - 10 septembre 2025

Bac NSI 2025 Métropole remplacement jour 2

Le second sujet de remplacement 2025 fait passer d’un carré de chiffrement aux permissions Linux, puis aux livraisons d’un supermarché. Il demande aussi un vrai esprit critique : une règle gloutonne mal précisée peut boucler au lieu de rejoindre la destination. Les corrections suivent les questions et rendent explicites les conventions nécessaires.

Trois exercices indépendants de 6,6 et 8 points,38 questions,3 h30 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

Chiffrer des digrammes avec le carré de Playfair

Playfair est un chiffrement historique par paires de lettres, appelées digrammes. La variante du sujet utilise un carré 5×5 contenant les lettres de la clé sans doublons, puis les lettres restantes en ordre alphabétique, en excluant W. Chaque lettre n’apparaît qu’une fois.

Partie A : construire la clé

Avec PLAYFAIR, le second A est ignoré. Les sept premières cases viennent de la clé :

Ligne / colonne01234
0PLAYF
1IRBCD
2EGHJK
3MNOQS
4TUVXZ

Figure 1 : carré de PLAYFAIR. Les entrées P, L, A, Y, F, I, R viennent de la clé ; B à Z complètent les cases restantes selon l’alphabet sans W.

Question 1

#

Donner le carré de chiffrement pour la clé EPREUVEDENSI.

Comprendre la correction
Ligne / colonne01234
0EPRUV
1DNSIA
2BCFGH
3JKLMO
4QTXYZ

Les lettres distinctes de la clé sont E, P, R, U, V, D, N, S, I, dans cet ordre. On ne trie pas cette partie : seul le complément alphabétique est trié. W ne doit pas réapparaître dans le remplissage.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)
def creer_liste_clef(clef):
    """Hypothèse : la clef ne contient pas W."""
    deja_utilises = []
    alphabet = 'ABCDEFGHIJKLMNOPQRSTUVXYZ'
    for i in range(len(clef)):
        if not clef[i] in deja_utilises:
            deja_utilises.append(clef[i])
    for lettre in alphabet:
        if not lettre in deja_utilises:
            deja_utilises.append(lettre)
    return deja_utilises

Exemple PLAYFAIR : la fonction renvoie les 25 lettres obtenues en lisant la figure 1 ligne après ligne.

Question 2

#

Donner l’assertion à placer au début de creer_liste_clef pour vérifier l’absence de W.

Comprendre la correction
assert 'W' not in clef

La condition doit être vraie pour continuer. Avec une clé contenant W et les assertions actives, une AssertionError est levée. Cela vérifie l’hypothèse demandée ; le code suppose par ailleurs des lettres majuscules de l’alphabet retenu.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)
def creer_carre(liste_clef):
    carre = [[0 for i in range(5)] for j in range(5)]
    for i in range(25):
        carre[...][...] = liste_clef[i]
    return carre

Question 3

#

Compléter la ligne 4 de creer_carre avec % et //.

Indice

Une ligne contient cinq lettres.

Comprendre la correction
def creer_carre(liste_clef):
    carre = [[0 for i in range(5)] for j in range(5)]
    for i in range(25):
        carre[i // 5][i % 5] = liste_clef[i]
    return carre

i // 5 donne le numéro de ligne et i % 5 la colonne. L’indice 4 est encore en(0,4), l’indice 5 passe en(1,0), et 24 est en(4,4). La compréhension imbriquée crée cinq lignes distinctes.

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

Partie B : préparer et chiffrer le message

On forme des paires. Si les deux lettres sont identiques, on insère X après la première puis on repart de la seconde. Une dernière lettre isolée reçoit un X. Ainsi BACCALAUREAT devient BA, CX, CA, LA, UR, EA, TX.

Pour chiffrer : même ligne, décaler chaque lettre d’une colonne à droite avec retour au début ; même colonne, descendre d’une ligne avec retour en haut ; sinon échanger les colonnes des deux coins du rectangle, en gardant leurs lignes.

PLAYFIRBCDEGHJKMNOQSTUVXZ
VI → TB. Bleu : entrée ; vert pointillé : sortie.
PLAYFIRBCDEGHJKMNOQSTUVXZ
VE → TH. Bleu : entrée ; vert pointillé : sortie.
PLAYFIRBCDEGHJKMNOQSTUVXZ
LA → AY. Bleu : entrée ; vert pointillé : sortie.
PLAYFIRBCDEGHJKMNOQSTUVXZ
NS → OM. Bleu : entrée ; vert pointillé : sortie.
PLAYFIRBCDEGHJKMNOQSTUVXZ
IX → CT. Bleu : entrée ; vert pointillé : sortie.
Figure 2 : digrammePositionsRègleChiffré
VIV(4,2), I(1,0)Rectangle : (4,0),(1,2)TB
VEV(4,2), E(2,0)Rectangle : (4,0),(2,2)TH
LAL(0,1), A(0,2)Même ligne : (0,2),(0,3)AY
NSN(3,1), S(3,4)Même ligne avec retour : (3,2),(3,0)OM
IXI(1,0), X(4,3)Rectangle : (1,3),(4,0)CT

Les cinq dessins de la figure 2 s’appuient sur le même carré de la figure 1 ; les cases d’entrée et de sortie sont repérées dans les cinq carrés, puis leurs coordonnées sont détaillées dans le tableau. VIVELANSI donne VI, VE, LA, NS, IX.

def couper_en_digrammes(message):
    digrammes = []
    i = 0
    while i < len(message) - 1:
        if message[i] == message[i+1]:
            digrammes.append(message[i] + 'X')
            i = i + 1
        else:
            ...
            i = i + 2
    if i == len(message) - 1:
        digrammes.append(message[i] + 'X')
    return digrammes

Question 4

#

Compléter la ligne 9 de couper_en_digrammes.

Comprendre la correction
digrammes.append(message[i] + message[i + 1])

Dans cette branche, les deux lettres consécutives sont différentes : on les ajoute telles quelles et on avance de 2. Dans la branche des doublons, on ajoute X après la première et on n’avance que de 1 pour retraiter la seconde.

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

Question 5

#

Donner couper_en_digrammes('BONJOUR').

Comprendre la correction
['BO', 'NJ', 'OU', 'RX']

Aucun doublon adjacent ne force d’insertion. Les six premières lettres donnent trois paires ; le R final reçoit X pour compléter la dernière.

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

Question 6

#

Chiffrer BONJOUR avec le carré PLAYFAIR.

Comprendre la correction
DigrammeRègle et positions obtenuesChiffré
BOMême colonne 2 : B→H, O→VHV
NJRectangle : N(3,1), J(2,3) → Q(3,3), G(2,1)QG
OURectangle : O(3,2), U(4,1) → N(3,1), V(4,2)NV
RXRectangle : R(1,1), X(4,3) → C(1,3), U(4,1)CU

Le message chiffré est HVQGNVCU. L’ordre des deux lettres de chaque rectangle doit rester lié à leurs lignes respectives ; permuter les coins inverserait le résultat.

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

Exemples avec PLAYFAIR : A donne(0,2), N donne(3,1).

Question 7

#

Écrire ligne_colonne(lettre, carre), qui renvoie les coordonnées d’une lettre.

Comprendre la correction
def ligne_colonne(lettre, carre):
    for i in range(5):
        for j in range(5):
            if carre[i][j] == lettre:
                return (i, j)

Les deux boucles inspectent les 25 cases et renvoient le couple dès qu’elles rencontrent la lettre. L’unicité dans le carré rend ce résultat non ambigu. Pour une lettre absente, Python renverrait implicitement None ; les entrées du chiffrement doivent donc respecter l’alphabet choisi.

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

Question 8

#

Écrire sur_la_meme_ligne(digramme, carre).

Comprendre la correction
def sur_la_meme_ligne(digramme, carre):
    i1, j1 = ligne_colonne(digramme[0], carre)
    i2, j2 = ligne_colonne(digramme[1], carre)
    return i1 == i2

On compare seulement les premières coordonnées. Les colonnes peuvent différer. RT donne faux, PL donne vrai. La fonction analogue sur_la_meme_colonne compare les deux secondes coordonnées.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
def chiffrer_digramme(digramme, carre):
    lettre1 = digramme[0]
    lettre2 = digramme[1]
    i1, j1 = ligne_colonne(lettre1, carre)
    i2, j2 = ligne_colonne(lettre2, carre)
    if sur_la_meme_ligne(digramme, carre):
        digramme_chiffre = carre[i1][(j1+1)%5] + carre[i2][(j2+1)%5]
    elif sur_la_meme_colonne(digramme, carre):
        digramme_chiffre = ...
    else:
        digramme_chiffre = ...
    return digramme_chiffre

Question 9

#

Compléter les lignes 9 et 11 de chiffrer_digramme.

PythonChiffrer un digramme dans le carré de PlayfairÉcrivez votre solution et mettez-la à l’épreuve

Complétez chiffrer_digramme(digramme, carre) : même ligne, décalez chaque lettre d’une colonne à droite ; même colonne, décalez d’une ligne vers le bas ; sinon, conservez les lignes et échangez les colonnes. Le carré fait 5×5, les décalages reviennent au début après le bord. Les lettres sont déjà présentes dans le carré. Respectez l’ordre des tests du sujet, même ligne avant même colonne. Le découpage du message n’est pas demandé dans cette fonction.

def chiffrer_digramme(digramme, carre):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Même ligne, sans retour au bord : L devient A et A devient Y, sur la première ligne du carré du sujet.
  • Dernière colonne vers la première : F revient sur P ; P avance vers L.
  • Même colonne, avec retour en haut : T est sur la dernière ligne de la colonne de P : son successeur cyclique est P.
  • Le rectangle garde l’ordre des lettres : Les colonnes sont échangées, les lignes restent celles des lettres de départ.
  • Un digramme de lettres identiques : Le remplissage du sujet peut produire XX. Le test même ligne est appliqué en premier, donc X avance sur Z.
  • Le carré reste une clé réutilisable : Chaque chiffrement lit la clé ; il ne déplace pas réellement ses lettres.
Indice

Même colonne : changer les lignes ; rectangle : échanger les colonnes.

Comprendre la correction
def chiffrer_digramme(digramme, carre):
    lettre1 = digramme[0]
    lettre2 = digramme[1]
    i1, j1 = ligne_colonne(lettre1, carre)
    i2, j2 = ligne_colonne(lettre2, carre)
    if sur_la_meme_ligne(digramme, carre):
        digramme_chiffre = carre[i1][(j1 + 1) % 5] + carre[i2][(j2 + 1) % 5]
    elif sur_la_meme_colonne(digramme, carre):
        digramme_chiffre = carre[(i1 + 1) % 5][j1] + carre[(i2 + 1) % 5][j2]
    else:
        digramme_chiffre = carre[i1][j2] + carre[i2][j1]
    return digramme_chiffre

Dans une colonne, on change les indices de ligne et conserve les colonnes. Le modulo 5 assure le retour du bord inférieur vers la première ligne. Dans le cas rectangle, on conserve i1, i2 et on échange j1,j2.

Le cas même ligne est testé avant même colonne. Avec un digramme XX, que la règle de remplissage du sujet peut produire, ce code applique donc le cas de ligne. Une variante pratique devrait définir un caractère de remplissage de remplacement ; le sujet ne le prévoit pas.

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

Question 10

#

Écrire chiffrer_playfair(message, clef) à l’aide des fonctions précédentes.

Comprendre la correction
def chiffrer_playfair(message, clef):
    carre = creer_carre(creer_liste_clef(clef))
    digrammes = couper_en_digrammes(message)
    resultat = ''
    for digramme in digrammes:
        resultat += chiffrer_digramme(digramme, carre)
    return resultat

Le carré est construit une fois, puis réutilisé pour toutes les paires. La concaténation conserve leur ordre. Pour VIVELANSI et PLAYFAIR, on obtient TBTHAYOMCT. Le message vide donne une chaîne vide.

Ce chiffrement historique sert ici à étudier tableaux et chaînes ; il ne convient pas à la protection actuelle de données sensibles. Le découpage avec des X n’est pas toujours réversible sans convention supplémentaire pour distinguer les X ajoutés des X réels.

Voir la question dans le sujet PDF, p. 7 (nouvel onglet)
Suivez le rectangle ou le décalage dans PlayfairUn atelier pour expérimenter

Choisissez une clé et un message du sujet, puis une paire. Les deux lettres sont repérées dans le carré ; l’atelier donne la règle exacte et les coordonnées de sortie.

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

HVQGNVCU

Paire1 : BO → HV. Même colonne : une ligne vers le bas. Les repères entrée/sortie ci-dessous donnent les cases utilisées.

Ligne / colonne01234
0PLAYF
1IRB (entrée)CD
2EGH (sortie)JK
3MNO (entrée)QS
4TUV (sortie)XZ

La règle dépend de deux coordonnées par lettre. Le modulo fait le retour au bord ; le rectangle échange les colonnes sans échanger les lignes.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Lire et convertir les permissions Linux

Partie A : lire le terminal

La figure 1 affiche le terminal de alovelace@cerebro:~$ après une commande masquée :

total 21297
-rw-r--r-- 8 root      www-data  1257 janv. 29 2019 concat.pls
drwxrwxr-x 2 alovelace alovelace 4096 févr. 18 2023 graph
-rwxrwxr-- 4 root      adminsys   344 juin  13 2021 todo.txt
-rwxrw-r-- 1 alovelace www-data   118 octo. 05 2024 verify.sh

Le premier caractère indique le type : -fichier ordinaire, d répertoire, l lien. Les trois groupes suivants concernent user, group, other. Dans chacun, r autorise la lecture, w l’écriture, x l’exécution ; un tiret signifie que le droit n’est pas accordé.

Question 1

#

Écrire la commande masquée qui produit cet affichage détaillé.

Comprendre la correction
ls -l

L’option -l demande le format long : type et permissions, nombre de liens, propriétaire, groupe, taille, date et nom. Une simple commande ls ne montre pas ces colonnes par défaut.

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

Question 2

#

Décrire les permissions user, group, other de verify.sh.

Comprendre la correction
CatégorieBitsDroits
Propriétaire alovelacerwxLecture, écriture, exécution
Groupe www-datarw-Lecture, écriture, pas d’exécution
Autresr--Lecture seulement

On écarte le premier tiret, qui indique le type de fichier, puis on découpe les neuf caractères en trois blocs. Le x du propriétaire ne donne pas automatiquement ce droit au groupe.

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

Question 3

#

Quelle commande le propriétaire doit-il saisir pour supprimer concat.pls ?

Comprendre la correction
rm concat.pls

Le propriétaire indiqué est root. La commande de suppression est rm ; sa réussite dépend des droits sur le répertoire contenant l’entrée, et pas seulement des permissions de lecture/écriture du fichier. L’invite de la capture est celle d’alovelace, ce qui ne change pas le fait que la question se place du côté du propriétaire.

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

Figure 2, extrait de man chmod reconstruit : chmod modifie les bits de permissions. La syntaxe accepte chmod [OPTION]... MODE[,MODE]... FICHIER..., un mode octal, ou --reference=FICHIER-R. Un mode symbolique choisit des catégories u, g, o, a puis une opération +,-ou=et des droits r, w, x. u désigne le propriétaire, g le groupe, o les autres, a tous. +ajoute les droits, -les retire. Pour un répertoire, x autorise sa traversée/recherche.

Question 4

#

Quel est l’effet de chmod g-wx todo.txt à partir des éléments de la figure 1 et du manuel ?

Indice

g vise le groupe ; wx désigne deux droits à retirer.

Comprendre la correction
ÉtatPermissions
Avant-rwxrwxr--
Après, si la commande est autorisée-rwxr--r--

g cible le groupe ; -retire les droits ; wx retire l’écriture et l’exécution. La lecture du groupe reste en place, et les droits user/other ne changent pas.

Le fichier appartient à root. Si alovelace exécute la commande sans privilège particulier, elle échoue : être membre du groupe ne suffit pas pour modifier les permissions. L’état « après » décrit l’effet demandé lorsque la commande est lancée par le propriétaire ou avec l’autorisation nécessaire.

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

Partie B : représentation octale

Chaque groupe de trois permissions produit trois bits : présence de r, w, x →1, absence →0. Les poids sont 4,2,1. Le résultat est un chiffre de 0 à 7. Les trois chiffres user, group, other forment le mode octal. Exemple rwxr-xr-- →754.

La chaîne de neuf caractères est la représentation d’affichage étudiée en Python ; une commande chmod emploie quant à elle un mode comme u+rwx ou un nombre octal, pas directement la chaîne rwxr-xr--.

Question 6

#

Écrire bin_to_oct, qui reçoit trois caractères 0 ou 1 et renvoie la valeur du motif, bit de poids fort à gauche.

Comprendre la correction
def bin_to_oct(chaine):
    return 4 * int(chaine[0]) + 2 * int(chaine[1]) + int(chaine[2])

Les caractères doivent être convertis en entiers avant la somme pondérée. Trois bits représentent une valeur de 0 à 7, donc un unique chiffre octal. La fonction renvoie un entier, comme demandé par les exemples 101→5 et 011→3.

Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
def mystere(chaine):
    resultat = ''
    for c in chaine:
        if c == '-':
            resultat = resultat + '0'
        else:
            resultat = resultat + '1'
    return resultat

Question 7

#

Déterminer mystere('rw-r-xr--').

Comprendre la correction
'110101100'

Les trois groupes deviennent 110,101,100. La fonction ne vérifie pas si une lettre est à la bonne position : elle remplace tout caractère autre que tiret par 1. Le sujet suppose que la représentation symbolique est valide.

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

symb_to_bin est disponible et convertit la chaîne symbolique en neuf bits. Exemples attendus : rwxrw-r-- →'764' ; rw-r--r-- →'644'.

def symb_to_oct(chaine):
    repr_bin = symb_to_bin(chaine)
    user = repr_bin[0] + repr_bin[1] + repr_bin[2]
    group = repr_bin[3] + repr_bin[4] + repr_bin[5]
    other = repr_bin[6] + repr_bin[7] + repr_bin[8]
    ...

Question 8

#

Compléter la fin de symb_to_oct, qui renvoie trois chiffres sous forme de chaîne.

Comprendre la correction
def symb_to_oct(chaine):
    repr_bin = symb_to_bin(chaine)
    user = repr_bin[0] + repr_bin[1] + repr_bin[2]
    group = repr_bin[3] + repr_bin[4] + repr_bin[5]
    other = repr_bin[6] + repr_bin[7] + repr_bin[8]
    return str(bin_to_oct(user)) + str(bin_to_oct(group)) + str(bin_to_oct(other))

Chaque groupe est converti séparément, puis les trois chiffres sont concaténés. Les str sont nécessaires : additionner trois entiers donnerait une somme, pas le code de permissions. Conserver une chaîne garde aussi un éventuel zéro initial.

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

Partie C : représenter les fichiers

class Fichier:
    def __init__(self, nom, poids, proprio, groupe, permission):
        self.nom = nom
        self.poids = poids
        self.proprietaire = proprio
        self.groupe = groupe
        self.permission = permission

Question 9

#

Créer mon_fichier pour concat.pls,1257 octets, propriétaire root, groupe www-data, permissions rw-r--r--.

Comprendre la correction
mon_fichier = Fichier('concat.pls', 1257, 'root', 'www-data', 'rw-r--r--')

On respecte l’ordre du constructeur : nom, poids, propriétaire, groupe, permissions. Le type initial du fichier, le tiret de ls-l, ne fait pas partie des neuf caractères stockés dans permission.

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

Question 10

#

Écrire la méthode chown qui change le nom du propriétaire dans l’objet.

Comprendre la correction
def chown(self, nouveau_proprietaire):
    self.proprietaire = nouveau_proprietaire

Cette méthode modifie le modèle Python. Elle n’exécute pas une commande système et ne change pas réellement les droits d’un fichier du disque.

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

Question 11

#

Écrire get_executable, qui renvoie les fichiers appartenant au propriétaire indiqué et exécutables par lui.

Indice

Le bit user-x est le troisième des neuf caractères.

Comprendre la correction
def get_executable(fichiers, proprietaire):
    resultat = []
    for fichier in fichiers:
        if fichier.proprietaire == proprietaire and fichier.permission[2] == 'x':
            resultat.append(fichier)
    return resultat

Deux conditions doivent être vraies : le propriétaire correspond et le troisième caractère du bloc user est x. La chaîne compte neuf caractères, donc cet indice est 2. Tester un x n’importe où accepterait à tort un fichier exécutable seulement par le groupe ou les autres.

Voir la question dans le sujet PDF, p. 12 (nouvel onglet)
Fabriquez les trois chiffres des permissionsUn atelier pour expérimenter

Activez lecture, écriture, exécution pour chaque catégorie. La représentation symbolique, les bits et le mode octal évoluent ensemble.

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

rwxr--r-- = 744

Chaque chiffre représente son propre groupe de trois bits. Retirer un droit modifie uniquement le poids correspondant dans cette catégorie.

CatégorieSymbolesBitsValeur
userrwx1117
groupr--1004
otherr--1004

r, w, x ont les poids 4,2,1. Une permission accordée à une catégorie ne s’applique pas automatiquement aux autres, et modifier un objet Python ne modifie pas le système réel.

Revoir les notions de cet exercice

Exercice 3 · 8 points

Commandes SQL et itinéraires de livraison

Un supermarché stocke ses produits, fournisseurs, commandes et lignes de commande. Les clés primaires de chaque table sont les identifiants ; les références relient Details aux commandes et produits, puis Produits aux fournisseurs.

id_produitid_commandeid_fournisseurProduitsDetailsCommandesFournisseurs
Figure 1 : références entre les tables du supermarché
Lire les connexions du schéma
  • Details vers Produits : id_produit
  • Details vers Commandes : id_commande
  • Produits vers Fournisseurs : id_fournisseur
RelationAttributs et types de la figure
Produitsid_produit int(PK), nom str, categorie str, prix float, quantite_stock int, id_fournisseur int(FK)
Detailsid_details int(PK), id_command int(FK), id_produit int(FK), quantite int, prix_unitaire float
Commandesid_commande int(PK), date date, total float
Fournisseursid_fournisseur int(PK), nom str, adresse str, Ville str, pays str

Les noms id_command, date et total de la figure diffèrent de ceux des tableaux qui suivent. Pour écrire des requêtes cohérentes, le corrigé utilise les noms des tables détaillées : id_commande, date_commande, total_commande.

Commandes.id_commandedate_commandetotal_commande
103/06/2025176
208/12/20241150
321/04/2025155
Produits.id_produitnomcategorieprixquantite_stockid_fournisseur
1Yaourts blanc x 4Alimentaire2.8502
2LaitAlimentaire1.22002
3PainAlimentaire1.51004
4Harry Potter 1Livre15203
5Jeu d’échecsJeux40303
6T-shirt taille MVêtement10801
7Jeans taille MVêtement25605
Fournisseurs.id_fournisseurnomadressevillepays
1Moda e stileVia della Moda, 45MilanoItalie
2Laiteries Unies22 Avenue des VachesLisieuxFrance
3Livres en Folie56 Boulevard des LivresToulouseFrance
4Boulangerie du Coin34 Rue du PainNantesFrance
5Estilo EspañolCalle de la Moda, 123MadridEspagne
Details.id_detailsid_commandeid_produitquantiteprix_unitaire
111202.8
2121001.2
3264010
4273025
535240
634515

Mots SQL proposés : SELECT, DISTINCT, FROM, WHERE, JOIN...ON, UPDATE...SET, DELETE, INSERT INTO...VALUES.

Question 1

#

Ajouter le produit croissant d’identifiant 10, vendu 0,90€, fourni par Boulangerie du Coin.

Comprendre la correction
INSERT INTO Produits (id_produit, nom, categorie, prix, id_fournisseur)
VALUES (10, 'croissant', 'Alimentaire', 0.90, 4);

Le fournisseur Boulangerie du Coin a l’identifiant 4. La quantité en stock n’est pas fournie ; la liste de colonnes permet de ne pas inventer cette donnée. Cette insertion suppose que la colonne omise accepte sa valeur par défaut ou NULL, aucune contrainte contraire n’étant donnée. Si le schéma réel exige une quantité non nulle, il faut obtenir cette information avant d’insérer.

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

Question 2

#

Livres en Folie déménage au 78 Rue des Jeux à Elbeuf, France. Écrire la mise à jour.

Comprendre la correction
UPDATE Fournisseurs
SET adresse = '78 Rue des Jeux', ville = 'Elbeuf', pays = 'France'
WHERE id_fournisseur = 3;

Le même fournisseur conserve son identifiant. On ne crée pas une nouvelle ligne, car tous ses produits doivent continuer à le référencer.

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

Question 3

#

Décrire le résultat de SELECT nom FROM Produits WHERE categorie = 'Alimentaire';.

Comprendre la correction

La requête donne le nom de chaque produit de catégorie Alimentaire. Sur l’extrait initial : Yaourts blanc x4, Lait et Pain. Si l’insertion de Q1 a été exécutée auparavant, croissant apparaît aussi. Le résultat ne contient ni les prix ni les fournisseurs.

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

Question 4

#

Afficher les détails des commandes dont le total est supérieur ou égal à 1000€.

Comprendre la correction
SELECT Details.*
FROM Details JOIN Commandes ON Details.id_commande = Commandes.id_commande
WHERE Commandes.total_commande >= 1000;

On filtre le montant total de la commande, pas la valeur d’une seule ligne. La commande 2 vaut 1150€ ; ses deux détails, identifiants 3 et 4, sont donc tous deux renvoyés.

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

Question 5

#

Afficher les noms des fournisseurs basés en Espagne ou en Italie.

Comprendre la correction
SELECT nom FROM Fournisseurs
WHERE pays = 'Espagne' OR pays = 'Italie';

Une ligne doit satisfaire l’un des deux pays. AND demanderait deux valeurs différentes dans la même colonne, ce qui serait impossible ici. Le résultat contient Moda e stile et Estilo Español.

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

Question 6

#

Afficher les noms de tous les fournisseurs qui ont vendu des produits alimentaires.

Indice

Un produit au catalogue n’est pas nécessairement présent dans une commande.

Comprendre la correction
SELECT DISTINCT Fournisseurs.nom
FROM Fournisseurs
JOIN Produits ON Fournisseurs.id_fournisseur = Produits.id_fournisseur
JOIN Details ON Produits.id_produit = Details.id_produit
WHERE Produits.categorie = 'Alimentaire';

Le verbe « ont vendu » conduit à vérifier une ligne de commande dans Details. Laiteries Unies a des produits alimentaires effectivement commandés dans l’extrait. DISTINCT évite de répéter son nom pour les yaourts et le lait.

Si l’on cherchait seulement les fournisseurs de produits alimentaires référencés au catalogue, on omettrait la jointure Details et on obtiendrait aussi Boulangerie du Coin. Les deux questions ne sont pas identiques.

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

Question 7

#

Afficher numéro et date des commandes, et nom du fournisseur, pour les commandes de produits de catégorie Vêtement.

SQLRelier les commandes de vêtements à leurs fournisseursÉcrivez votre solution et mettez-la à l’épreuve

Renvoyez le numéro et la date des commandes ainsi que le nom du fournisseur pour les produits de catégorie Vêtement effectivement commandés. Les colonnes utilisent les noms des tableaux détaillés du sujet.

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

Les cas de test proposés :

  • Extraits officiels du supermarché : Les quatre tables reprennent les extraits du PDF. La commande 2 contient des vêtements de deux fournisseurs ; elle produit donc deux lignes distinctes.
  • Cas complémentaire : même fournisseur, plusieurs produits : Jeu complémentaire pédagogique, distinct des données officielles. Les deux vêtements du même fournisseur appartiennent à la même commande : le triple projeté ne doit apparaître qu’une fois. Un vêtement en stock mais jamais commandé ne doit pas créer une ligne.
Comprendre la correction
SELECT DISTINCT Commandes.id_commande, Commandes.date_commande, Fournisseurs.nom
FROM Commandes
JOIN Details ON Commandes.id_commande = Details.id_commande
JOIN Produits ON Details.id_produit = Produits.id_produit
JOIN Fournisseurs ON Produits.id_fournisseur = Fournisseurs.id_fournisseur
WHERE Produits.categorie = 'Vêtement';

Les quatre tables sont utiles : commande, date ; détail, produit commandé ; produit, catégorie et fournisseur ; fournisseur, nom. Dans l’extrait, la commande 2 du 08/12/2024 donne deux lignes, pour Moda e stile et Estilo Español. DISTINCT supprime seulement les triples identiques.

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

Un fournisseur de Toulouse dessert Bordeaux, Calais, Lyon, Marseille, Nantes, Paris et Strasbourg. La figure 2 utilise les initiales et les distances en kilomètres.

490465385300470600405600315340550250400CPSNLBTM
Figure 2 : réseau routier du sujet, distances en kilomètres
Lire les connexions du schéma
  • P relié à S : 490
  • P relié à L : 465
  • P relié à N : 385
  • P relié à C : 300
  • S relié à L : 470
  • S relié à C : 600
  • L relié à T : 405
  • L relié à N : 600
  • L relié à M : 315
  • N relié à B : 340
  • N relié à C : 550
  • T relié à B : 250
  • T relié à M : 400
InitialeVille
CCalais
PParis
SStrasbourg
NNantes
LLyon
BBordeaux
TToulouse
MMarseille

Question 8

#

Déterminer le plus court chemin en distance de Toulouse à Calais.

Comprendre la correction

Toulouse → Bordeaux → Nantes → Calais, soit 250 + 340 + 550 = 1140 km. Une route via Lyon et Paris vaut 405+465+300=1170 km, donc est un peu plus longue.

Sommet fixé dans un calcul de Dijkstra depuis ToulouseDistance minimale connue(km)
Toulouse0
Bordeaux250
Marseille400
Lyon405
Nantes590
Paris870
Strasbourg875
Calais1140

Les distances sont positives : fixer le plus petit coût provisoire permet de certifier le résultat, contrairement à un simple choix de la route sortante la plus courte.

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

Question 9

#

Donner le parcours en profondeur depuis Calais, avec choix alphabétique des voisins.

Comprendre la correction

C, N, B, T, L, M, P, S, soit Calais, Nantes, Bordeaux, Toulouse, Lyon, Marseille, Paris, Strasbourg.

On descend autant que possible : depuis Nantes, Bordeaux est le premier voisin non visité ; depuis Toulouse, Lyon précède Marseille. À Lyon, Marseille est explorée avantParis. Quand une branche est épuisée, on revient au dernier sommet ayant encore un voisin non visité.

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

Question 10

#

Donner le parcours en largeur depuis Calais, avec choix alphabétique des voisins.

Comprendre la correction

C, N, P, S, B, L, T, M. La première couche aprèsCalais estNantes, Paris, Strasbourg. En traitantNantes, on découvreBordeaux puisLyon. Bordeaux découvreToulouse avant queLyon ne découvreMarseille. Une ville déjà découverte n’est pas ajoutée une seconde fois.

Voir la question dans le sujet PDF, p. 16 (nouvel onglet)
graphe = {
  "Calais": {
    "Paris": 300,
    "Strasbourg": 600,
    "Nantes": 550
  },
  "Paris": {
    "Strasbourg": 490,
    "Lyon": 465,
    "Nantes": 385,
    "Calais": 300
  },
  "Strasbourg": {
    "Paris": 490,
    "Lyon": 470,
    "Calais": 600
  },
  "Nantes": {
    "Paris": 385,
    "Lyon": 600,
    "Bordeaux": 340,
    "Calais": 550
  },
  "Lyon": {
    "Paris": 465,
    "Strasbourg": 470,
    "Toulouse": 405,
    "Nantes": 600
  },
  "Bordeaux": {
    "Nantes": 340,
    "Toulouse": 250
  },
  "Toulouse": {
    "Lyon": 405,
    "Bordeaux": 250,
    "Marseille": 400
  },
  "Marseille": {
    "Toulouse": 400
  }
}

Question 11

#

Ajouter la route Lyon-Marseille de 315 km dans le dictionnaire fourni, où elle manque.

Comprendre la correction
graphe['Lyon']['Marseille'] = 315
graphe['Marseille']['Lyon'] = 315

Le réseau est non orienté et sa représentation stocke chaque arête dans les deux listes de voisinage. Modifier un seul sens ferait diverger le dictionnaire du graphe de la figure 2.

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

Question 12

#

Écrire distance(graphe, ville1, ville2), qui donne la distance si les villes sont adjacentes, sinonNone.

Comprendre la correction
def distance(graphe, ville1, ville2):
    if ville1 in graphe and ville2 in graphe[ville1]:
        return graphe[ville1][ville2]
    return None

On consulte l’arête directe dans le dictionnaire interne. Cette fonction ne cherche pas un itinéraire passant par d’autres villes. Le premier test protège aussi le cas d’un nom de ville absent du graphe.

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

On suppose déjà codée trouver_chemin(graphe, ville_depart, ville_destination), qui renvoie la liste des villes d’un chemin s’il existe.

Question 13

#

Écrire distance_totale, qui utilise trouver_chemin pour calculer la distance d’un chemin existant entre deux villes, sinonNone.

Comprendre la correction
def distance_totale(graphe, ville_depart, ville_destination):
    chemin = trouver_chemin(graphe, ville_depart, ville_destination)
    if chemin is None or len(chemin) == 0:
        return None
    total = 0
    for i in range(len(chemin) - 1):
        total += graphe[chemin[i]][chemin[i + 1]]
    return total

On additionne les poids de chaque paire de villes consécutives, jusqu’à l’avant-dernier indice. Pour une origine égale à la destination, un chemin d’un seul sommet a une distance 0. Le code suppose que l’absence de chemin est signalée par None ou une liste vide ; cette convention doit correspondre à la fonction fournie.

La distance calculée est celle du chemin renvoyé. Le sujet ne dit pas que trouver_chemin optimise la distance : on ne peut pas conclure que le total est minimal.

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

On ajoute les durées de trajet en heures arrondies à deux décimales :

graphe['Paris'] = {'Strasbourg': [490, 6.37], 'Lyon': [465, 5.58],
                    'Nantes': [385, 3.47], 'Calais': [300, 3.3]}

Question 14

#

Dans le graphe pondéré par[distance, durée], donner graphe['Paris']['Nantes'][1].

Comprendre la correction

3,47, de type float, soit 3,47 heures. L’indice 0 contient 385 km et l’indice 1 la durée. Une durée décimale 3,47 h ne signifie pas 3 h47 min.

Voir la question dans le sujet PDF, p. 17 (nouvel onglet)
Liaison, mêmes valeurs dans les deux sensDistance(km)Durée(h)Ratio affiché(h/km)
Paris-Strasbourg4906.370.013
Paris-Lyon4655.580.012
Paris-Nantes3853.470.009
Paris-Calais3003.30.011
Strasbourg-Lyon4704.230.009
Strasbourg-Calais60090.015
Lyon-Toulouse4054.860.012
Lyon-Nantes6007.20.012
Lyon-Marseille3152.840.009
Nantes-Bordeaux3404.080.012
Nantes-Calais5508.250.015
Toulouse-Bordeaux2502.50.010
Toulouse-Marseille40060.015
def ratio_duree_distance(graphe):
    for ville, connexions in ...:
        for destination, valeurs in ...:
            distance, duree = ...
            ratio = ...
            graphe[ville][destination].append(...)
    return graphe

Question 15

#

Compléter ratio_duree_distance, qui ajoute à chaque arête le ratio durée/distance.

Comprendre la correction
def ratio_duree_distance(graphe):
    for ville, connexions in graphe.items():
        for destination, valeurs in connexions.items():
            distance, duree = valeurs
            ratio = round(duree / distance, 3)
            graphe[ville][destination].append(ratio)
    return graphe

La première boucle parcourt les villes et leurs dictionnaires, la seconde les voisins et leurs listes. Le ratio est exprimé en heures par kilomètre, l’inverse d’une vitesse moyenne. L’arrondi à trois décimales reproduit les valeurs montrées dans le sujet :3,47/385≈0,009.

La fonction modifie les listes en place et suppose deux éléments au départ. L’appeler une seconde fois sans adaptation ferait échouer le dépaquetage ou ajouterait des données en double dans une autre version. Les deux sens d’une arête doivent recevoir les mêmes informations.

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

Le sujet présente un élève demandant à un assistant : « Écris un algorithme, en langage naturel, pour trouver un chemin entre deux villes en minimisant le ratio durée/distance où à chaque étape, on choisira l’arête avec le ratio le plus faible. » Cette demande est l’objet de l’analyse, pas une instruction de cette page.

Question 16

#

Nommer un algorithme qui construit la solution étape par étape en choisissant à chaque fois l’arête de plus faible ratio.

Comprendre la correction

Un algorithme glouton. Il choisit ce qui paraît le meilleur localement à chaque étape, sans examiner toutes les conséquences du choix. Ce principe ne garantit pas à lui seul un optimum global ni même l’arrivée à destination.

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

Question 17

#

Déterminer le chemin trouvé entre Toulouse et Calais avec cet algorithme.

Indice 1

À Bordeaux, comparez les deux sorties, y compris celle qui retourne à Toulouse.

Indice 2

Précisez si l’algorithme a le droit de revenir dans une ville déjà visitée.

Comprendre la correction

Si l’on interdit de revisiter les villes déjà parcourues, on obtient Toulouse → Bordeaux → Nantes → Paris → Calais. Les choix successifs de ratio sont 0,010 ;0,012 ;0,009 ;0,011. Le trajet mesure 1275 km et prend 13,35 h selon les données arrondies.

La consigne originale ne précise toutefois pas cette interdiction. Sans elle, Toulouse choisitBordeaux(ratio 0,010), puis Bordeaux choisitToulouse(0,010, inférieur aux 0,012 vers Nantes) : l’algorithme boucle entre ces deux villes et ne rejoint jamaisCalais. Il n’existe donc pas de trajet terminé déterminé par le seul prompt sans ajouter une règle contre les cycles.

Même avec cette règle, minimiser chaque ratio d’arête ne prouve pas que le ratio total somme(durées)/somme(distances) est minimal. Ce ratio global n’est pas la somme des ratios locaux.

Voir la question dans le sujet PDF, p. 19 (nouvel onglet)
Un glouton peut-il se perdre entre deux villes ?Un atelier pour expérimenter

Comparez plus courte distance, plus courte durée et choix glouton du ratio. Activez ou non l’interdiction de revisiter une ville et observez le trajet construit.

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

Toulouse → Bordeaux → Nantes → Paris → Calais

Un optimum de distance, de durée et un choix de ratio local sont des problèmes différents. Ici les chemins simples peuvent être énumérés pour vérifier les minima globaux.

ÉtapeDistanceDuréeRatio local
Toulouse → Bordeaux2502.500.010
Bordeaux → Nantes3404.080.012
Nantes → Paris3853.470.009
Paris → Calais3003.300.011

Avant d’exécuter un algorithme, préciser ses règles d’arrêt et de visite. Un bon choix local n’est pas une preuve de terminaison ni d’optimalité globale.

Revoir les notions de cet exercice

Du sujet à la méthode

Votre prochaine séance de révision

  • Dans Playfair, écrivez les deux coordonnées et le cas applicable avant de remplacer les lettres.
  • Pour les permissions, distinguez l’effet demandé par chmod et l’autorisation de lancer cette modification.
  • Pour le glouton final, explicitez l’interdiction des cycles ; sans elle, le premier aller-retour suffit à invalider le trajet attendu.

Retrouver ces notions dans d’autres sujets

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

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