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
Tournoi de volley : suivre les relations entre joueurs, équipes et matchs
Bois-Plage organise en juillet un tournoi de volley par équipes de quatre. Les inscriptions sont stockées dans trois tables. Chaque clé primaire est un identifiant entier ; les clés étrangères relient les lignes indiquées dans le schéma ci-dessous.
| Table | Attributs et types | Clés étrangères |
|---|---|---|
| joueur | id_joueur INT (primaire), nom TEXT, prenom TEXT,ann_naiss INT, commune TEXT,num_port TEXT | Aucune |
| equipe | id_equipe INT (primaire), nom TEXT,j_1 INT,j_2 INT,j_3 INT,j_4 INT, points INT | j_1,j_2,j_3,j_4 référencent joueur.id_joueur |
| match | id_match INT (primaire),eq_1 INT,eq_2 INT,eq_gagnante INT, score TEXT | eq_1,eq_2,eq_gagnante référencent equipe.id_equipe |
Les numéros de téléphone sont textuels pour conserver leur zéro initial. Les extraits de clôture des inscriptions sont :
id_joueur | nom | prenom | ann_naiss | commune | num_port |
|---|---|---|---|---|---|
| 25 | Leclerc | Océane | 2008 | Bois-Plage | 0660358945 |
| 26 | Renault | Henri | 1971 | Guilland | 0625597427 |
| 27 | Desousa | Laure | 1980 | Bois-Plage | 0746881113 |
| 28 | Hernand | Yves | 1986 | Lebrundan | 0739401689 |
| 29 | Giraud | Brigitte | 1972 | Saint-Adrien | 0651936319 |
| 30 | Barbier | Laure | 1979 | Bois-Plage | 0787028125 |
id_equipe | nom | j_1 | j_2 | j_3 | j_4 | points |
|---|---|---|---|---|---|---|
| 8 | Les Mr Freeze | 7 | 12 | 5 | 33 | 0 |
| 9 | Tagadas Winners | 45 | 23 | 67 | 65 | 0 |
| 10 | Volley Warriors | 25 | 27 | 30 | 35 | 0 |
| 11 | Les Piafs | 37 | 32 | 41 | 28 | 0 |
Question 1
#Expliquer le rôle des clés primaires dans ces relations.
Indice
Distinguez l’identité d’une ligne de ses informations descriptives.
Comprendre la correction
Une clé primaire identifie sans ambiguïté chaque ligne d’une table. Sa valeur est unique et non nulle. id_joueur distingue les personnes, id_equipe les équipes et id_match les rencontres. Les noms peuvent être partagés ou modifiés ; l’identifiant permet aux autres tables de référencer une ligne précise.
Question 2
#Quelles données la table match ne pourrait-elle pas distinguer sans id_match ?
Indice
Deux équipes peuvent-elles se rencontrer plus d’une fois ?
Comprendre la correction
L’identifiant permet notamment d’enregistrer plusieurs rencontres entre les mêmes équipes, éventuellement avec le même gagnant et le même score. Utiliser seulement le couple(eq_1,eq_2) comme clé interdirait une revanche entre les mêmes équipes dans les mêmes rôles. Sans nouvel identifiant ni autre attribut discriminant, des rencontres distinctes mais identiques sur tous les champs restants seraient indiscernables.
Question 3
#Donner le résultat de SELECT prenom FROM joueur WHERE ann_naiss<1985 sur l’extrait.
Indice
Le test porte sur l’année de naissance, strictement inférieure à 1985.
Comprendre la correction
| prenom |
|---|
| Henri |
| Laure |
| Brigitte |
| Laure |
Les identifiants 26,27,29 et 30 satisfont le filtre. Laure apparaît deux fois car deux personnes différentes portent ce prénom. Sans ORDER BY, l’ordre des lignes n’est pas garanti ; le tableau présente l’ordre de l’extrait pour faciliter la lecture.
Question 4
#Modifier la requête pour éliminer les doublons.
Indice
Dédupliquer un résultat est différent de supprimer des données.
Comprendre la correction
SELECT DISTINCT prenom FROM joueur WHERE ann_naiss < 1985;DISTINCT s’applique aux valeurs projetées, ici au prénom seul. Le résultat comporte Henri, Laure etBrigitte une fois chacun. Il ne supprime aucune personne de la table.
Question 5
#Obtenir les noms, années de naissance et numéros mobiles des personnes qui habitent Bois-Plage.
SQLSélectionner les joueurs de Bois-PlageÉcrivez votre solution et mettez-la à l’épreuve
Renvoyez le nom, l’année de naissance et le numéro mobile des personnes qui habitent Bois-Plage. Conservez les téléphones sous forme de texte.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Extrait officiel des inscriptions : Les six personnes viennent du tableau officiel. La commune filtre les lignes et les trois colonnes demandées constituent le résultat.
- Cas complémentaire : noms et prénoms distincts : Jeu complémentaire pédagogique, distinct des données officielles. Deux personnes ont le même prénom, mais une seule réside dans la commune. Le nom de famille reste la première colonne demandée.
Indice
Choisissez les colonnes utiles puis le critère de commune.
Comprendre la correction
SELECT nom, ann_naiss, num_port FROM joueur WHERE commune = 'Bois-Plage';La projection respecte les trois colonnes demandées. Le filtre textuel compare à la valeur complète de la commune, avec son trait d’union. Le numéro est conservé comme chaîne, pas converti en entier.
Question 6
#Obtenir nom et prénom du joueur j_1 de l’équipe Les Kangourous, absente de l’extrait.
Indice
La colonne j_1 contient l’identifiant du joueur recherché.
Comprendre la correction
SELECT j.nom, j.prenom
FROM joueur AS j JOIN equipe AS e ON j.id_joueur = e.j_1
WHERE e.nom = 'Les Kangourous';La jointure suit uniquement j_1. Joindre aussi les trois autres membres répondrait à une autre question. L’absence de cette équipe dans l’extrait n’empêche pas d’écrire la requête sur la base complète ; on ne peut simplement pas annoncer le nom du joueur à partir des lignes visibles.
Question 7
#Mettre à jour les 5 points gagnés par Volley Warriors.
Indice
Utilisez l’identifiant connu de l’équipe.
Comprendre la correction
UPDATE equipe SET points = 5 WHERE id_equipe = 10;L’identifiant 10 est celui de Volley Warriors dans l’extrait et cible une seule ligne. Une condition sur son nom serait aussi conforme si ce nom identifie bien cette équipe. Sans WHERE, toutes les équipes recevraient 5 points.
Question 8
#Écrire la suppression du joueur d’identifiant 35.
Indice
Consultez aussi les clés étrangères qui désignent 35.
Comprendre la correction
DELETE FROM joueur WHERE id_joueur = 35;La requête demandée est simple, mais le schéma impose une contrainte importante : Volley Warriors référence encore ce joueur dans j_4. Si les clés étrangères sont activées sans cascade adaptée, la suppression sera refusée. Il faut d’abord traiter cette appartenance selon la règle de gestion retenue(remplacement du joueur, annulation d’inscription ou conservation de l’historique). La requête seule ne doit pas être présentée comme forcément exécutable sur les données fournies.
À la fin du tournoi, voici un extrait de match :
id_match | eq_1 | eq_2 | eq_gagnante | score |
|---|---|---|---|---|
| 32 | 3 | 8 | 8 | 25-20 |
| 33 | 3 | 9 | 3 | 25-15 |
| 34 | 3 | 10 | 10 | 25-7 |
Question 9
#Obtenir les identifiants des matchs auxquels l’équipe 12 a participé.
Indice
Cherchez dans les deux colonnes de participants.
Comprendre la correction
SELECT id_match FROM match WHERE eq_1 = 12 OR eq_2 = 12;L’équipe peut occuper le premier ou le second rôle. Le OR retient les deux cas ; AND exigerait qu’elle soit les deux équipes du même match. L’équipe gagnante seule ne suffit pas, car les matchs perdus sont aussi des participations.
Question 10
#Obtenir les identifiants des matchs dont le joueur 1 de l’équipe 1 habite Bois-Plage.
Indice
Suivez les rôles dans l’ordre : match, équipe 1, joueur 1.
Comprendre la correction
SELECT m.id_match
FROM match AS m
JOIN equipe AS e ON e.id_equipe = m.eq_1
JOIN joueur AS j ON j.id_joueur = e.j_1
WHERE j.commune = 'Bois-Plage';La relation se suit en deux étapes : match.eq_1 désigne equipe.id_equipe, puis equipe.j_1 désigne joueur.id_joueur. Le filtre est posé sur la commune de cette personne. Ni l’équipe 2 ni les joueurs 2 à 4 ne doivent intervenir dans la sélection.
Question 11
#Obtenir, par ordre alphabétique, les noms et prénoms des joueurs ayant gagné au moins un match en tant que joueur 1 de l’équipe 1.
Indice
Le gagnant du match doit être la même équipe que celle déjà jointe.
Comprendre la correction
SELECT DISTINCT j.nom, j.prenom
FROM match AS m
JOIN equipe AS e ON e.id_equipe = m.eq_1
JOIN joueur AS j ON j.id_joueur = e.j_1
WHERE m.eq_gagnante = m.eq_1
ORDER BY j.nom, j.prenom;La jointure suit les mêmes rôles que précédemment, puis le filtre exige que l’équipe gagnante soit eq_1. DISTINCT évite qu’une même personne apparaisse plusieurs fois après plusieurs victoires. Le tri s’effectue d’abord sur le nom, puis sur le prénom. La requête ne sélectionne pas les vainqueurs qui jouaient dans l’équipe 2 : cette restriction est explicitement demandée.
Le rôle dans le match change la requêteUn atelier pour expérimenter
Explorez un petit jeu de test complémentaire au sujet. Chaque ligne décrit équipe 1, équipe 2 et gagnante. Choisissez une équipe et comparez participer à gagner dans le rôle équipe 1.
Lire le résultat de l’expérience initiale
4 matchs retenus.
Jeu de test pédagogique distinct de l’extrait officiel. Participer utilise OR ;gagner comme équipe1 ajoute une égalité entre gagnante et équipe1.
| Match | Équipe1 | Équipe2 | Gagnante |
|---|---|---|---|
| 1 | 12 | 3 | 12 |
| 2 | 3 | 12 | 3 |
| 3 | 10 | 12 | 12 |
| 4 | 12 | 10 | 10 |
Une jointure doit suivre les rôles précis de la question, pas seulement relier toutes les tables disponibles.
Exercice 2 · 6 points
Dictionnaire de mots : hachage, insertion ordonnée et dichotomie
On veut retrouver rapidement des mots composés de lettres majusculesA àZ. Un dictionnaire Python associe à une clé entière une liste de mots rangés alphabétiquement. La clé est la somme des codesASCII des lettres, reduite à son octet de poids faible. Des mots différents peuvent donc partager une clé : c’est la liste associée qui permet de les distinguer.
d = {
44: ['ABAISSEMENT','ADMINISTRATEUR', ..., 'VERSETS'],
74: ['ABAISSER','ABLATION', ..., 'TROU'],
243: ['ABANDON','ALLEGRETTO', ..., 'ZIP'],
36: ['ABANDONNANT','ABOLITIONNISTE', ..., 'VOULAIT'],
134: ['ABANDONNE','AGNOSTICISME', ..., 'VOIES'],
40: ['ABANDONNENT','ACCOUCHEUSE', ..., 'YACK']
}| Lettre | ASCII hexadécimal |
|---|---|
| A | 0x41 |
| B | 0x42 |
| C | 0x43 |
| D | 0x44 |
| E | 0x45 |
| F | 0x46 |
| G | 0x47 |
| H | 0x48 |
| I | 0x49 |
| J | 0x4A |
| K | 0x4B |
| L | 0x4C |
| M | 0x4D |
| N | 0x4E |
| O | 0x4F |
| P | 0x50 |
| Q | 0x51 |
| R | 0x52 |
| S | 0x53 |
| T | 0x54 |
| U | 0x55 |
| V | 0x56 |
| W | 0x57 |
| X | 0x58 |
| Y | 0x59 |
| Z | 0x5A |
Les points de suspension désignent des mots non reproduits dans l’extrait, pas des données à insérer. Le préfixe 0x indique l’hexadécimal. Exemple : NSI donne 0x4E+0x53+0x49=0xEA=234 ; ADMINISTRATEUR donne une somme 0x42C, dont on conserve 0x2C=44.
Question 1
#Calculer en hexadécimal la clé du mot EW.
Indice
Additionnez les codes des deux lettres, pas leurs positions dans l’alphabet.
Comprendre la correction
E vaut 0x45 et W vaut 0x57. Leur somme vaut 0x9C, soit 156 en décimal. Elle tient déjà sur un octet et n’est donc pas modifiée par la réduction modulo 256.
Question 2
#Comparer sans calculer les clés de SAC et CAS. Justifier.
Indice
La fonction choisie tient-elle compte de l’ordre des lettres ?
Comprendre la correction
Elles sont identiques. Les deux mots contiennent les mêmes lettres et la somme est commutative : changer leur ordre ne change ni la somme ni son reste modulo 256. C’est une collision de hachage ; elle ne signifie pas que les mots sont égaux.
def code_hachage(mot):
somme = ...
for caractere in ...:
somme = ...
return somme % 0x100Question 3
#Compléter code_hachage aux lignes 2,3 et 4.
Indice
Additionner les codesASCII dans un accumulateur entier.
Comprendre la correction
def code_hachage(mot):
somme = 0
for caractere in mot:
somme += ord(caractere)
return somme % 0x100L’accumulateur commence à 0, la boucle parcourt les caractères du mot et ajoute leur code renvoyé par ord. Le retour réduit seulement la somme finale. On pourrait aussi réduire l’accumulateur à chaque étape : les deux méthodes donnent le même reste final.
Question 4
#Expliquer somme%0x100 et donner les valeurs possibles de la clé.
Indice
Un octet possède 2⁸ valeurs possibles.
Comprendre la correction
0x100 vaut 256. Le modulo renvoie le reste de la division entière par 256, donc un entier entre 0 et 255 inclus. Cela conserve l’octet de poids faible de la somme. Il existe seulement 256 clés : si l’on stocke davantage de mots, des collisions sont inévitables. Le mot vide, si on l’autorisait, aurait pour clé 0.
PartieB : les listes sont triées dans l’ordre alphabétique des majuscules. La fonction fournie modifie la liste en place et la renvoie :
def ajouter_mot_liste(liste, mot):
i = 0
while i < len(liste):
if mot < liste[i]:
liste.insert(i, mot)
return liste
i += 1
liste.append(mot)
return listeajouter_mot_liste([], 'NSI') # ['NSI']
ajouter_mot_liste(['NSI'], 'PYTHON') # ['NSI','PYTHON']
ajouter_mot_liste(['NSI','PYTHON'], 'OBJET') # ['NSI','OBJET','PYTHON']
ajouter_mot_liste(['NSI','OBJET','PYTHON'], 'RAM') # ['NSI','OBJET','PYTHON','RAM']Question 5
#Dans le pire cas, quel est l’ordre de grandeur du nombre de comparaisons de chaînes de ajouter_mot_liste, pour une liste de n mots ?
Indice
Placez le nouveau mot après tous les mots déjà présents.
Comprendre la correction
L’ordre est linéaire :Θ(n). Si le mot doit être placé à la fin, le test mot<liste[i] est effectué pour chacun des n mots avant append. Il peut être effectué une seule fois si le nouveau mot précède le premier. On compte ici les comparaisons entre chaînes, comme demandé ; le coût interne d’une comparaison dépend aussi de la longueur de leur préfixe commun.
Question 6
#Quelle expression teste si la clé c est présente dans dico ?
Indice
L’appartenance à un dictionnaire ne parcourt pas ses valeurs.
Comprendre la correction
c in dicoPour un dictionnaire, l’opérateur in porte sur les clés. Il ne recherche pas c dans les listes de mots associées.
Question 7
#Écrire ajouter_mot_dict(dict_mots, mot), sans valeur de retour, en utilisant code_hachage et ajouter_mot_liste.
Indice
Traiter une clé absente avant d’insérer dans sa liste.
Comprendre la correction
def ajouter_mot_dict(dict_mots, mot):
cle = code_hachage(mot)
if cle not in dict_mots:
dict_mots[cle] = []
ajouter_mot_liste(dict_mots[cle], mot)La clé détermine le groupe. Si ce groupe n’existe pas, on crée sa liste ; sinon, on conserve ses mots. L’insertion ordonnée maintient ensuite la précondition nécessaire à la dichotomie. Ne pas remplacer systématiquement la valeur par[mot], car cela supprimerait tous les mots entrés auparavant avec la même clé. Le contrat fourni autorise les doublons de mots ; une politique d’unicité demanderait un test supplémentaire.
PartieC : le contrat dit que debut est inclus et fin exclu. Le code et l’exemple fournis sont :
def est_present(liste, mot, debut, fin):
if debut > fin:
return False
milieu = (debut + fin) // 2
if liste[milieu] > mot:
return est_present(liste, mot, 0, milieu - 1)
elif liste[milieu] < mot:
return est_present(liste, mot, milieu + 1, fin)
else:
return Trueliste_mots = ['FONCTION','NSI','PYTHON','OBJET','RAM']
est_present(liste_mots,'NSI',0,len(liste_mots)) # TrueQuestion 8
#Pour l’appel fourni recherchant NSI, donner debut et fin à chaque appel récursif.
Indice
Exécutez les paramètres réellement écrits, même lorsqu’ils violent le contrat annoncé.
Comprendre la correction
| Appel du code fourni | debut | fin | milieu | Valeur lue |
|---|---|---|---|---|
| 1 | 0 | 5 | 2 | PYTHON |
| 2 | 0 | 1 | 0 | FONCTION |
| 3 | 1 | 1 | 1 | NSI |
Le code compare d’abord PYTHON àNSI et appelle(0,1). FONCTION est ensuite inférieur àNSI, donc l’appel suivant est(1,1), où il lit NSI et renvoieTrue. C’est bien la trace du programme fourni, même si elle révèle une incohérence : avec fin exclusive, l’intervalle[1,1[ devrait être vide.
Autre défaut de l’exemple : OBJET est placé après PYTHON, donc la liste annoncée n’est pas triée. Cet appel réussit malgré ces erreurs, il ne les valide pas. Il faut conserver cette trace pour répondre à la question, puis corriger les conventions avant de réutiliser la fonction.
Question 9
#Pourquoi la recherche dichotomique demande-t-elle généralement moins d’opérations qu’une recherche mot par mot ?
Indice
La propriété de tri permet d’écarter plusieurs candidats après une seule comparaison.
Comprendre la correction
Dans une liste triée, la comparaison au milieu permet d’éliminer environ la moitié de l’intervalle : si le mot cherché est plus petit, tous les mots à droite du milieu sont trop grands ; s’il est plus grand, tous ceux de gauche sont trop petits. Une recherche séquentielle peut devoir examiner chaque mot. Sans tri, l’élimination d’une moitié n’est pas justifiée et la dichotomie peut manquer un mot présent.
Question 10
#Quel est l’ordre de grandeur du nombre de comparaisons en dichotomie sur n mots ?
Indice
Cherchez combien de divisions par 2 réduisent n à 1.
Comprendre la correction
L’ordre est logarithmique : O(log₂ n), et Θ(log n) dans le pire cas pour une recherche binaire standard. Après k divisions par 2, il reste environ n/2ᵏ candidats ; on atteint une taille constante lorsque k est de l’ordre de log₂n. Une entrée vide se traite sans comparaison de mots.
Question 11
#Écrire mot_present(dict_mots, mot) en utilisant code_hachage et est_present.
PythonUne collision de hachage n’est pas un mot trouvéÉcrivez votre solution et mettez-la à l’épreuve
Écrivez mot_present(dict_mots, mot). Le dictionnaire associe chaque code de hachage à une liste de mots triée dans l’ordre lexicographique. Calculez la clé avec code_hachage, puis utilisez est_present sur l’intervalle [0, len(liste)[. Le helper de dichotomie fourni ici corrige les erreurs de bornes discutées dans le corrigé. La seule présence de la clé ne prouve pas que mot figure dans sa liste.
def mot_present(dict_mots, mot):
# À vous de jouer
passLes cas de test proposés :
- Un dictionnaire vide : Il faut tester la présence de la clé avant de lire la liste.
- Le seul mot de sa liste : Une borne finale exclusive égale à zéro oublierait ce seul candidat.
- Deux anagrammes partagent la clé : La somme des codes de lettres crée une collision pour les anagrammes, sans les rendre identiques.
- Chercher aux deux extrémités : Les deux extrémités de la liste triée appartiennent à l’intervalle de recherche.
- Une clé connue, une liste sans candidat : La dichotomie doit recevoir un intervalle vide sans être remplacée par un test sur la seule clé.
Indice
La clé choisit un groupe ; la dichotomie choisit un mot dans ce groupe.
Comprendre la correction
La recherche calcule d’abord la clé, puis ne visite que la liste correspondante. Une clé absente suffit à répondreFalse. Une clé présente ne prouve pas que le mot existe, à cause des collisions : il faut chercher dans sa liste triée.
def mot_present(dict_mots, mot):
cle = code_hachage(mot)
if cle not in dict_mots:
return False
liste = dict_mots[cle]
return est_present(liste, mot, 0, len(liste))Pour rendre cette fonction correcte sur tous les cas du contrat, voici la correction nécessaire de est_present :
def est_present(liste, mot, debut, fin):
if debut >= fin:
return False
milieu = (debut + fin) // 2
if liste[milieu] > mot:
return est_present(liste, mot, debut, milieu)
if liste[milieu] < mot:
return est_present(liste, mot, milieu + 1, fin)
return TrueTrois corrections respectent l’intervalle[debut, fin[ : le cas vide est debut>=fin ; la branche gauche garde debut et utilise milieu comme nouvelle fin exclusive ; la branche droite commence à milieu+1. Le milieu a déjà été comparé, on ne le réintroduit donc pas dans la recherche. Le code initial réinitialisait début à 0 et supprimait un candidat supplémentaire à gauche avec milieu−1.
Un test complet inclut le mot absent après le dernier élément, un mot absent avant le premier, une liste vide et un mot situé à une extrémité. Ces cas font apparaître les erreurs de bornes qu’un succès surNSI ne montre pas.
Voir la somme et sa collision de hachageUn atelier pour expérimenter
Saisissez un mot en lettresA àZ. Comparez notamment SAC etCAS, puis ADMINISTRATEUR. Le tableau suit la somme et le reste sur un octet après chaque lettre.
Lire le résultat de l’expérience initiale
Clé : 234 (0xEA).
L’ordre des lettres ne change pas la somme.Une clé identique ne prouve pas que deux mots sont égaux.
| Lettre | ASCII décimal | Somme cumulée | Reste modulo256 |
|---|---|---|---|
| N | 78 | 78 | 78 |
| S | 83 | 161 | 161 |
| I | 73 | 234 | 234 |
Le hachage réduit le groupe à examiner ; il ne remplace pas la comparaison des mots.
Exercice 3 · 8 points
Diviseurs et multiples : énumérer les chemins sans répétition
Les entiers 1 à 9 sont disposés en cercle. On peut aller d’un nombre à un diviseur ou un multiple différent de lui-même, sans réutiliser un nombre. Le but est d’obtenir un chemin non prolongeable aussi long que possible. Un chemin de six nombres possède cinq arêtes : le sujet mesure sa longueur en arêtes, alors que len(chemin) compte ses sommets.
Depuis 4, on peut aller vers 1,2 ou 8. Le chemin 4,8,2,6 peut être prolongé par 1 puis 7. On s’arrête alors, car le seul voisin de 7, le nombre 1, a déjà été utilisé.
Lire les connexions du schéma
- 4 vers 8
- 8 vers 2
- 2 vers 6
- 6 vers 1
- 1 vers 7
Figures 2 et 3 : six autres chemins non prolongeables
Lire les connexions du schéma
- 1 vers 2
- 2 vers 6
- 6 vers 3
- 3 vers 9
Lire les connexions du schéma
- 1 vers 7
Lire les connexions du schéma
- 1 vers 3
- 3 vers 6
- 6 vers 2
- 2 vers 4
- 4 vers 8
Lire les connexions du schéma
- 3 vers 6
- 6 vers 1
- 1 vers 7
Lire les connexions du schéma
- 3 vers 1
- 1 vers 5
Lire les connexions du schéma
- 3 vers 6
- 6 vers 2
- 2 vers 4
- 4 vers 8
- 8 vers 1
- 1 vers 7
Question 1
#Donner un chemin non prolongeable qui commence par 3→9→1→2.
Indice
Après avoir prolongé, contrôlez tous les voisins du dernier nombre.
Comprendre la correction
Par exemple 3 → 9 → 1 → 2 → 4 → 8. Chaque paire respecte une relation de divisibilité et aucun nombre ne se répète. À8, les seuls voisins sont 1,2 et 4, tous déjà présents : le chemin est donc non prolongeable. La question n’exige pas de prouver qu’il est le plus long parmi tous les chemins.
PartieA : chaque Sommet contient sa valeur et deux listes d’objets, diviseurs et multiples. Le graphe complet est :
Lire les connexions du schéma
- 1 relié à 2
- 1 relié à 3
- 1 relié à 4
- 1 relié à 5
- 1 relié à 6
- 1 relié à 7
- 1 relié à 8
- 1 relié à 9
- 2 relié à 4
- 2 relié à 6
- 2 relié à 8
- 3 relié à 6
- 3 relié à 9
- 4 relié à 8
class Sommet:
def __init__(self,valeur):
self.valeur = valeur
self.diviseurs = []
self.multiples = []def creer_jeu(n):
jeu = []
for valeur in range(1,n+1):
sommet = Sommet(valeur)
jeu.append(sommet)
return jeu
jeu_9 = creer_jeu(9)Question 2
#Quelles valeurs prend valeur pendant creer_jeu(9) ?
Indice
La boucle crée un objet pour chaque nombre du jeu.
Comprendre la correction
Les entiers 1,2,3,4,5,6,7,8 et 9, dans cet ordre. La borne 10 de range(1,10) est exclue.
Question 3
#Que vaut jeu_9[0].valeur après cette création ?
Indice
Le jeu commence à 1 mais les indicesPython à 0.
Comprendre la correction
La valeur est 1.L’indice 0 désigne le premier objet créé, qui représente l’entier 1. Il ne faut pas confondre l’indice dans la liste et le nombre représenté.
def relier_diviseurs(self,jeu):
for s in jeu:
if s != self and self.valeur % s.valeur == 0:
self.diviseurs.append(s)
s.multiples.append(self)Question 4
#Expliquer le test s!=self and self.valeur%s.valeur==0 de relier_diviseurs.
Indice
Les deux conditions répondent à deux règles différentes du jeu.
Comprendre la correction
s!=self exclut une liaison du sommet vers lui-même. Le reste nul signifie que s.valeur divise exactement self.valeur.La méthode ajoute alors s aux diviseurs de self et self aux multiples de s : les deux objets connaissent leur relation réciproque. Les valeurs du jeu sont positives, donc le calcul du modulo ne divise jamais par zéro.
Question 5
#Donner le contenu de jeu_9[5].diviseurs après avoir relié ses diviseurs dans le jeu.
Indice
À l’indice 5, on trouve le nombre 6.
Comprendre la correction
jeu_9[5] représente 6. Ses diviseurs propres sont 1,2 et 3, donc la liste contient les objets [jeu_9[0],jeu_9[1],jeu_9[2]], dans cet ordre. Ce ne sont pas les entiers eux-mêmes : on obtient les valeurs par la méthode de la question 7. La consigne écrit parfois jeu au lieu de jeu_9 ; on utilise ici la liste du jeu à neuf sommets.
Question 6
#Compléter la ligne manquante de creer_jeu avant jeu.append(sommet).
Indice
Les diviseurs propres d’un entier positif sont plus petits que lui.
Comprendre la correction
def creer_jeu(n):
jeu = []
for valeur in range(1,n+1):
sommet = Sommet(valeur)
sommet.relier_diviseurs(jeu)
jeu.append(sommet)
return jeuLa ligne est sommet.relier_diviseurs(jeu). Au moment où l’on crée la valeur v, la liste jeu contient déjà 1 àv−1, donc tous les diviseurs propres possibles de v.L’appel met aussi à jour les listes de multiples des anciens sommets. Le sommet courant est ajouté ensuite. Cette construction utilise l’ordre croissant pour ne tester chaque paire qu’une fois.
Le squelette est return [... for sommet in ...]. Le PDF donne les exemples jeu_9[7].lister_diviseurs() et jeu_9[1].lister_multiples().
Question 7
#Compléter lister_diviseurs pour renvoyer les valeurs entières des objets diviseurs.
Indice
Extraire l’attribut valeur de chaque objet.
Comprendre la correction
def lister_diviseurs(self):
return [sommet.valeur for sommet in self.diviseurs]La compréhension transforme une liste d’objets en liste de nombres sans modifier les liaisons. Le même principe donne lister_multiples avec self.multiples.Dans le jeu complet,8 possède les diviseurs 1,2,4 ;2 possède les multiples 4,6,8.L’exemple du PDF qui affiche seulement[4,8] pour 2 oublie 6 et ne correspond pas au graphe complet.
Question 8
#Compléter les tests vérifiant que le sommet représentant 3 est relié à tous ses voisins.
Indice
Trois voisins attendus : un diviseur et deux multiples.
Comprendre la correction
l_div_3 = jeu_9[2].lister_diviseurs()
l_mult_3 = jeu_9[2].lister_multiples()
assert 1 in l_div_3
assert 6 in l_mult_3
assert 9 in l_mult_3Le nombre 3 est à l’indice 2. Son seul diviseur propre est 1 et ses multiples dans 1..9 sont 6 et 9. Ces assertions prouvent la présence des voisins attendus mais pas l’absence de voisins erronés. Pour renforcer le test, on peut aussi vérifier les listes exactes :
assert l_div_3 == [1]
assert l_mult_3 == [6,9]PartieB : la file utilise une liste donnees et un indice decalage. La tête logique est à cet indice ; les cases précédentes ne sont pas supprimées mais ignorées. Cela évite de décaler les cases à chaque retrait. Le code fourni, hors méthode taille à compléter, est :
class File:
def __init__(self):
self.donnees = []
self.decalage = 0
def est_vide(self):
return self.decalage == len(self.donnees)
def enfiler(self,element):
self.donnees.append(element)
def defiler(self):
assert not self.est_vide()
tete = self.donnees[self.decalage]
self.decalage += 1
return tete
def taille(self):
...Question 9
#Expliquer assert not self.est_vide() dans defiler.
Indice
L’accès à donnees[decalage] exige qu’une tête logique existe.
Comprendre la correction
L’assertion impose que la file contienne un élément avant le retrait. Si elle est vide, une AssertionError interrompt l’appel au lieu de lire une position invalide.C’est une précondition de defiler, qui rappelle que le programme appelant doit contrôler l’état de la file.
Question 10
#Compléter taille de la classe File.
Indice
Soustraire la partie ignorée de la liste.
Comprendre la correction
def taille(self):
return len(self.donnees) - self.decalagelen(donnees) compte aussi les éléments déjà retirés.L’indice decalage est précisément leur nombre. La différence donne donc les éléments encore présents. Par exemple, après trois insertions et deux retraits, la liste physique contient toujours trois cases mais la taille logique vaut 1.
Question 11
#Écrire le test complet : créer une file, enfiler 1,2,3, vérifier la taille, défiler dans le bon ordre, puis vérifier le vide.
Indice
Un test de taille et un test d’ordre vérifient des propriétés différentes.
Comprendre la correction
f = File()
assert f.est_vide()
f.enfiler(1)
f.enfiler(2)
f.enfiler(3)
assert f.taille() == 3
assert f.defiler() == 1
assert f.defiler() == 2
assert f.defiler() == 3
assert f.est_vide()
assert f.taille() == 0Les valeurs de retour vérifient FIFO : le premier entré sort en premier. Une simple vérification de taille ne détecterait pas une structure qui retire les éléments à l’envers. On peut ensuite enfiler 4 et vérifier qu’il ressort correctement malgré decalage déjà avancé ; la file vide ne nécessite pas ici de vider physiquement donnees.
def rechercher_chemins(jeu):
chemins_np = []
f = File()
for sommet in jeu:
f.enfiler([sommet])
while ...:
chemin = ...
dernier = ...
voisins = dernier.diviseurs + dernier.multiples
prolongeable = ...
for voisin in voisins:
if voisin not in chemin:
prolongeable = ...
f.enfiler(...)
if not prolongeable:
chemins_np.append(chemin)
return chemins_npQuestion 12
#Compléter rechercher_chemins pour énumérer tous les chemins non prolongeables depuis tous les sommets.
Indice
Le booléen décrit l’existence d’au moins un prolongement de ce chemin précis.
Comprendre la correction
def rechercher_chemins(jeu):
chemins_np = []
f = File()
for sommet in jeu:
f.enfiler([sommet])
while not f.est_vide():
chemin = f.defiler()
dernier = chemin[-1]
voisins = dernier.diviseurs + dernier.multiples
prolongeable = False
for voisin in voisins:
if voisin not in chemin:
prolongeable = True
f.enfiler(chemin + [voisin])
if not prolongeable:
chemins_np.append(chemin)
return chemins_npChaque départ est placé dans la file comme chemin d’un seul objet. Le booléen prolongeable est remis à False pour chaque chemin, puis passe à True dès qu’au moins un voisin admissible existe. Si aucun prolongement n’a été créé, le chemin courant est terminal et est ajouté au résultat.
Le test voisin not in chemin interdit une répétition dans ce chemin, mais n’interdit pas de réutiliser ce sommet dans un autre candidat. Une visite globale par sommet serait incorrecte : les historiques déjà parcourus changent les prolongements encore autorisés. Chaque branche utilise chemin+[voisin], une nouvelle liste.L’algorithme termine puisque chaque chemin utilise au plus n sommets distincts, mais le nombre de chemins peut être très élevé.
Question 13
#Écrire valeurs_chemin qui transforme un chemin d’objets en liste de leurs valeurs.
Indice
Appliquer la même transformation qu’à la question 7.
Comprendre la correction
def valeurs_chemin(chemin):
return [sommet.valeur for sommet in chemin]Les objets restent nécessaires à la recherche pour accéder aux diviseurs et multiples. La projection sur valeur est réservée à l’affichage et à la liste finale. Cette fonction ne modifie pas le chemin d’origine.
Question 14
#Compléter les lignes produisant tous les chemins non prolongeables du jeu à 9 entiers sous forme de listes de valeurs.
Indice
Le résultat doit être une liste de listes.
Comprendre la correction
chemins = []
for chemin in rechercher_chemins(jeu_9):
chemins.append(valeurs_chemin(chemin))Le nom choisi à la question 13 est valeurs_chemin ; la mention chemin_valeur dans cette consigne est une variante typographique à harmoniser.L’accumulation utilise append pour conserver un chemin par élément. Utiliser extend fusionnerait tous les nombres en une liste plate et ferait perdre les frontières entre chemins.
Question 15
#Écrire extraire_plus_longs_chemins(L_chemins), qui renvoie tous les chemins de longueur maximale.
Indice
Distinguer remplacement lors d’un nouveau maximum et ajout lors d’une égalité.
Comprendre la correction
def extraire_plus_longs_chemins(L_chemins):
resultats = []
longueur_max = -1
for chemin in L_chemins:
if len(chemin) > longueur_max:
longueur_max = len(chemin)
resultats = [chemin]
elif len(chemin) == longueur_max:
resultats.append(chemin)
return resultatsUn nouveau maximum remplace les candidats précédents ; une égalité ajoute une autre solution. Initialiser le maximum à−1 permet de traiter aussi une liste contenant un chemin vide. La liste d’entrée vide renvoie[]. Le résultat reste toujours une liste de chemins, même s’il n’y en a qu’un : l’exemple du PDF affichant une liste plate dans ce cas contredit ce contrat.
assert extraire_plus_longs_chemins([[1,3,6,2,4,8]]) == [[1,3,6,2,4,8]]
assert extraire_plus_longs_chemins([[1,7],[3,1,5],[4,1,7]]) == [[3,1,5],[4,1,7]]Comparer len(chemin) suffit pour choisir le plus long, même si la longueur mathématique du chemin vaut len(chemin)−1 : soustraire la même constante ne change pas les comparaisons. Dans le jeu à 9 sommets, le maximum est 8 sommets, soit 7 arêtes, et 16 chemins atteignent cette taille.
| Sommets | Chemins non prolongeables |
|---|---|
| 9 | 377 |
| 10 | 800 |
| 11 | 925 |
| 12 | 5279 |
| 13 | 5935 |
| 14 | 9896 |
Question 16
#Peut-on résoudre un jeu de taille aussi grande que souhaité avec ce programme ? Justifier à partir du tableau fourni.
Indice
La file contient des chemins complets, dont le nombre augmente avec les branches possibles.
Comprendre la correction
Pas en pratique.L’énumération de tous les chemins simples entraîne une explosion du travail et de la mémoire, notamment dans la file qui conserve les chemins en attente. Le tableau passe de 377 chemins terminaux pour 9 sommets à 9896 pour 14, avec déjà 5279 à 12. La finitude d’un jeu garantit la terminaison théorique de cette exploration, pas un temps ni une mémoire raisonnables pour une taille arbitraire.
Les quelques valeurs du tableau ne suffisent pas à démontrer une formule asymptotique précise. Mais le mécanisme de branchement explique le risque : à chaque prolongement, plusieurs choix peuvent créer plusieurs listes de candidats. Le coût ne se limite pas au nombre de sommets ni aux seuls chemins finalement conservés.
Combien de chemins se cachent dans ce petit jeu ?Un atelier pour expérimenter
Faites varier la taille du jeu de 3 à 10 entiers. Le moteur énumère réellement les chemins sans répétition et compte les chemins non prolongeables. Il affiche un des plus longs, en distinguant sommets et arêtes.
Lire le résultat de l’expérience initiale
377 chemins terminaux ;maximum 8 sommets,soit7 arêtes.
Le parcours interne est exhaustif :un sommet peut réapparaître dans plusieurs chemins,mais jamais deux fois dans le même.
| Position | Nombre | Passage suivant |
|---|---|---|
| 1 | 4 | Vers un multiple |
| 2 | 8 | Vers un diviseur |
| 3 | 2 | Vers un multiple |
| 4 | 6 | Vers un diviseur |
| 5 | 3 | Vers un multiple |
| 6 | 9 | Vers un diviseur |
| 7 | 1 | Vers un multiple |
| 8 | 5 | Plus de voisin disponible |
L’état de recherche est un chemin entier ; sa combinatoire peut être grande même avec peu de sommets.
Du sujet à la méthode
Votre prochaine séance de révision
- Suivre les rôles demandés dans les jointures.
- Un exemple qui réussit ne valide pas les bornes d’une recherche.
- Pour les chemins, préciser si la longueur compte des sommets ou des arêtes.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 25-NSIJ2G11 (PDF) · Publication d’origine (nouvel onglet). Corrigé et explications pédagogiques proposés par Sofien.
