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
Classement d’athlètes : tri par sélection et ABR
Une compétition interscolaire regroupe 100 m, saut en longueur, lancer du poids et 1500 m. Chaque athlète reçoit un score global, somme des points convertis selon les règles du sujet :
| Épreuve | Points |
|---|---|
| 100 m | (20 - temps en secondes) × 10 |
| Longueur | distance en mètres × 20 |
| Poids | distance en mètres × 10 |
| 1500 m | 500 - temps en secondes |
On souhaite classer les athlètes du plus grand au plus petit score, d’abord avec dictionnaires et tri puis avec objets et arbre.
Question 1
#Alex réalise 13,0 s au 100 m, 5,2 m en longueur, 9,0 m au poids et 310,0 s au 1500 m. Calculer son score.
Indice
Convertissez séparément chaque performance avant de sommer.
Comprendre la correction
70 + 104 + 90 + 190 = 454 points. Les courses récompensent un temps plus court, tandis que les lancers et sauts récompensent une distance plus grande. La formule est celle du problème, pas un barème officiel d’athlétisme.
def nb_points(epreuve, valeur):
points = 0
if epreuve == "100m":
points = (20 - valeur) * 10
elif epreuve == "longueur":
...
elif epreuve == "poids":
...
elif epreuve == "1500m":
...
return ...Question 2
#Compléter nb_points aux lignes 6,8,10,11.
Indice
Les deux courses diminuent lorsque le temps augmente.
Comprendre la correction
def nb_points(epreuve, valeur):
points = 0
if epreuve == "100m":
points = (20 - valeur) * 10
elif epreuve == "longueur":
points = valeur * 20
elif epreuve == "poids":
points = valeur * 10
elif epreuve == "1500m":
points = 500 - valeur
return pointsChaque étiquette sélectionne sa formule. Le return final commun renvoie les points calculés. Avec le squelette donné, une étiquette inconnue renverrait 0 ; une validation explicite serait une extension possible, mais les quatre épreuves sont ici garanties.
l_athletes = [
{"nom":"Alex", "performances":{"100m":13.0,"longueur":5.2,"poids":9.0,"1500m":310.0}, "score":0},
{"nom":"Anna", "performances":{"100m":12.2,"longueur":5.8,"poids":8.5,"1500m":230.0}, "score":0},
{"nom":"Rayan", "performances":{"100m":13.5,"longueur":5.0,"poids":9.5,"1500m":205.0}, "score":0}
]def score(athlete):
total = 0
performances = athlete[...]
for epreuve in performances:
valeur = performances[...]
total += ...
athlete["score"] = totalQuestion 3
#Compléter score(athlete), qui calcule et enregistre le score du dictionnaire reçu.
Indice
Descendez d’abord dans le sous-dictionnaire performances.
Comprendre la correction
performances = athlete["performances"]
# Dans la boucle :
valeur = performances[epreuve]
total += nb_points(epreuve, valeur)Le dictionnaire externe stocke nom, performances et score ; performances est lui-même un dictionnaire indexé par épreuve. La fonction appelle nb_points pour chaque paire épreuve/valeur puis écrit le total dans athlete["score"]. Elle modifie l’objet reçu et ne renvoie pas le score. Il faut calculer tous les scores avant de trier les athlètes.
def classer(l):
n = len(l)
for i in range(n):
max_index = i
for j in range(i + 1, n):
if ...:
max_index = j
temp = l[i]
l[i] = l[max_index]
l[max_index] = tempQuestion 4
#Compléter le test de classer(l) pour trier les scores en ordre décroissant.
Indice
Cherchez le maximum de la partie encore non classée.
Comprendre la correction
if l[j]["score"] > l[max_index]["score"]:max_index désigne le meilleur score trouvé dans la zone non triée. On compare la clé score de deux dictionnaires, pas les dictionnaires eux-mêmes. Après la boucle interne, cet athlète est échangé avec la position i, qui devient définitive.
Question 5
#Identifier ce tri parmi insertion, sélection, bulles et fusion.
Indice
Le nom vient de la recherche répétée du meilleur élément restant.
Comprendre la correction
Tri par sélection : à chaque position, on sélectionne le maximum parmi les éléments restants puis on le place. On n’insère pas progressivement l’élément courant parmi ses prédécesseurs, comme le ferait le tri par insertion.
Question 6
#Donner le nombre de comparaisons d’éléments pour une liste de taille n.
Indice
Comptez la longueur de range(i+1,n) pour chaque i.
Comprendre la correction
(n-1)+(n-2)+…+1+0 = n(n-1)/2. La première boucle interne compare n-1 candidats, la suivante n-2, etc. Le nombre ne dépend pas de l’ordre initial des scores : même une liste déjà triée subit ces comparaisons. Le coût est donc quadratique, Θ(n²), pour cette mesure.
Partie B : classement par objets et ABR
class Athlete:
def __init__(self, nom, m100, longueur, poids, m1500):
self.nom = nom
self.m100 = m100
self.longueur = longueur
self.poids = poids
self.m1500 = m1500
self.score = self.calculer_score()
def calculer_score(self):
...Question 7
#Créer l’objet alex avec les performances d’Alex.
Indice
Respectez la signature du constructeur.
Comprendre la correction
alex = Athlete("Alex", 13.0, 5.2, 9.0, 310.0)L’ordre des arguments est nom, 100 m, longueur, poids, 1500 m. Le constructeur calculera son score par la méthode, donc on ne fournit pas 454 comme argument supplémentaire.
Question 8
#Écrire calculer_score.
Indice
Le constructeur attend une valeur de retour, pas uniquement une modification de l’instance.
Comprendre la correction
def calculer_score(self):
return ((20 - self.m100) * 10
+ self.longueur * 20
+ self.poids * 10
+ (500 - self.m1500))La méthode renvoie un résultat pour l’affectation self.score du constructeur. Elle lit les performances déjà initialisées ; elle ne doit pas lire self.score avant sa création. Si les performances changent plus tard, il faudra recalculer ce score stocké pour que le classement reste cohérent.
Propriété imposée : tous les scores du sous-arbre gauche sont strictement plus petits ; ceux du droit sont supérieurs ou égaux.
| Ordre | Nom | Score |
|---|---|---|
| 1 | Martin | 354 |
| 2 | Lena | 351 |
| 3 | Rayan | 350 |
| 4 | Yanis | 355 |
| 5 | Ninon | 351 |
| 6 | Ana | 356 |
Question 9
#Dessiner l’ABR après insertion successive de Martin 354, Lena 351, Rayan 350, Yanis 355, Ninon 351, Ana 356.
Indice
L’égalité 351 doit être placée dans la branche droite de Lena.
Comprendre la correction
Martin 354
├─ gauche : Lena 351
│ ├─ gauche : Rayan 350
│ └─ droite : Ninon 351
└─ droite : Yanis 355
└─ droite : Ana 356Les scores strictement plus petits vont à gauche ; les scores égaux ou plus grands vont à droite. Ninon 351 passe donc à gauche de Martin puis à droite de Lena. Les noms ne départagent pas l’égalité : seule la règle sur le score décide.
class Noeud:
def __init__(self, athlete):
self.valeur = athlete
self.gauche = None
self.droite = None
def inserer(self, athlete):
if athlete.score < self.valeur.score:
if self.gauche == ...:
self.gauche = Noeud(athlete)
else:
...
else:
if self.droite == ...:
self.droite = Noeud(athlete)
else:
...Question 10
#Compléter Noeud.inserer.
Indice
Il faut créer au premier emplacement vide, mais descendre si l’emplacement est déjà occupé.
Comprendre la correction
def inserer(self, athlete):
if athlete.score < self.valeur.score:
if self.gauche is None:
self.gauche = Noeud(athlete)
else:
self.gauche.inserer(athlete)
else:
if self.droite is None:
self.droite = Noeud(athlete)
else:
self.droite.inserer(athlete)Le choix de branche est effectué à chaque nœud rencontré. Dès qu’un enfant manque, le nouvel athlète est enveloppé dans un Noeud et rattaché à l’arbre. Sinon, l’appel récursif descend dans le sous-arbre choisi. La branche else inclut l’égalité de scores et applique donc la convention du sujet.
def classer(self, classement=None):
if classement is None:
classement = []
if self.droite is not None:
self.droite.classer(classement)
classement.append(self.valeur.nom)
if self.gauche is not None:
self.gauche.classer(classement)
return classementQuestion 11
#Expliquer le parcours de classer et pourquoi il donne un classement décroissant.
Indice
Le parcours commence par les grands scores et place la racine entre ses sous-arbres.
Comprendre la correction
C’est un parcours en profondeur infixe inversé : droite, nœud, gauche. Les scores les plus grands du sous-arbre droit sont ajoutés avant le score courant, puis les plus petits du gauche. Pour l’arbre précédent : Ana, Yanis, Martin, Ninon, Lena, Rayan, scores 356,355,354,351,351,350. Ninon précède Lena à score égal du fait de sa position à droite ; ce parcours n’assure donc pas la stabilité chronologique des ex æquo.
La liste classement est créée seulement lors de l’appel initial où le paramètre vaut None, puis partagée par les sous-appels. Ils y ajoutent leurs noms sans recréer de liste vide. Chaque nœud est visité une seule fois.
Question 12
#Donner un avantage et un inconvénient de chaque approche : dictionnaires avec tri, puis objets avec ABR.
Indice
Distinguez le coût d’insertion, le coût de parcours et le risque d’arbre déséquilibré.
Comprendre la correction
| Approche | Avantage | Inconvénient |
|---|---|---|
| Liste de dictionnaires + sélection | Représentation simple, directement parcourable et facile à afficher | Reclasser par cette sélection coûte Θ(n²), même si la liste est presque triée |
| Objets + ABR | Insertion localisée et parcours de classement en O(n), avec organisation des données dans des objets | L’arbre non équilibré peut dégénérer : insertion O(n), construction O(n²), mémoire de liens supplémentaire |
Si l’ABR reste équilibré, chaque insertion coûte O(log n), mais cette propriété n’est pas garantie par le code. La comparaison dépend aussi du tri choisi : un tri fusion sur liste peut donner O(n log n) sans arbre. Il faut comparer les algorithmes précis du sujet plutôt que déclarer les objets intrinsèquement plus rapides.
L’ordre d’insertion change-t-il le classement ou le coût ?Un atelier pour expérimenter
Insérez les six athlètes dans différents ordres. Le modèle reconstruit l’ABR, mesure sa hauteur et affiche le classement droite-nœud-gauche.
Lire le résultat de l’expérience initiale
Hauteur de l’ABR : 3
Les scores restent décroissants au parcours, mais la forme de l’arbre et le coût des insertions changent. Les égalités suivent toujours la branche droite.
| Rang | Athlète | Score |
|---|---|---|
| 1 | Ana | 356 |
| 2 | Yanis | 355 |
| 3 | Martin | 354 |
| 4 | Ninon | 351 |
| 5 | Lena | 351 |
| 6 | Rayan | 350 |
Un ABR ordonne les valeurs, mais ne s’équilibre pas spontanément. La performance dépend de sa hauteur.
Exercice 2 · 6 points
Carré de Polybe : générer une grille et convertir les coordonnées
Le chiffrement de Polybe du sujet remplace chaque lettre majuscule ou chiffre par son couple (ligne,colonne) dans une grille 6 × 6. Les coordonnées commencent à 1. La grille d’exemple est :
| Ligne | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | Q | 7 | A | X | 2 | J |
| 2 | 9 | E | H | 0 | R | M |
| 3 | L | Z | 4 | W | D | O |
| 4 | 6 | V | N | B | 8 | K |
| 5 | P | Y | 1 | S | T | F |
| 6 | G | C | 3 | I | U | 5 |
N est en (4,3). Le message NSI devient (4,3),(5,4),(6,4). Les 26 lettres et 10 chiffres occupent une case chacun.
Question 1
#Déchiffrer (6,2),(3,6),(3,5),(2,2) avec cette grille.
Indice
Le premier entier est la ligne, le second la colonne.
Comprendre la correction
CODE : C est ligne 6 colonne 2, O ligne 3 colonne 6, D ligne 3 colonne 5, E ligne 2 colonne 2. Inverser ligne et colonne donnerait un autre message.
On construit une grille en écrivant d’abord la clé, composée ici de caractères tous distincts, puis les lettres A à Z absentes, puis les chiffres 0 à 9 absents. Bien que le texte dise « lettres », les exemples de clé incluent aussi des chiffres. Pour 2048ALGORITHMES, l’ordre est 2048ALGORITHMESBCDFJKNPQUVWXYZ135679 :
| Ligne | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | 2 | 0 | 4 | 8 | A | L |
| 2 | G | O | R | I | T | H |
| 3 | M | E | S | B | C | D |
| 4 | F | J | K | N | P | Q |
| 5 | U | V | W | X | Y | Z |
| 6 | 1 | 3 | 5 | 6 | 7 | 9 |
Question 2
#Chiffrer BAC avec la clé SECURITY1024.
Indice
Complétez l’alphabet en sautant les caractères déjà placés par la clé.
Comprendre la correction
L’ordre d’insertion est SECURITY1024ABDFGHJKLMNOPQVWXZ356789. La grille commence par SECURITY1024 puis les lettres et chiffres encore absents. B est en (3,2), A en (3,1) et C en (1,3), donc [(3,2),(3,1),(1,3)].
| Ligne | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | S | E | C | U | R | I |
| 2 | T | Y | 1 | 0 | 2 | 4 |
| 3 | A | B | D | F | G | H |
| 4 | J | K | L | M | N | O |
| 5 | P | Q | V | W | X | Z |
| 6 | 3 | 5 | 6 | 7 | 8 | 9 |
Question 3
#Pourquoi le chiffrement est-il symétrique ?
Indice
Les deux participants doivent partager le même secret de construction de grille.
Comprendre la correction
La même clé permet à l’émetteur et au destinataire de reconstruire la même grille, utilisée pour chiffrer et déchiffrer. Elle doit rester secrète pour les tiers. Ce procédé de substitution reste un exemple pédagogique : il conserve les répétitions de caractères et ne fournit pas une sécurité adaptée aux échanges modernes.
def generer_ordre(cle):
ordre_insertion = cle
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
for lettre in alphabet:
if lettre not in ordre_insertion:
ordre_insertion += lettre
return ordre_insertionQuestion 4
#Que renvoie generer_ordre("AXU7") ?
Indice
Le 7 placé dans la clé ne réapparaît pas parmi les chiffres de fin.
Comprendre la correction
AXU7BCDEFGHIJKLMNOPQRSTVWYZ012345689AXU7 est conservé en tête. On parcourt ensuite A à Z puis 0 à 9, en ignorant A,X,U,7 déjà présents. Chaque caractère autorisé apparaît exactement une fois, soit 36 caractères.
Question 5
#Écrire grille_vide(n), n lignes de n chaînes vides.
Indice
Une grille modifiable demande des lignes indépendantes.
Comprendre la correction
def grille_vide(n):
return [["" for _ in range(n)] for _ in range(n)]La compréhension externe crée des lignes distinctes. [[""]*n]*n partagerait la même liste entre toutes les lignes et la modification d’une case aurait plusieurs effets. Pour n=3, on obtient [["","",""],["","",""],["","",""]].
def generer_grille(cle):
ordre_insertion = generer_ordre(cle)
grille = grille_vide(6)
indice = 0
for i in range(...):
for j in range(...):
grille[i][j] = ...
indice += 1
return grilleQuestion 6
#Compléter generer_grille(cle).
Indice
i choisit la ligne, j la colonne, indice parcourt la chaîne de manière continue.
Comprendre la correction
for i in range(6):
for j in range(6):
grille[i][j] = ordre_insertion[indice]
indice += 1Les deux boucles parcourent la grille ligne par ligne, comme la construction définie. indice prend les valeurs 0 à 35. La fonction suppose une clé valide, sans répétition ni caractère étranger à l’alphabet, condition nécessaire pour que generer_ordre produise bien 36 symboles uniques.
def dechiffrer(cle, message):
resultat = ""
grille = generer_grille(cle)
for t in message:
resultat = resultat + grille[...][...]
return resultatQuestion 7
#Compléter l’accès de dechiffrer pour des coordonnées numérotées à partir de 1.
Indice
Transformez séparément numéro de ligne et numéro de colonne en indices Python.
Comprendre la correction
resultat = resultat + grille[t[0] - 1][t[1] - 1]Les couples chiffrés vont de 1 à 6, mais les indices Python de 0 à 5. Il faut retirer 1 à chacune des deux composantes. Avec 2048ALGORITHMES, (4,4),(3,3),(2,4) donne NSI. Un simple -1 sur la ligne seulement laisserait la colonne décalée.
Avec 2048ALGORITHMES, le début du dictionnaire est {"2":(1,1),"0":(1,2),"4":(1,3),"8":(1,4),"A":(1,5),…}.
Question 8
#Écrire generer_dico(cle), associant chaque caractère à ses coordonnées à partir de 1.
Indice
La clé est le caractère et la valeur son couple de coordonnées.
Comprendre la correction
def generer_dico(cle):
grille = generer_grille(cle)
dico = {}
for i in range(6):
for j in range(6):
dico[grille[i][j]] = (i + 1, j + 1)
return dicoCette fois on passe des indices internes aux coordonnées externes : on ajoute 1. La grille ne contenant aucun doublon, chaque caractère a une unique position. Le dictionnaire permet ensuite une recherche directe par caractère, au lieu de parcourir jusqu’à 36 cases pour chaque lettre du message.
Question 9
#Écrire chiffrer(cle,message), qui renvoie une liste de tuples.
Indice
La fonction renvoie une liste de coordonnées, pas une chaîne de nombres concaténés.
Comprendre la correction
def chiffrer(cle, message):
dico = generer_dico(cle)
resultat = []
for caractere in message:
resultat.append(dico[caractere])
return resultatOn construit le dictionnaire une fois, puis on le réutilise pour chaque symbole. Le message est supposé composé de lettres majuscules et chiffres disponibles. Les espaces, accents et minuscules ne sont pas dans ce modèle ; il faudrait annoncer une règle de traitement plutôt que les supprimer silencieusement. Le message vide produit une liste vide.
Alice et Bob veulent changer chaque jour la clé de Polybe et envisagent un échange asymétrique.
Question 10
#Distinguer chiffrement symétrique et asymétrique pour l’échange quotidien d’une clé entre Alice et Bob.
Indice
La paire publique/privée évite de transmettre le secret de déchiffrement lui-même.
Comprendre la correction
Le chiffrement symétrique utilise le même secret partagé pour chiffrer et déchiffrer. Le chiffrement asymétrique utilise une paire de clés : une clé publique peut servir à chiffrer pour un destinataire, qui déchiffre avec sa clé privée. Ainsi Alice peut protéger la clé de Polybe avec la clé publique authentifiée de Bob ; lui seul possède la clé privée correspondante. L’identité associée à la clé publique doit être vérifiée pour éviter une substitution. Changer la clé de Polybe chaque jour ne transforme pas pour autant cette substitution simple en chiffrement moderne robuste.
Changer la clé déplace-t-il tout le message ?Un atelier pour expérimenter
Choisissez une clé valide et un message. La grille est reconstruite, le message chiffré puis relu par coordonnées. Repérez les caractères placés dès le début par la clé.
Lire le résultat de l’expérience initiale
BAC → (3,2) (3,1) (1,3)
Le premier nombre désigne la ligne et le second la colonne, de 1 à 6. Pour indexer un tableau Python, on retire 1 à chacun.
| Ligne | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 1 | S | E | C | U | R | I |
| 2 | T | Y | 1 | 0 | 2 | 4 |
| 3 | A | B | D | F | G | H |
| 4 | J | K | L | M | N | O |
| 5 | P | Q | V | W | X | Z |
| 6 | 3 | 5 | 6 | 7 | 8 | 9 |
Une convention de coordonnées doit être réversible : +1 au chiffrement, -1 au déchiffrement.
Revoir les notions de cet exercice
Exercice 3 · 8 points
Démineur : voisinage, propagation et scores en ligne
Le champ de mines est une grille. Une mine est codée -1 ; une case libre indique le nombre de mines parmi ses huit voisines au maximum. Cliquer une case numérotée la révèle ; cliquer une case à zéro révèle aussi ses voisines libres, puis propage tant que des zéros sont rencontrés. Une mine fait perdre ; révéler toutes les cases libres fait gagner. La figure 1 donne cette grille 6 × 7 et ses huit mines :
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|---|
| 0 | 1 | -1 | 2 | 1 | 2 | 1 | 1 |
| 1 | 1 | 2 | 3 | -1 | 2 | -1 | 1 |
| 2 | 1 | 2 | -1 | 2 | 2 | 1 | 1 |
| 3 | 2 | -1 | 2 | 2 | 1 | 1 | 0 |
| 4 | -1 | 2 | 1 | 1 | -1 | 2 | 1 |
| 5 | 1 | 1 | 0 | 1 | 1 | 2 | -1 |
(1,2) vaut 3 à cause des mines (0,1),(2,2),(1,3). (3,6) vaut 0 car aucune case voisine ne contient de mine. Les coordonnées sont (ligne,colonne) à partir de zéro.
class Demineur:
def __init__(self, hauteur, largeur, pourcentage_mines):
# 15,6 % se donne sous la forme 0.156
assert ..., "Le pourcentage de mines doit être compris entre 10% et 30%."
...hauteur = hauteur
...largeur = largeur
...pourcentage_mines = pourcentage_minesQuestion 1
#Compléter l’assertion imposant entre 10 % et 30 % de mines.
Indice
Le constructeur reçoit une fraction entre 0 et 1.
Comprendre la correction
assert 0.10 <= pourcentage_mines <= 0.30, "Le pourcentage de mines doit être compris entre 10% et 30%."Le paramètre est une proportion : 15,6 % est représenté par 0.156, pas 15.6. Les deux bornes sont incluses. Une grille de dimensions invalides demanderait une autre validation, distincte de cette assertion.
Question 2
#Compléter les trois affectations d’attributs du constructeur.
Indice
Les attributs doivent appartenir à l’objet.
Comprendre la correction
self.hauteur = hauteur
self.largeur = largeur
self.pourcentage_mines = pourcentage_minesself désigne l’instance créée. Les paramètres locaux sont conservés dans des attributs pour être réutilisés par les méthodes de génération et de voisinage.
| Niveau | Grille | Mines | Proportion indicative |
|---|---|---|---|
| Débutant | 8×8 | 10 | 15,6 % |
| Intermédiaire | 16×16 | 40 | 15,6 % |
| Expert | 30×16 | 99 | 20,6 % |
Question 3
#Créer demineur_intermediaire selon les recommandations.
Indice
Le constructeur attend hauteur, largeur, puis proportion.
Comprendre la correction
demineur_intermediaire = Demineur(16, 16, 0.156)La recommandation est une grille de 256 cases pour environ 40 mines, soit 15,6 %. Le code de placement donné atteint 40, car il incrémente jusqu’à atteindre ou dépasser 256×0.156 = 39.936. On peut aussi donner exactement 40/256 si l’on souhaite représenter le ratio exact.
Exemple 3 × 5 : [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]].
def grille_demineur_vide(self):
return [[0 for _ in range(...)] for _ in range(...)]Question 4
#Compléter grille_demineur_vide.
Indice
L’ordre largeur à l’intérieur, hauteur à l’extérieur correspond à liste de lignes.
Comprendre la correction
def grille_demineur_vide(self):
return [[0 for _ in range(self.largeur)]
for _ in range(self.hauteur)]La boucle interne construit une ligne de largeur cases ; l’externe répète cette création hauteur fois. Les lignes sont indépendantes. Le constructeur peut ensuite affecter self.grille_demineur = self.grille_demineur_vide().
randint(a,b) renvoie un entier aléatoire entre a et b inclus.
def placer_mines(self):
compteur_mines = 0
nombre_bombes = self.largeur * self.hauteur * self.pourcentage_mines
while compteur_mines < nombre_bombes:
ligne = randint(0, self.hauteur - 1)
colonne = randint(0, ...)
if self.grille_demineur[...][...] == 0:
self.grille_demineur[...][...] = -1
compteur_mines = ...Question 5
#Compléter placer_mines aux lignes 14 à 17.
Indice
Une mine n’est comptée qu’après placement sur une case libre.
Comprendre la correction
colonne = randint(0, self.largeur - 1)
if self.grille_demineur[ligne][colonne] == 0:
self.grille_demineur[ligne][colonne] = -1
compteur_mines += 1Les bornes de randint sont incluses, donc le dernier indice est largeur-1. Une case déjà minée ne doit pas incrémenter le compteur : cela compterait deux fois la même mine. On suppose une grille encore vide et que la méthode est appelée une seule fois avant le calcul des nombres.
La quantité nombre_bombes du squelette est un flottant. La condition compteur_mines < nombre_bombes provoque un arrondi à l’entier supérieur si le produit n’est pas entier. Pour une politique de nombre de mines explicite, on préférera calculer un entier avec round ou une autre convention annoncée avant la boucle. Cette nuance devient visible dans l’exemple de la question suivante.
voisines((ligne,colonne)) renvoie une liste de tuples valides. Pour une grille 3×5 : voisines((0,0)) donne [(1,1),(1,0),(0,1)] ; voisines((1,2)) donne [(0,1),(0,3),(0,2),(2,1),(2,3),(2,2),(1,1),(1,3)].
Question 6
#Écrire nombre_voisines_avec_mines, en utilisant voisines déjà disponible.
PythonCompter les mines autour d’une case, bords comprisÉcrivez votre solution et mettez-la à l’épreuve
Complétez nombre_voisines_avec_mines dans la classe Demineur. La classe de support fournit les attributs hauteur, largeur, grille_demineur et la méthode voisines(coordonnees_case), qui exclut la case centrale et les positions hors grille. Une mine vaut -1 ; les autres nombres ne sont pas des mines. Le générateur aléatoire et l’interface graphique sont volontairement absents : les tests installent des grilles déterministes pour vérifier cette méthode seule.
class Demineur(DemineurSupport):
def nombre_voisines_avec_mines(self, coordonnees_case):
# Utilisez self.voisines
passLes cas de test proposés :
- Un coin avec une mine en diagonale : Le voisinage contient les diagonales, même dans un coin.
- Huit mines autour de la case centrale : Une case intérieure a huit voisines, pas seulement quatre.
- Les nombres affichés ne sont pas des mines : Compter des cases non nulles ou additionner leurs valeurs donnerait un faux résultat.
- Une grille rectangulaire : Les limites de ligne et de colonne ne sont pas interchangeables.
- La case centrale ne se compte pas et la grille ne change pas : La méthode de voisinage exclut déjà le centre. La méthode demandée lit la grille sans la recalculer.
Indice
Les informations numériques et les mines se distinguent par la valeur -1.
Comprendre la correction
def nombre_voisines_avec_mines(self, coordonnees_case):
nombre = 0
for ligne, colonne in self.voisines(coordonnees_case):
if self.grille_demineur[ligne][colonne] == -1:
nombre += 1
return nombreLa méthode voisines fournit seulement les coordonnées valides du voisinage, en excluant la case centrale. On compte les -1 et non les valeurs positives déjà calculées. La somme des nombres des cases voisines ne donnerait pas le nombre de mines, et pourrait compter plusieurs fois une même mine.
Exemple de grille 6×6 imprimée avec dix positions de mines, reproduit comme donnée source :
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | 2 | -1 | 2 | 1 | 2 | 1 |
| 1 | 2 | -1 | 4 | -1 | 2 | -1 |
| 2 | 2 | 3 | -1 | 2 | 2 | 1 |
| 3 | 2 | -1 | 2 | 2 | 1 | 1 |
| 4 | -1 | 3 | 2 | 2 | -1 | 2 |
| 5 | 1 | 2 | -1 | 2 | 2 | -1 |
Il est présenté après demineur_nsi = demineur(6,6,0.278), puis generer_demineur().
Question 7
#Écrire generer_demineur pour donner à chaque case non minée son nombre de mines voisines.
Indice
Ne remplacez jamais une mine par son nombre de voisines.
Comprendre la correction
def generer_demineur(self):
self.placer_mines()
for ligne in range(self.hauteur):
for colonne in range(self.largeur):
if self.grille_demineur[ligne][colonne] != -1:
self.grille_demineur[ligne][colonne] = (
self.nombre_voisines_avec_mines((ligne, colonne)))L’appel place les mines sur la grille initialement vide, puis toutes les cases libres sont annotées. Si les mines ont déjà été placées par le constructeur, cet appel initial doit être omis : il ne faut pas placer les mines deux fois. Les -1 restent intacts, donc le calcul des cases suivantes ne dépend pas des chiffres déjà écrits.
L’exemple imprimé utilise demineur(6,6,0.278) mais la classe s’appelle Demineur. Il montre dix mines, alors que le placement fourni ferait ceil(36×0.278)=11. Pour obtenir dix mines, utiliser le ratio exact 10/36 ou une règle d’arrondi explicite. De plus, certains nombres de voisinage affichés dans les figures sont incohérents ; un programme doit les recalculer à partir des mines, pas recopier ces chiffres comme attendus.
Partie C : interface du jeu
La grille de visibilité commence entièrement à False ; True signifie une case révélée. Une mine révèle toutes les cases. Les figures 3/4 proposent cette grille de données, un écran initial entièrement caché, puis un clic (0,3) :
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | -1 | 1 | 0 | 0 | 0 | 0 |
| 1 | 2 | 2 | 1 | 0 | 1 | 1 |
| 2 | 1 | -1 | 1 | 1 | -1 | 1 |
| 3 | 1 | 1 | 1 | 1 | 1 | 1 |
| 4 | 1 | -1 | 1 | 0 | 0 | 0 |
| 5 | 1 | 1 | 1 | 0 | 0 | 0 |
Figure 4, visibilité imprimée après clic (à distinguer de la propagation corrigée expliquée ci-dessous) :
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | false | true | true | true | true | true |
| 1 | false | true | true | true | true | true |
| 2 | false | false | true | true | false | false |
| 3 | false | false | false | false | false | false |
| 4 | false | false | false | false | false | false |
| 5 | false | false | false | false | false | false |
Le constructeur général initialise self.grille_visibilite = [[False for _ in range(self.largeur)] for _ in range(self.hauteur)].
Question 8
#Écrire visibilite(coordonnees_case) pour mettre à jour la grille de visibilité après un clic, éventuellement récursivement.
Indice
La visibilité sert aussi de marque de visite pour la propagation.
Comprendre la correction
def visibilite(self, coordonnees_case):
ligne, colonne = coordonnees_case
if self.grille_visibilite[ligne][colonne]:
return
if self.grille_demineur[ligne][colonne] == -1:
for i in range(self.hauteur):
for j in range(self.largeur):
self.grille_visibilite[i][j] = True
return
self.grille_visibilite[ligne][colonne] = True
if self.grille_demineur[ligne][colonne] == 0:
for voisine in self.voisines(coordonnees_case):
self.visibilite(voisine)On marque une case avant de visiter ses voisines, sinon les appels pourraient revenir indéfiniment sur le même zéro. Une case numérotée est révélée mais ne propage pas. Un zéro révèle sa composante de zéros et sa frontière de cases numérotées. Une mine cliquée révèle toute la grille comme demandé. Depuis un zéro correctement calculé, aucune voisine n’est une mine, donc la propagation ne déclenche pas de défaite.
Les figures 3 et 4 imprimées ne sont pas cohérentes avec leurs mines : en (1,3), la mine (2,4) impose le nombre 1, et non 0. Les cases (3,0),(3,1),(3,2) devraient valoir 2, non 1. Avec les mines réellement dessinées, cliquer (0,3) révèle les colonnes 1 à 5 des lignes 0 et 1, soit dix cases. La correction et l’atelier utilisent ces nombres recalculés ; ils ne prolongent pas la propagation à partir du faux zéro imprimé.
Partie D : scores en ligne
Le schéma pédagogique stocke les comptes et les meilleurs scores par niveau :
| id_joueur | pseudo | mot_de_passe (données fictives du sujet) |
|---|---|---|
| 1 | Grimdal | EgxGB6a3bRinllon |
| 2 | Raptor | J17NtfMS3Dudjjln |
| 3 | PetiteFée | UuukBSvj01VrGoGD |
| 4 | Kirna | 7NcDFPNl0Xy1MEPb |
| joueur | niveau | score | temps |
|---|---|---|---|
| 1 | facile | 1200 | 72 |
| 2 | expert | 1196 | 366 |
| 3 | intermédiaire | 997 | 230 |
| 2 | intermédiaire | 997 | 200 |
| 4 | expert | 1097 | 400 |
| Demineur.niveau | dimension | pourcentage mines |
|---|---|---|
| facile | 8x8 | 15.6 |
| intermédiaire | 16x16 | 15.6 |
| expert | 30x16 | 20.6 |
Question 9
#Donner les clés étrangères de Meilleur_score et leurs clés primaires cibles.
Indice
Les tables de définition des joueurs et des niveaux sont les tables cibles.
Comprendre la correction
Meilleur_score.joueur référence Joueur.id_joueur ; Meilleur_score.niveau référence Demineur.niveau. Un meilleur score doit être associé à un compte et à un niveau existants.
Question 10
#Donner une requête renvoyant les lignes (expert,1196) et (intermédiaire,997).
SQLExtraire les résultats de RaptorÉcrivez votre solution et mettez-la à l’épreuve
Le tableau demandé par la question regroupe les niveaux et scores du joueur 2, Raptor. Écrivez une requête donnant ses colonnes niveau et score, quel que soit le niveau auquel il a joué.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Scores officiels du démineur : Les comptes et scores fictifs sont ceux de l’énoncé. Le score 997 existe aussi pour une autre personne : filtrer seulement les nombres ne sélectionne pas correctement le joueur.
- Cas complémentaire : les scores évoluent : Jeu complémentaire pédagogique, distinct des données officielles. Les résultats ne sont plus ceux de l’extrait. Le critère reste le joueur 2, pas une liste de scores recopiée.
Indice
Cherchez le joueur commun aux deux scores affichés.
Comprendre la correction
SELECT niveau, score
FROM Meilleur_score
WHERE joueur = 2;Ces deux lignes sont les meilleurs scores du joueur 2, Raptor. La requête ne fixe pas d’ordre ; si l’affichage doit reproduire exactement expert avant intermédiaire, on peut ajouter ORDER BY niveau. D’autres requêtes accidentellement équivalentes sur cet extrait ne décriraient pas aussi clairement le même besoin.
Question 11
#Kirna remplace son mot de passe par cGhhxDE4. Écrire la mise à jour du schéma fourni.
Indice
Ciblez le compte par sa clé primaire.
Comprendre la correction
UPDATE Joueur
SET mot_de_passe = 'cGhhxDE4'
WHERE id_joueur = 4;L’identifiant 4 cible Kirna sans modifier les autres comptes. Cette écriture répond au schéma simplifié du sujet ; un service réel ne stocke pas un mot de passe en clair mais une empreinte de mot de passe salée calculée par un mécanisme adapté.
SELECT pseudo FROM Joueur
JOIN Meilleur_score ON Joueur.id_joueur = Meilleur_score.joueur
WHERE niveau = 'expert' AND score > 1000 AND temps < 400;Question 12
#Donner le résultat de la requête sur les experts avec score >1000 et temps <400.
Indice
L’égalité à 400 n’est pas acceptée par temps < 400.
Comprendre la correction
| pseudo |
|---|
| Raptor |
Raptor a un score expert 1196 et un temps 366 : les trois conditions sont remplies. Kirna a bien plus de 1000 points mais son temps vaut exactement 400, exclu par le test strict. Les autres lignes ne sont pas expert.
Question 13
#Corriger le niveau facile pour qu’il soit désigné débutant dans la base.
Indice
Renommer une clé primaire exige de maintenir les références pendant l’opération.
Comprendre la correction
INSERT INTO Demineur (niveau, dimension, "pourcentage mines")
SELECT 'débutant', dimension, "pourcentage mines"
FROM Demineur WHERE niveau = 'facile';
UPDATE Meilleur_score
SET niveau = 'débutant'
WHERE niveau = 'facile';
DELETE FROM Demineur WHERE niveau = 'facile';Cette séquence préserve les références même avec des clés étrangères contrôlées immédiatement : créer d’abord le niveau cible, déplacer ensuite les scores, puis retirer l’ancien niveau. Deux UPDATE naïfs, parent puis enfants ou inversement, peuvent violer la contrainte intermédiaire si la mise à jour n’est pas en cascade ou différée. Les trois commandes doivent être exécutées ensemble dans une transaction.
Le tableau du sujet écrit « pourcentage mines » avec un espace. Les guillemets doubles ci-dessus citent cet identifiant SQL ; si le schéma implémenté utilise pourcentage_mines, il faut employer ce nom réel. La séquence suppose que débutant n’existe pas déjà. Avec une clé étrangère ON UPDATE CASCADE explicitement configurée, une mise à jour du niveau parent aurait aussi suffi, mais cette configuration n’est pas annoncée.
Révéler une zone de démineur sans traverser une mineUn atelier pour expérimenter
Choisissez une case de la grille 6×6 des figures 3 et 4. Les nombres sont recalculés à partir des quatre mines du dessin. Un zéro propage, un nombre s’arrête, une mine révèle tout.
Lire le résultat de l’expérience initiale
10 case(s) révélée(s)
La propagation utilise un ensemble de cases déjà visitées. Les chiffres de la grille source comportaient des erreurs ; ici ils sont recalculés pour respecter exactement les mines (0,0), (2,1), (2,4), (4,1).
| Ligne | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | Cachée | 1 | 0 | 0 | 0 | 0 |
| 1 | Cachée | 2 | 1 | 1 | 1 | 1 |
| 2 | Cachée | Cachée | Cachée | Cachée | Cachée | Cachée |
| 3 | Cachée | Cachée | Cachée | Cachée | Cachée | Cachée |
| 4 | Cachée | Cachée | Cachée | Cachée | Cachée | Cachée |
| 5 | Cachée | Cachée | Cachée | Cachée | Cachée | Cachée |
La propagation doit reposer sur des voisins et des nombres exacts. Le marquage avant exploration empêche les retours infinis.
Revoir les notions de cet exercice
- Programmation objet : classes, objets, attributs et méthodes
- La récursivité : comprendre les appels et les résultats
- Parcourir un graphe en profondeur : DFS
- Clés primaires, clés étrangères et contraintes d’intégrité
- SQL : comprendre et écrire des jointures
- SQL : insérer, modifier et supprimer des données
Du sujet à la méthode
Votre prochaine séance de révision
- Annoncez le sens de tri et la règle des ex æquo avant de construire l’ABR.
- Pour toute conversion de coordonnées, vérifiez une case du bord pour repérer un décalage de 1.
- Au démineur, recalculer les nombres depuis les mines constitue une vérification indépendante des figures imprimées.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26-NSIJ1G11 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
