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 / colonne | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | P | L | A | Y | F |
| 1 | I | R | B | C | D |
| 2 | E | G | H | J | K |
| 3 | M | N | O | Q | S |
| 4 | T | U | V | X | Z |
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 / colonne | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | E | P | R | U | V |
| 1 | D | N | S | I | A |
| 2 | B | C | F | G | H |
| 3 | J | K | L | M | O |
| 4 | Q | T | X | Y | Z |
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.
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_utilisesExemple 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 clefLa 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.
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 carreQuestion 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 carrei // 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.
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.
| Figure 2 : digramme | Positions | Règle | Chiffré |
|---|---|---|---|
| VI | V(4,2), I(1,0) | Rectangle : (4,0),(1,2) | TB |
| VE | V(4,2), E(2,0) | Rectangle : (4,0),(2,2) | TH |
| LA | L(0,1), A(0,2) | Même ligne : (0,2),(0,3) | AY |
| NS | N(3,1), S(3,4) | Même ligne avec retour : (3,2),(3,0) | OM |
| IX | I(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 digrammesQuestion 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.
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.
Question 6
#Chiffrer BONJOUR avec le carré PLAYFAIR.
Comprendre la correction
| Digramme | Règle et positions obtenues | Chiffré |
|---|---|---|
| BO | Même colonne 2 : B→H, O→V | HV |
| NJ | Rectangle : N(3,1), J(2,3) → Q(3,3), G(2,1) | QG |
| OU | Rectangle : O(3,2), U(4,1) → N(3,1), V(4,2) | NV |
| RX | Rectangle : 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.
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.
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 == i2On 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.
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_chiffreQuestion 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
passLes 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_chiffreDans 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.
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 resultatLe 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.
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 / colonne | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | P | L | A | Y | F |
| 1 | I | R | B (entrée) | C | D |
| 2 | E | G | H (sortie) | J | K |
| 3 | M | N | O (entrée) | Q | S |
| 4 | T | U | V (sortie) | X | Z |
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.shLe 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 -lL’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.
Question 2
#Décrire les permissions user, group, other de verify.sh.
Comprendre la correction
| Catégorie | Bits | Droits |
|---|---|---|
| Propriétaire alovelace | rwx | Lecture, écriture, exécution |
| Groupe www-data | rw- | Lecture, écriture, pas d’exécution |
| Autres | r-- | 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.
Question 3
#Quelle commande le propriétaire doit-il saisir pour supprimer concat.pls ?
Comprendre la correction
rm concat.plsLe 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.
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
| État | Permissions |
|---|---|
| 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.
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 5
#Donner en base 10 la valeur du motif binaire correspondant à rw-.
Comprendre la correction
6. r, w,-donnent 110, soit 1×4 + 1×2 + 0×1 = 6. Le caractère tiret vaut 0, il ne représente pas un nombre négatif.
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.
def mystere(chaine):
resultat = ''
for c in chaine:
if c == '-':
resultat = resultat + '0'
else:
resultat = resultat + '1'
return resultatQuestion 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.
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.
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 = permissionQuestion 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.
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_proprietaireCette 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.
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 resultatDeux 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.
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égorie | Symboles | Bits | Valeur |
|---|---|---|---|
| user | rwx | 111 | 7 |
| group | r-- | 100 | 4 |
| other | r-- | 100 | 4 |
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.
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.
Lire les connexions du schéma
- Details vers Produits : id_produit
- Details vers Commandes : id_commande
- Produits vers Fournisseurs : id_fournisseur
| Relation | Attributs et types de la figure |
|---|---|
| Produits | id_produit int(PK), nom str, categorie str, prix float, quantite_stock int, id_fournisseur int(FK) |
| Details | id_details int(PK), id_command int(FK), id_produit int(FK), quantite int, prix_unitaire float |
| Commandes | id_commande int(PK), date date, total float |
| Fournisseurs | id_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_commande | date_commande | total_commande |
|---|---|---|
| 1 | 03/06/2025 | 176 |
| 2 | 08/12/2024 | 1150 |
| 3 | 21/04/2025 | 155 |
| Produits.id_produit | nom | categorie | prix | quantite_stock | id_fournisseur |
|---|---|---|---|---|---|
| 1 | Yaourts blanc x 4 | Alimentaire | 2.8 | 50 | 2 |
| 2 | Lait | Alimentaire | 1.2 | 200 | 2 |
| 3 | Pain | Alimentaire | 1.5 | 100 | 4 |
| 4 | Harry Potter 1 | Livre | 15 | 20 | 3 |
| 5 | Jeu d’échecs | Jeux | 40 | 30 | 3 |
| 6 | T-shirt taille M | Vêtement | 10 | 80 | 1 |
| 7 | Jeans taille M | Vêtement | 25 | 60 | 5 |
| Fournisseurs.id_fournisseur | nom | adresse | ville | pays |
|---|---|---|---|---|
| 1 | Moda e stile | Via della Moda, 45 | Milano | Italie |
| 2 | Laiteries Unies | 22 Avenue des Vaches | Lisieux | France |
| 3 | Livres en Folie | 56 Boulevard des Livres | Toulouse | France |
| 4 | Boulangerie du Coin | 34 Rue du Pain | Nantes | France |
| 5 | Estilo Español | Calle de la Moda, 123 | Madrid | Espagne |
| Details.id_details | id_commande | id_produit | quantite | prix_unitaire |
|---|---|---|---|---|
| 1 | 1 | 1 | 20 | 2.8 |
| 2 | 1 | 2 | 100 | 1.2 |
| 3 | 2 | 6 | 40 | 10 |
| 4 | 2 | 7 | 30 | 25 |
| 5 | 3 | 5 | 2 | 40 |
| 6 | 3 | 4 | 5 | 15 |
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.
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.
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.
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.
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.
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.
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.
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.
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
| Initiale | Ville |
|---|---|
| C | Calais |
| P | Paris |
| S | Strasbourg |
| N | Nantes |
| L | Lyon |
| B | Bordeaux |
| T | Toulouse |
| M | Marseille |
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 Toulouse | Distance minimale connue(km) |
|---|---|
| Toulouse | 0 |
| Bordeaux | 250 |
| Marseille | 400 |
| Lyon | 405 |
| Nantes | 590 |
| Paris | 870 |
| Strasbourg | 875 |
| Calais | 1140 |
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.
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é.
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.
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'] = 315Le 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.
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 NoneOn 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.
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 totalOn 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.
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.
| Liaison, mêmes valeurs dans les deux sens | Distance(km) | Durée(h) | Ratio affiché(h/km) |
|---|---|---|---|
| Paris-Strasbourg | 490 | 6.37 | 0.013 |
| Paris-Lyon | 465 | 5.58 | 0.012 |
| Paris-Nantes | 385 | 3.47 | 0.009 |
| Paris-Calais | 300 | 3.3 | 0.011 |
| Strasbourg-Lyon | 470 | 4.23 | 0.009 |
| Strasbourg-Calais | 600 | 9 | 0.015 |
| Lyon-Toulouse | 405 | 4.86 | 0.012 |
| Lyon-Nantes | 600 | 7.2 | 0.012 |
| Lyon-Marseille | 315 | 2.84 | 0.009 |
| Nantes-Bordeaux | 340 | 4.08 | 0.012 |
| Nantes-Calais | 550 | 8.25 | 0.015 |
| Toulouse-Bordeaux | 250 | 2.5 | 0.010 |
| Toulouse-Marseille | 400 | 6 | 0.015 |
def ratio_duree_distance(graphe):
for ville, connexions in ...:
for destination, valeurs in ...:
distance, duree = ...
ratio = ...
graphe[ville][destination].append(...)
return grapheQuestion 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 grapheLa 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.
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.
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.
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.
| Étape | Distance | Durée | Ratio local |
|---|---|---|---|
| Toulouse → Bordeaux | 250 | 2.50 | 0.010 |
| Bordeaux → Nantes | 340 | 4.08 | 0.012 |
| Nantes → Paris | 385 | 3.47 | 0.009 |
| Paris → Calais | 300 | 3.30 | 0.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.
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.
