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
Alice, Bob et Mallory : distinguer chiffrement, signature et empreinte
Alice et Bob communiquent sur un réseau ouvert. Eve écoute les messages ; Mallory veut usurper une identité. L’exercice distingue le fait de cacher un contenu et celui d’en authentifier l’auteur. Alice ne connaît Bob que par les réseaux sociaux et veut lui transmettre ce rendez-vous :
m0 = "Rendez-vous à 16h place de la liberté. Signé : Alice."La fonction code ci-dessous est une opération abstraite fournie par l’énoncé. On applique ses propriétés annoncées ; il n’est pas demandé d’implémenter un algorithme cryptographique réel.
Partie A : chiffrement symétrique
Avec une clé unique, code(m, cle) transforme une chaîne en chaîne, et code(code(m, cle), cle) == m. Alice calcule m1 = code(m0, cle), puis transmet sur le réseau le message chiffré et la clé.
Question 1
#Donner l’instruction que Bob doit écrire pour déchiffrer le message et affecter le résultat à m2.
Indice
Le chiffrement symétrique utilise la même clé pour l’aller et le retour.
Comprendre la correction
m2 = code(m1, cle)On applique une seconde fois la transformation avec la même clé. La propriété donnée garantit alors m2 == m0. Mais Eve a reçu à la fois m1 et cle : elle peut effectuer exactement la même opération. Le problème vient donc de la transmission de la clé sur le canal observé, pas d’une erreur dans cette instruction.
Partie B : chiffrement asymétrique
Chaque personne possède une paire de clés associées. Pour une paire (cle1, cle2), le sujet suppose code(code(m, cle1), cle2) == code(code(m, cle2), cle1) == m. Il suppose aussi qu’on ne peut pas déduire une clé de l’autre ni retrouver un message sans la clé inverse nécessaire.
Alice possède (cle1_a, cle2_a), Bob (cle1_b, cle2_b). Ils publient cle1_a et cle1_b, et gardent secrètes leurs clés cle2.
Question 2
#Alice calcule maintenant m1 = code(m0, cle1_b) avec la clé diffusée par Bob. Donner l’instruction qui permet à Bob de retrouver le rendez-vous.
Indice
Repérez d’abord la clé utilisée par Alice, puis son associée.
Comprendre la correction
m2 = code(m1, cle2_b)La transformation inverse emploie l’autre clé de la paire de Bob. Il faut choisir la clé du destinataire Bob, et non une clé d’Alice. Comme m1 a été créé avec cle1_b, l’application de cle2_b retrouve m0.
Question 3
#Justifier que, dans les hypothèses de l’énoncé, Eve ne peut désormais pas prendre connaissance du message secret.
Indice
Connaître la clé qui chiffre n’est pas connaître celle qui permet de déchiffrer.
Comprendre la correction
Eve connaît le chiffré m1 et la clé publique cle1_b. Pour inverser ce chiffrement, elle aurait besoin de cle2_b, que Bob n’a pas publiée. Le sujet suppose qu’il est impossible de la déduire de cle1_b et de déchiffrer sans elle.
Cette conclusion utilise bien les hypothèses du modèle : notamment, la clé publique reçue doit être celle de Bob. Empêcher Mallory de substituer une autre clé demanderait d’authentifier cette clé, par exemple au moyen d’un certificat dans un protocole réel.
Question 4
#Dans cet échange, indiquer quelle est la clé publique et quelle est la clé privée.
Indice
Une clé privée reste connue de son seul détenteur.
Comprendre la correction
Pour Bob : cle1_b est publique et cle2_b est privée. De même, Alice publie cle1_a et conserve cle2_a. Le statut public ou privé vient de la règle de diffusion, pas d’un rôle immuable « chiffrement » ou « déchiffrement » dans le modèle abstrait.
Question 5
#Bob veut envoyer Bien reçu. Rendez-vous à 16h donc. à Alice sans qu’Eve le lise. Donner, dans l’ordre, les instructions de Bob puis d’Alice.
Indice
La confidentialité vise le destinataire : à qui appartient la clé privée qui doit déchiffrer ?
Comprendre la correction
# Chez Bob
reponse = 'Bien reçu. Rendez-vous à 16h donc.'
reponse_chiffree = code(reponse, cle1_a)
# Bob transmet reponse_chiffree à Alice.
# Chez Alice
reponse_lue = code(reponse_chiffree, cle2_a)Le destinataire a changé : on utilise donc la paire d’Alice. Bob peut accéder à sa clé publique pour préparer l’envoi, tandis qu’Alice possède seule la clé privée inverse. Chiffrer avec la clé privée de Bob ne cacherait pas ce message : tout le monde dispose de sa clé publique pour inverser l’opération selon le modèle.
Question 6
#Expliquer pourquoi il faut deux clés, plutôt que la seule clé privée.
Indice
Revenez à la fuite de la clé symétrique dans la partie A.
Comprendre la correction
Alice doit disposer d’une information lui permettant de préparer un message que seul Bob pourra ouvrir. La clé publique remplit le premier rôle sans révéler l’information secrète nécessaire au second. Avec une unique clé symétrique, Alice devrait connaître le même secret que Bob et il faudrait le partager par un canal sûr avant leur premier échange.
Deux clés permettent ainsi de séparer « autoriser tout le monde à m’envoyer un secret » de « m’autoriser seul à lire ce secret ». Il ne s’agit pas de doubler arbitrairement une clé : les deux clés ont des rôles complémentaires.
Partie C : signer le message
Alice transmet ici son message en clair. Mallory l’intercepte et le remplace par :
m3 = "Rendez-vous à 15h rue de la dictature. Signé : Alice."Pour contrer cette substitution, Alice calcule m0_s = code(m0, cle2_a), puis envoie le message et cette signature. On suppose impossible de construire la signature d’un message sans la clé privée ayant servi à cette transformation.
Question 7
#Expliquer comment m0, sa signature m0_s et cle1_a permettent à Bob de vérifier la provenance du message et de détecter la substitution de m3 par Mallory.
Indice
Bob ne doit pas seulement déchiffrer la signature : il doit comparer son résultat au message effectivement reçu.
Comprendre la correction
authentique = (code(m0_s, cle1_a) == m0)Bob transforme la signature avec la clé publique authentique d’Alice et compare le résultat au message reçu. La signature a été produite avec cle2_a : selon les hypothèses du sujet, seul son détenteur pouvait fabriquer cette signature du message.
Si Mallory remplace seulement m0 par m3, le résultat obtenu à partir de la signature reste m0 et ne correspond plus au texte reçu. Fabriquer la bonne signature de m3 nécessiterait la clé privée d’Alice. Une simple mention « Signé : Alice » dans le texte ne fournit, elle, aucune preuve.
Le contenu reste public dans cette partie : la signature apporte une vérification de l’origine et de l’intégrité dans ce modèle, pas la confidentialité.
Partie D : signer une empreinte
Transmettre le message et sa signature complète double approximativement le volume. On propose de signer une réduction du message. ord donne le code d’un caractère : ord('a') = 97, ord('b') = 98, ord('c') = 99. abs(4) = 4 et abs(-6) = 6.
Pour une chaîne de longueur n, on additionne, pour i allant de 1 à n - 1, i × abs(ord(s[i]) - ord(s[i - 1])). Pour abca :
| Indice | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| Caractère | a | b | c | a |
1 × |98 - 97| + 2 × |99 - 98| + 3 × |97 - 99| = 9. Le PDF évoque ASCII, mais ses messages contiennent des accents : pour retrouver les résultats annoncés avec Python, on utilise bien ord, qui renvoie les points de code Unicode (et coïncide avec ASCII sur les caractères ASCII).
Question 8
#Donner, sans justifier, la réduction de la chaîne bac.
Indice
Il y a deux différences successives, pondérées par 1 et par 2.
Comprendre la correction
5.
Pour comprendre le calcul après avoir répondu : 1 × |97 - 98| + 2 × |99 - 97| = 1 + 4 = 5. Le premier caractère sert au premier écart ; il n’ajoute pas un terme d’indice zéro.
Question 9
#Écrire la fonction Python reduction. Elle doit notamment donner 9 pour abca, 62 073 pour m0 et 53 681 pour m3.
Indice
Traduisez un terme de la somme, puis placez-le dans une boucle qui commence à 1.
Comprendre la correction
def reduction(s):
total = 0
for i in range(1, len(s)):
total += i * abs(ord(s[i]) - ord(s[i - 1]))
return totalLe parcours commence à 1 pour que s[i - 1] désigne toujours le caractère précédent, et s’arrête au dernier caractère. À chaque tour, on calcule la différence des codes, on prend sa valeur absolue, puis on multiplie par l’indice courant.
L’ordre des opérations compte : appliquer abs à la somme finale ne reproduirait pas la somme des valeurs absolues demandée. Pour une chaîne vide ou de longueur 1, il n’existe aucune paire consécutive ; la boucle est vide et le résultat vaut 0.
assert reduction('abca') == 9
assert reduction('bac') == 5
assert reduction('') == 0
assert reduction('a') == 0
assert reduction(m0) == 62073
assert reduction(m3) == 53681Question 10
#Alice calcule m0_r = str(reduction(m0)), puis m0_s = code(m0_r, cle2_a). Décrire comment Bob vérifie le message reçu.
Indice
Comparez deux valeurs de même type, obtenues indépendamment.
Comprendre la correction
empreinte_recue = code(m0_s, cle1_a)
empreinte_calculee = str(reduction(message_recu))
coherent = (empreinte_recue == empreinte_calculee)Bob vérifie la signature avec la clé publique d’Alice et recalcule lui-même l’empreinte du texte reçu. La conversion str est nécessaire : la fonction code manipule des chaînes, alors que reduction renvoie un entier. Dans l’exemple, substituer m3 donne "53681" au lieu de "62073" et la comparaison échoue.
Limite de cette réduction : deux messages distincts peuvent avoir la même valeur. Par exemple reduction('abc') == reduction('cba') == 3. La comparaison décrit bien le mécanisme demandé, mais cette réduction pédagogique ne permet pas de garantir l’authenticité de n’importe quel texte : une véritable signature compacte utilise une fonction de hachage cryptographique adaptée.
Question 11
#On propose de ne prendre que les dix premiers indices du message dans le calcul de la réduction. Commenter cette approche.
Indice
Comparez les débuts de m0 et m3, puis repérez où leurs informations importantes changent.
Comprendre la correction
Elle laisse toute la fin du message hors du contrôle. Mallory peut modifier le rendez-vous, l’adresse ou un autre passage situé après le préfixe signé sans changer la réduction. Dans cet exercice, les deux messages commencent par le même préfixe Rendez-vous : une réduction limitée au début ne distinguerait même plus le faux rendez-vous donné.
Réduire le travail de calcul en ignorant une partie du message ne résout donc pas le problème de sécurité. Pour une empreinte utile, chaque partie du texte doit influencer le résultat ; cela reste une condition nécessaire, mais pas suffisante, puisqu’il faut aussi rendre la recherche de collisions difficile.
Partie E : réunir confidentialité et signature
Dans la partie D, Eve pouvait lire le message transmis en clair. On veut à présent protéger à la fois le contenu et son origine.
Question 12
#Proposer un protocole permettant à Alice d’envoyer un message confidentiel à Bob tout en certifiant son origine. Détailler aussi le déchiffrement et la vérification chez Bob.
Indice
La clé du destinataire protège le secret ; la clé privée de l’auteur produit la signature.
Comprendre la correction
Il faut combiner deux paires et deux objectifs : la clé publique de Bob pour la confidentialité vers Bob, et la clé privée d’Alice pour une signature d’Alice. Dans le modèle de la partie C, on peut signer le message complet pour éviter la faiblesse de la réduction étudiée :
# Chez Alice
signature = code(m0, cle2_a)
message_chiffre = code(m0, cle1_b)
signature_chiffree = code(signature, cle1_b)
# Envoyer message_chiffre et signature_chiffree.
# Chez Bob
message_lu = code(message_chiffre, cle2_b)
signature_lue = code(signature_chiffree, cle2_b)
authentique = (code(signature_lue, cle1_a) == message_lu)Bob doit posséder sa clé privée pour ouvrir les deux éléments transmis. Il vérifie ensuite que la signature d’Alice correspond exactement au contenu déchiffré. Chiffrer également la signature complète empêche Eve de retrouver le message en lui appliquant la clé publique d’Alice.
On peut garder une signature plus courte en signant l’empreinte comme dans la partie D, puis en chiffrant message et signature pour Bob ; la vérification compare alors les empreintes. Mais avec la réduction simplifiée de cet exercice, cela conserve le risque de collision expliqué à la question 10. Pour une garantie réelle, il faut une fonction de hachage et un schéma de signature cryptographiques adaptés, ainsi que des clés publiques authentifiées.
Une signature du début protège-t-elle la fin du message ?Un atelier pour expérimenter
Comparez le vrai rendez-vous et celui de Mallory. Faites varier le nombre de caractères pris en compte dans la réduction : vous verrez à quel moment leurs contenus commencent à être distingués. Ce calcul illustre une faiblesse ; il ne constitue pas un algorithme cryptographique.
Lire le résultat de l’expérience initiale
Même réduction : la comparaison ne détecte pas la différence.
Le début des deux rendez-vous est identique. Les différences importantes ne sont prises en compte que lorsqu’on dépasse ce préfixe.
| Message | Préfixe analysé | Réduction |
|---|---|---|
| Rendez-vous à 16h place de la liberté. Signé : Alice. | Rendez-vou | 1259 |
| Rendez-vous à 15h rue de la dictature. Signé : Alice. | Rendez-vou | 1259 |
Confidentialité, authenticité et résistance aux collisions sont des propriétés différentes. La vérification doit porter sur ce qu’on prétend protéger.
Exercice 2 · 6 points
Ventes d’un magasin : requêtes SQL et erreurs de dictionnaires
Un magasin de bricolage utilise quatre tables. Les attributs ref_client, ref_produit, ref_remise et ref_vente sont les clés primaires de leurs tables respectives. Dans remise, valeur est un taux exprimé en pourcentage. Les tableaux suivants sont des extraits.
client
| ref_client | nom | prenom | telephone | |
|---|---|---|---|---|
| 25123 | Renaud | Martine | renaudm@tmail.com | 0601020304 |
| 25137 | Dupont | Jacques | dj@mail.fr | 0604030201 |
| 25145 | Pasteur | Emile | pasteur0@dmail.fr | 0611121314 |
| 25149 | Eiffel | Franck | eiffel95@popmail.fr | 0614131211 |
| 25189 | Kanek | Elise | ekanek@mail.fr | 0600112233 |
| 25322 | Shar | Sofia | shs@fmail.fr | 0644332211 |
produit
| ref_produit | designation | type | prix_unitaire |
|---|---|---|---|
| 85235 | Marteau TAP | Outillage | 15.89 |
| 86782 | Rouleau peinture | Outillage | 9.55 |
| 89363 | Niveau à bulle | Outillage | 8.2 |
| 89552 | Clous inox | Visserie | 4.5 |
| 89588 | Sac sable | Materiau | 11.6 |
remise
| ref_remise | designation | valeur | date_debut | date_fin |
|---|---|---|---|---|
| 289 | Client en or | 25 | 2025/01/01 | 2025/12/31 |
| 326 | Fin de serie | 40 | 2025/01/01 | 2025/12/31 |
| 275 | Jour fou | 30 | 2025/03/17 | 2025/03/19 |
| 263 | Soldes hiver | 20 | 2025/01/01 | 2025/02/01 |
vente
| ref_vente | date | ref_produit | ref_client | quantité | ref_remise |
|---|---|---|---|---|---|
| 25631 | 2025/03/16 | 86782 | 25123 | 2 | 289 |
| 25632 | 2025/03/16 | 89363 | 25123 | 1 | 289 |
| 25633 | 2025/03/17 | 85235 | 25149 | 1 | 326 |
| 25634 | 2025/03/18 | 89588 | 25145 | 5 | 275 |
Les clauses utilisables comprennent SELECT, FROM, WHERE avec AND et OR, JOIN ... ON, UPDATE, INSERT, DELETE, DISTINCT et ORDER BY.
Question 1
#Expliquer le rôle d’une clé primaire et rappeler la contrainte de son choix.
Indice
L’identifiant doit désigner une seule ligne et être présent.
Comprendre la correction
Une clé primaire identifie sans ambiguïté chaque ligne d’une relation. Ses valeurs doivent être uniques et non nulles. Deux clients ne peuvent donc pas partager une même valeur de ref_client.
Une clé peut être composée de plusieurs attributs ; l’unicité porte alors sur leur combinaison. Le nom d’un client ne convient pas à lui seul si des homonymes sont possibles, même si les quelques lignes de l’extrait ont des noms différents.
Dans vente, ref_produit et ref_client sont des clés étrangères vers les tables de même nom. Le lien de remise permet également d’interpréter le taux associé à la vente.
Question 2
#Détailler un achat d’Emile Pasteur d’après les extraits : date, article ou articles et taux de remise éventuel.
Indice
Identifiez d’abord ref_client, puis lisez les références de la ligne correspondante dans vente.
Comprendre la correction
Emile Pasteur possède la référence client 25145. La vente 25634 indique un achat le 18 mars 2025 de 5 sacs de sable (produit 89588), avec la remise Jour fou de 30 % (référence 275).
On suit successivement la clé client, la ligne de vente, la référence produit et la référence remise. La valeur 275 n’est pas un taux : elle identifie la ligne dont le taux est 30. La quantité 5 n’est pas non plus cinq lignes de vente distinctes.
Question 3
#Ajouter Gilles Bertaut, référence 25345, e-mail gbertaut@fmail.fr, téléphone 0641424344.
Indice
Nommez les colonnes pour rendre explicite l’association de chaque valeur.
Comprendre la correction
INSERT INTO client (ref_client, nom, prenom, email, telephone)
VALUES (25345, 'Bertaut', 'Gilles', 'gbertaut@fmail.fr', '0641424344');L’ordre des valeurs correspond à celui des colonnes explicitement nommées. Le téléphone est une chaîne : ses chiffres ne sont pas destinés à un calcul et son zéro initial doit être conservé. Inverser le nom et le prénom donnerait une ligne valide en apparence, mais fausse.
Question 4
#Corriger l’e-mail de Sofia Shar avec la valeur shars@fmail.fr.
Indice
Retrouvez l’identifiant de la cliente, puis ciblez cette ligne.
Comprendre la correction
UPDATE client
SET email = 'shars@fmail.fr'
WHERE ref_client = 25322;La référence 25322 identifie précisément la cliente visible dans l’extrait. Seul l’e-mail est modifié. La clause WHERE évite de modifier l’ensemble des clients ; utiliser la clé est aussi plus robuste qu’un filtre sur un nom susceptible d’avoir des homonymes.
Les dates de toute la base sont des chaînes au format fixe aaaa/mm/jj. Par exemple, '2025/04/12' < '2025/05/03' est vrai.
Question 5
#Afficher une seule fois la référence de chaque client ayant effectué un achat à partir du 1er janvier 2025.
Indice
Filtrez les ventes, puis dédupliquez les références clients.
Comprendre la correction
SELECT DISTINCT ref_client
FROM vente
WHERE date >= '2025/01/01';La date appartient à la vente. Il n’est pas nécessaire de joindre la table client puisque la référence recherchée est déjà présente dans vente. DISTINCT retire les répétitions dues à plusieurs achats d’un même client.
« À partir du » inclut le 1er janvier : on utilise >=, pas >. Les chaînes se comparent correctement dans le format fixe année/mois/jour annoncé, car les composantes sont ordonnées de la plus importante à la moins importante et complétées sur la même largeur.
Question 6
#La tondeuse de référence 90222 présente un défaut. Obtenir les noms et téléphones des clients qui l’ont achetée depuis le 15 septembre 2024, début du lot défectueux.
SQLRappeler les acheteurs du lot défectueuxÉcrivez votre solution et mettez-la à l’épreuve
Renvoyez sans répétition le nom et le téléphone des clients ayant acheté le produit 90222 à partir du 15 septembre 2024 inclus. Conservez le format de date AAAA/MM/JJ du sujet.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Extraits officiels : aucune tondeuse dans les lignes montrées : Ces lignes viennent des extraits officiels. La tondeuse 90222 est mentionnée dans la question mais ne figure pas dans ces extraits ; ce jeu restreint ne produit aucun rappel.
- Cas complémentaire : date limite et doublons : Jeu complémentaire pédagogique, distinct des données officielles. Un achat à la date de début compte. Les achats répétés ne doivent pas provoquer deux lignes de rappel ; un autre produit ne correspond pas au lot.
- Cas complémentaire : achat uniquement le premier jour : Jeu complémentaire pédagogique, distinct des données officielles. Ce client n’a aucun achat ultérieur : un comparateur strict ferait réellement disparaître son rappel.
Indice
Quelles informations sont dans vente, et lesquelles faut-il chercher dans client ?
Comprendre la correction
SELECT DISTINCT c.nom, c.telephone
FROM client AS c
JOIN vente AS v ON v.ref_client = c.ref_client
WHERE v.ref_produit = 90222
AND v.date >= '2024/09/15';La table vente permet de sélectionner le produit et la date ; la table client fournit les coordonnées à rappeler. La jointure suit les références clients, et les deux conditions sont réunies avec AND.
La table produit n’est pas indispensable ici : la référence à filtrer est connue. Avec OR, on rappellerait notamment des clients ayant acheté n’importe quel article après la date, ce qui ne correspond pas à la campagne demandée.
Chaque table est désormais représentée par un dictionnaire : la clé primaire devient clé du dictionnaire et la valeur est la liste des autres attributs, dans l’ordre du tableau. Le PDF écrit Client puis utilise client, et présente une apostrophe fermante typographique ; on uniformise ici le nom et les guillemets pour pouvoir lire le code. La ligne incomplète d’Eiffel est conservée telle qu’elle apparaît dans cet exemple :
client = {
25123: ['Renaud', 'Martine', 'renaudm@tmail.com', '0601020304'],
25137: ['Dupont', 'Jacques', 'dj@mail.fr', '0604030201'],
25145: ['Pasteur', 'Emile', 'pasteur0@dmail.fr', '0611121314'],
25149: ['Eiffel', 'Franck', 'eiffel95@popmail.fr']
}vente = {
25631: ['2025/03/16', 86782, 25123, 2, 289],
25632: ['2025/03/16', 89363, 25123, 1, 289],
25633: ['2025/03/17', 85235, 25149, 1, 326],
25634: ['2025/03/18', 89588, 25145, 5, 275]
}select_tel(client, 25137) correspond à SELECT telephone FROM client WHERE ref_client = 25137;.
Question 7
#Compléter select_tel, qui reçoit un dictionnaire client et une référence, puis renvoie le téléphone :
def select_tel(client, ref_client):
return client[...][...]Indice
Il y a deux accès successifs, avec deux rôles différents : une clé puis un indice.
Comprendre la correction
def select_tel(client, ref_client):
return client[ref_client][3]Le premier accès utilise la clé du dictionnaire, ref_client. Le second sélectionne l’élément d’indice 3 de sa liste : nom en 0, prénom en 1, e-mail en 2, téléphone en 3. Ainsi, pour 25137, le résultat est '0604030201'.
Ce second accès suppose que la liste contient bien quatre éléments. Le dictionnaire illustré dans le PDF omet le téléphone de Franck Eiffel ; cette ligne incomplète provoquerait une autre erreur, étudiée dans l’atelier. Le contrat de la question et l’exemple d’appel concernent une ligne complète.
Question 8
#Pourquoi select_tel(client, 1234) provoque-t-elle KeyError: 1234, et à quelle situation du magasin cela correspond-il ?
Indice
L’exception cite la clé qui n’a pas été trouvée.
Comprendre la correction
La clé 1234 n’existe pas dans le dictionnaire client. L’accès client[1234] échoue avant même qu’on essaie de lire le téléphone. Cela correspond à une référence client absente des données utilisées, par exemple une référence saisie incorrectement ou un client qui n’a pas été enregistré.
Il ne s’agit pas d’un mauvais indice dans une liste : cette autre situation déclencherait IndexError. Le nom de l’exception indique donc quelle étape de l’accès a échoué.
Question 9
#Modifier select_tel pour renvoyer None au lieu d’une KeyError si la référence est absente.
Indice
Testez ref_client in client avant de lire la valeur associée.
Comprendre la correction
def select_tel(client, ref_client):
if ref_client in client:
return client[ref_client][3]
return NoneLe test d’appartenance intervient avant l’accès. Si la clé est présente, on utilise la ligne ; sinon on renvoie la valeur spéciale None, sans guillemets. Cette version protège précisément contre une clé absente, comme le demande la question.
Elle ne répare pas une liste d’attributs incomplète pour une clé existante. Ce serait un autre contrôle de données, à ajouter si le contrat de l’application doit couvrir cette situation. Mélanger ces deux erreurs empêcherait de diagnostiquer leur cause.
Question 10
#Écrire nb_produits(vente, ref_produit), qui renvoie la quantité totale vendue de la référence demandée. Pour les marteaux, on appelle nb_produits(vente, 85235).
Indice
Lisez la quantité dans chaque ligne retenue au lieu de compter seulement les lignes.
Comprendre la correction
def nb_produits(vente, ref_produit):
total = 0
for ligne in vente.values():
if ligne[1] == ref_produit:
total += ligne[3]
return totalLes valeurs du dictionnaire sont les lignes de vente. L’indice 1 contient la référence produit et l’indice 3 la quantité. À chaque ligne correspondant au produit demandé, on ajoute cette quantité au total.
Ajouter 1 compterait des lignes de vente, pas des unités. Dans l’extrait, le sac de sable apparaît dans une seule vente mais la quantité vendue est 5. Si aucune vente ne correspond, le total reste naturellement à 0.
assert nb_produits(vente, 85235) == 1
assert nb_produits(vente, 89588) == 5
assert nb_produits(vente, 99999) == 0Question 11
#On se limite exactement aux extraits. Pourquoi DELETE FROM produit WHERE ref_produit = 89363; doit-elle échouer avec foreign key constraint failed ?
Indice
Cherchez la référence 89363 dans la colonne ref_produit de vente.
Comprendre la correction
La vente 25632 référence encore le produit 89363, le niveau à bulle. Supprimer ce produit laisserait une vente pointant vers une référence qui n’existe plus. La clé étrangère de vente interdit cette incohérence et le SGBD refuse donc la suppression.
Ce n’est pas une erreur de syntaxe SQL : le sens de l’opération viole une contrainte du modèle. Le sujet demande de conserver ce refus ; il ne demande pas de supprimer les ventes pour forcer l’opération.
Question 12
#Réécrire delete_prod pour refuser la suppression d’un produit encore référencé par une vente :
def delete_prod(produit, vente, ref_produit):
del produit[ref_produit]Indice
Reproduisez avant del le contrôle de référence assuré par le SGBD.
Comprendre la correction
def delete_prod(produit, vente, ref_produit):
for ligne in vente.values():
if ligne[1] == ref_produit:
return False
if ref_produit not in produit:
return False
del produit[ref_produit]
return TrueAvant toute suppression, on parcourt les ventes. Dès qu’une ligne utilise la référence, on quitte la fonction sans modifier produit. C’est seulement après avoir vérifié toutes les lignes que l’on peut supprimer la clé.
La convention choisie ici est de renvoyer False en cas de refus ou de référence absente, et True si la suppression a été effectuée. Le refus serait aussi possible avec un message ou une exception adaptée, mais il doit laisser les dictionnaires inchangés. Ne supprimez pas d’abord le produit en espérant vérifier ensuite les références.
Diagnostiquer un accès au dictionnaire des clientsUn atelier pour expérimenter
Choisissez une référence et activez ou non le contrôle d’existence de la clé. Comparez une référence valide, une référence absente et la ligne d’Eiffel tronquée dans le dictionnaire du PDF.
Lire le résultat de l’expérience initiale
Résultat : 0604030201.
La clé mène à une liste complète ; son élément d’indice 3 est le téléphone.
| Indice | Attribut | Valeur de la ligne |
|---|---|---|
| 0 | nom | Dupont |
| 1 | prenom | Jacques |
| 2 | dj@mail.fr | |
| 3 | telephone | 0604030201 |
Un test doit viser une cause précise. Une clé absente et un enregistrement incomplet ne provoquent pas la même erreur.
Revoir les notions de cet exercice
Exercice 3 · 8 points
Jonglerie : construire puis parcourir un automate de siteswaps
Le siteswap est une notation des figures de jonglerie, comme 441, 7531 ou 453. Le sujet remplace les trajectoires physiques par un rythme régulier. Un entier de lancer indique le nombre de temps avant le retour d’une balle. Un état est une liste de 0 et de 1 : etat[i] = 1 signifie qu’une balle est disponible dans la main après i temps ; etat[0] indique la balle disponible immédiatement.
Partie A : un lancer, puis la gravité
e1 = [1, 0, 0, 1, 1, 0]Cet état décrit trois balles : une dans la main et deux qui reviennent dans 3 et 4 temps. L’indice maximal est 5 : la hauteur maximale vaut 5. Une balle doit être envoyée vers un emplacement libre, pour éviter deux arrivées simultanées.
| Figure 2 : lancer depuis e1 | Possible ? | Raison |
|---|---|---|
| 1 | Oui | Emplacement 1 libre |
| 2 | Oui | Emplacement 2 libre |
| 3 | Non | Une balle y est déjà attendue |
| 4 | Non | Une balle y est déjà attendue |
| 5 | Oui | Emplacement 5 libre |
Si etat[0] vaut 0, il n’y a rien à lancer : le seul lancer autorisé est alors, par convention, 0. Un lancer 0 est interdit lorsqu’une balle est dans la main.
Question 1
#Pour e2 = [1, 1, 0, 1, 0, 0], indiquer tous les lancers possibles.
Indice
Cherchez les zéros d’indice strictement positif, après avoir vérifié la position 0.
Comprendre la correction
2, 4 et 5. Une balle est disponible en position 0. Les positions positives libres sont 2, 4 et 5 ; 1 et 3 sont déjà réservées à une arrivée. Le lancer 0 est interdit puisque la main contient une balle.
Question 2
#Même question pour e3 = [0, 1, 1, 0, 1].
Indice
La première case décide d’abord si un lancer positif est possible.
Comprendre la correction
Seulement 0. La main est vide : on attend le temps suivant. La position 3 est libre, mais cela ne suffit pas à autoriser un lancer positif, puisqu’il n’y a pas de balle à envoyer.
Question 3
#Compléter les lignes 4, 7 et 8 de lancer_possible pour renvoyer True si le lancer est autorisé et False sinon :
def lancer_possible(etat, lancer):
if lancer >= len(etat) or lancer < 0:
return False
if lancer == 0 and ...:
return False
if lancer > 0:
if etat[0] == 0 or ...:
...
return TrueIndice
Écrivez les causes de refus, puis vérifiez qu’aucun cas invalide ne peut atteindre le dernier return.
Comprendre la correction
def lancer_possible(etat, lancer):
if lancer >= len(etat) or lancer < 0:
return False
if lancer == 0 and etat[0] == 1:
return False
if lancer > 0:
if etat[0] == 0 or etat[lancer] == 1:
return False
return TrueLe premier test élimine les indices hors du tableau. Pour le lancer 0, la présence d’une balle dans la main suffit à refuser. Pour un lancer positif, deux causes indépendantes imposent un refus : aucune balle disponible, ou un emplacement d’arrivée déjà occupé. On les réunit avec or.
Le retour True n’intervient qu’après avoir éliminé tous ces cas. Cette façon d’écrire est plus facile à contrôler qu’une grande condition unique mêlant bornes, main et destination.
Après chaque lancer, toutes les balles redescendent d’un cran. La figure 3 donne cet exemple avec e1 et un lancer 2 :
| Phase | État |
|---|---|
| Avant le lancer | [1, 0, 0, 1, 1, 0] |
| Balle placée en 2 | [0, 0, 1, 1, 1, 0] |
| Après la gravité | [0, 1, 1, 1, 0, 0] |
Question 4
#Depuis e2 = [1, 1, 0, 1, 0, 0], on lance 5. Donner le nouvel état après le lancer et la gravité.
Indice
Effectuez séparément le placement, puis le décalage.
Comprendre la correction
[1, 0, 1, 0, 1, 0]Avant la gravité, on retire la balle de la main et on l’inscrit en position 5 : [0, 1, 0, 1, 0, 1]. Puis chaque arrivée se rapproche d’un temps : on décale les cases vers les petits indices et on ajoute 0 à la fin. Le nombre de balles reste égal à trois.
Question 5
#Compléter la ligne 4 de lancer_balle ; plusieurs lignes sont autorisées. Le lancer est supposé valide. L’état d’origine ne doit pas être modifié :
def lancer_balle(etat, lancer):
nouvel_etat = [balle for balle in etat]
...
return nouvel_etatIndice
Le dernier indice redevient vide après la gravité ; la longueur reste constante.
Comprendre la correction
def lancer_balle(etat, lancer):
nouvel_etat = [balle for balle in etat]
nouvel_etat[0] = 0
if lancer > 0:
nouvel_etat[lancer] = 1
nouvel_etat.pop(0)
nouvel_etat.append(0)
return nouvel_etatLa copie protège l’entrée. On libère la main, puis on réserve l’arrivée si le lancer est positif. Le pop(0) effectue le décalage temporel et append(0) conserve la longueur. Pour un lancer 0 valide, aucune balle n’est ajoutée : seules les arrivées existantes se rapprochent.
Écrire nouvel_etat = etat au lieu de la copie créerait un alias, et la suite modifierait l’état fourni. Deux tests utiles sont donc la conservation du nombre de balles et l’égalité de l’entrée avec sa valeur avant l’appel.
Partie B : toutes les séquences possibles
Question 6
#Écrire liste_lancers_possibles(etat). On attend [1, 2, 5] pour e1 et [0] pour [0, 1, 1, 1, 0].
Indice
Parcourez tous les candidats et réutilisez le prédicat de la question 3.
Comprendre la correction
def liste_lancers_possibles(etat):
lancers = []
for lancer in range(len(etat)):
if lancer_possible(etat, lancer):
lancers.append(lancer)
return lancersLa fonction examine tous les indices autorisés par la taille du tableau, y compris 0. Elle laisse lancer_possible décider lesquels sont valides. On conserve ainsi une seule définition des règles de lancer, au lieu de la recopier avec le risque d’une divergence.
L’ordre croissant produit les listes des exemples. Une fonction qui testerait seulement les positions libres oublierait la condition sur la présence d’une balle dans la main.
Depuis e1, un lancer 1 donne [1, 0, 1, 1, 0, 0], d’où les lancers 1, 4 et 5 sont possibles. Les séquences de longueur 2 depuis e1 sont donc [1, 1], [1, 4], [1, 5], [2, 0] et [5, 0].
Pour une longueur n, la méthode est la suivante : si n vaut 0, la seule séquence est la séquence vide. Sinon, pour chaque premier lancer valide, on calcule l’état obtenu, puis toutes les suites de longueur n - 1 à partir de cet état ; on ajoute le premier lancer au début de chacune. Le squelette est :
def calcule_sequences(etat, n):
if n == 0:
return [[]]
else:
s_possibles = []
l_lancers = ...
for lancer in l_lancers:
etat2 = ...
s_etat2 = calcule_sequences(etat2, n - 1)
for ...:
s_possibles.append([lancer] + ...)
return s_possiblesQuestion 7
#Justifier que calcule_sequences est une fonction récursive.
Indice
Repérez l’appel portant exactement le nom de la fonction en cours de définition.
Comprendre la correction
Elle s’appelle elle-même dans calcule_sequences(etat2, n - 1). Un premier lancer transforme l’état, puis le même problème est résolu pour une séquence plus courte. La présence de boucles n’empêche pas une fonction d’être récursive.
Question 8
#Expliquer brièvement pourquoi la fonction termine pour un entier n positif. Les boucles for sont supposées bornées.
Indice
Donnez une quantité entière qui diminue et le cas où les appels s’arrêtent.
Comprendre la correction
À chaque appel récursif, le paramètre n diminue de 1. C’est un entier qui atteint donc 0 après un nombre fini d’appels le long de chaque branche. À 0, la fonction renvoie immédiatement [[]], sans nouvel appel. Chaque nœud n’a qu’un nombre fini de branches puisque les boucles sont bornées.
Le nombre de possibilités peut augmenter fortement, mais une exécution longue n’est pas une absence de terminaison. Le raisonnement utilise la précondition n entier non négatif ; il ne justifierait pas un appel avec n négatif.
Question 9
#Compléter les lignes 10, 12, 14 et 15 de calcule_sequences.
PythonExplorer toutes les suites de lancers possiblesÉcrivez votre solution et mettez-la à l’épreuve
Complétez calcule_sequences(etat, n). etat est le calendrier binaire des retombées : etat[0] indique si une balle arrive maintenant. Les fonctions liste_lancers_possibles et lancer_balle sont fournies. Renvoyez toutes les listes de n lancers autorisés successivement, sans modifier etat. À n=0, il existe une séquence : la séquence vide. Les tests acceptent n’importe quel ordre des séquences.
def calcule_sequences(etat, n):
# À vous de jouer
passLes cas de test proposés :
- Une séquence de longueur zéro : [] voudrait dire aucune solution ; [[]] représente l’unique choix consistant à ne lancer plus rien.
- Une balle disponible, deux hauteurs libres : Le lancer 0 est interdit lorsqu’une balle doit être relancée ; les positions 1 et 2 sont libres.
- Le calendrier change après le lancer : Après 2, aucune balle n’arrive immédiatement : le second lancer vaut obligatoirement 0.
- Aucune balle au premier instant : On ne peut pas inventer une balle disponible : le seul choix est d’attendre.
- Une impasse produit zéro séquence : Toutes les futures cases sont occupées et une balle arrive : aucun lancer positif n’est autorisé.
- Branches indépendantes et état conservé : Préfixer un suffixe doit créer une nouvelle liste ; les branches ne se mélangent pas.
Indice
La dernière boucle parcourt les séquences renvoyées, et non les valeurs de l’état.
Comprendre la correction
def calcule_sequences(etat, n):
if n == 0:
return [[]]
else:
s_possibles = []
l_lancers = liste_lancers_possibles(etat)
for lancer in l_lancers:
etat2 = lancer_balle(etat, lancer)
s_etat2 = calcule_sequences(etat2, n - 1)
for sequence in s_etat2:
s_possibles.append([lancer] + sequence)
return s_possiblesLa liste des premiers lancers provient de liste_lancers_possibles. Pour chacun, lancer_balle construit l’état à partir duquel il reste n - 1 choix à effectuer. Enfin, on préfixe chaque suffixe renvoyé avec [lancer].
Le cas de base [[]] représente une collection contenant une séquence vide. S’il était remplacé par [], la dernière boucle n’aurait aucun suffixe auquel ajouter le premier lancer et toutes les solutions disparaîtraient. La concaténation crée une nouvelle liste et évite de modifier les suffixes réutilisés.
Avec n = 2 et le premier lancer 1, les suffixes sont [1], [4] et [5] ; on construit donc [1, 1], [1, 4] et [1, 5]. Les branches commençant par 2 et 5 ajoutent chacune leur suite terminée par 0.
Faire grandir l’arbre des séquences depuis e1Un atelier pour expérimenter
Choisissez une longueur de 0 à 4. Les séquences sont calculées en appliquant les règles de lancer après chaque état, et non en combinant librement les nombres.
Lire le résultat de l’expérience initiale
5 séquence(s) valide(s) de longueur 2.
Chaque ligne est une branche complète. Son état final sert de point de départ pour prolonger la séquence au tour suivant.
| Lancers | État final |
|---|---|
| [1, 1] | [1, 1, 1, 0, 0, 0] |
| [1, 4] | [0, 1, 1, 1, 0, 0] |
| [1, 5] | [0, 1, 1, 0, 1, 0] |
| [2, 0] | [1, 1, 1, 0, 0, 0] |
| [5, 0] | [0, 1, 1, 1, 0, 0] |
Une séquence est une succession de choix dépendants : le lancer précédent détermine les choix suivants.
Partie C : un automate d’états
On représente à présent les états par des chaînes de bits. Un arc de e vers f, étiqueté n, signifie qu’un lancer n transforme e en f. La figure suivante donne l’automate à deux balles et hauteur maximale 4 :
Lire les connexions du schéma
- 10010 vers 10100 : 1
- 10010 vers 01100 : 2
- 10010 vers 00110 : 4
- 10100 vers 11000 : 1
- 10100 vers 01100 : 3
- 10100 vers 01010 : 4
- 11000 vers 10100 : 3
- 11000 vers 11000 : 2
- 11000 vers 10010 : 4
- 01010 vers 10100 : 0
- 01100 vers 11000 : 0
- 00110 vers 01100 : 0
Question 10
#Compléter le dictionnaire des listes d’adjacence de l’automate de la figure 4. Chaque tuple contient le lancer puis l’état d’arrivée :
automate = {
'11000': [(3, '10100'), (2, '11000'), (4, '10010')],
'01010': [(0, '10100')],
'10100': ...,
...: [(0, '11000')],
...: ...,
...: ...
}Indice
Lisez toutes les flèches qui partent de chaque sommet, avec leur étiquette.
Comprendre la correction
automate = {
'11000': [(3, '10100'), (2, '11000'), (4, '10010')],
'01010': [(0, '10100')],
'10100': [(1, '11000'), (3, '01100'), (4, '01010')],
'01100': [(0, '11000')],
'10010': [(1, '10100'), (2, '01100'), (4, '00110')],
'00110': [(0, '01100')]
}Chaque clé représente un état de départ et chaque tuple un arc sortant. Par exemple, le tuple (2, '01100') sous '10010' signifie qu’un lancer 2, gravité incluse, fait passer de cet état à 01100. La boucle (2, '11000') doit être conservée : un lancer peut ramener au même état.
Les six états du graphe possèdent deux balles et une dernière case nulle. Cette dernière case correspond à l’emplacement le plus haut, remis à zéro après le décalage temporel. Il faut recopier les arcs orientés, pas ajouter automatiquement leurs inverses.
Question 11
#Écrire lancer_balle_automate, qui reçoit l’automate, un état et un lancer. Elle renvoie l’état obtenu, ou la chaîne vide si le lancer n’est pas possible. On attend '01100' depuis 10010 avec un lancer 2, et '' depuis 11000 avec un lancer 1.
Indice
Le retour d’échec se place après le parcours des arcs.
Comprendre la correction
def lancer_balle_automate(automate, etat, lancer):
if etat not in automate:
return ''
for etiquette, suivant in automate[etat]:
if etiquette == lancer:
return suivant
return ''On parcourt seulement les arcs sortants de l’état demandé. Le tuple est décomposé en son étiquette et son arrivée ; la première étiquette égale au lancer fournit la réponse. Si aucun arc ne convient, on renvoie la valeur d’échec imposée, la chaîne vide.
Le contrôle initial de présence de l’état rend aussi la fonction utilisable en cas d’état inconnu. Ne renvoyez pas la chaîne vide dès le premier arc dont l’étiquette ne convient pas : un arc ultérieur peut correspondre.
Un siteswap répète une séquence qui revient à son état de départ. Par exemple, 3, 1 parcourt 11000 → 10100 → 11000. La séquence 1, 2, 3, 4, 0 revient à 10100 ; la séquence 2 reste sur 11000. Exemples de parcours :
parcours_sequence_depart(automate, '11000', [3, 1]) # '11000'
parcours_sequence_depart(automate, '10010', [4, 0]) # '01100'
parcours_sequence_depart(automate, '10100', [3, 4]) # NoneQuestion 12
#Écrire parcours_sequence_depart, qui reçoit l’automate, un état de départ et une liste de lancers. Elle renvoie l’état final, ou None si un lancer est impossible.
Indice
Mettez à jour l’état courant après chaque lancer et vérifiez le signal d’échec immédiatement.
Comprendre la correction
def parcours_sequence_depart(automate, depart, sequence):
courant = depart
for lancer in sequence:
courant = lancer_balle_automate(automate, courant, lancer)
if courant == '':
return None
return courantLa variable courant représente l’état atteint après les lancers déjà traités. Chaque transition dépend donc de cet état actualisé, et non de l’état initial. Si la fonction précédente renvoie la chaîne vide, on convertit ce signal en None, comme l’exige ce nouveau contrat.
Une suite vide renvoie l’état de départ. Une suite non vide peut être entièrement réalisable sans ramener au départ : cette fonction vérifie le parcours, pas encore la possibilité de répéter la figure en boucle.
Question 13
#Écrire departs_siteswap, qui renvoie tous les états depuis lesquels la séquence est un siteswap. On attend ['10100'] pour [1, 2, 3, 4, 0], et [] pour [2, 1, 0].
Indice
Un parcours réalisable ne suffit pas : la dernière case doit être l’état du départ.
Comprendre la correction
def departs_siteswap(automate, sequence):
departs = []
for depart in automate:
if parcours_sequence_depart(automate, depart, sequence) == depart:
departs.append(depart)
return departsOn essaie chaque état du dictionnaire comme point de départ. Une séquence est une figure répétable depuis cet état seulement si son parcours est possible et se termine sur le même état. Comparer le résultat au départ vérifie directement ces deux conditions : None ne peut pas être égal à une chaîne d’état.
Tester uniquement resultat is not None accepterait des trajets ouverts qu’on ne pourrait pas nécessairement répéter. Il faut aussi conserver tous les départs valides, pas renvoyer dès le premier succès.
Inventer une figure et suivre chaque transitionUn atelier pour expérimenter
Choisissez un état de départ et saisissez jusqu’à douze lancers séparés par des virgules. Le tableau suit les transitions du véritable automate du sujet et indique si la figure revient au départ. Essayez 3,1 depuis 11000, puis 1,2,3,4,0 depuis 10100.
Lire le résultat de l’expérience initiale
La figure revient au départ : elle peut être répétée.
L’état final fournit exactement les conditions nécessaires pour recommencer le premier lancer.
| Temps | État avant | Lancer | État après |
|---|---|---|---|
| 1 | 11000 | 3 | 10100 |
| 2 | 10100 | 1 | 11000 |
Une figure répétable est un parcours fermé. Garder l’état courant permet de distinguer un échec, un chemin réalisable et un cycle.
Du sujet à la méthode
Votre prochaine séance de révision
- Expliquez séparément confidentialité, signature et empreinte : elles répondent à des questions différentes.
- Pour un accès composé, identifiez la première étape qui peut échouer avant d’ajouter un contrôle.
- Pour la jonglerie, tracez le placement de la balle puis le décalage temporel ; comparez ensuite les transitions calculées au graphe.
- Refaites la récursion à partir du cas n=0 et vérifiez le retour à l’état initial avant de déclarer une figure répétable.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 25-NSIJ2NC1 (PDF) · Publication d’origine (nouvel onglet). Corrigé et explications pédagogiques proposés par Sofien.
