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
Compagnie de danse : relier les spectacles et charger le matériel
La compagnie L’air de l’art se déplace avec ses danseurs, costumes et accessoires. Jessica, sa secrétaire, utilise une base relationnelle pour relier les personnes, chorégraphies et représentations. Les adresses sont simplifiées à des villes et les codes postaux contiennent cinq chiffres. Le schéma est :
| Table | Attributs | Clé primaire | Clés étrangères |
|---|---|---|---|
| personne | id, nom, adresse, codep | id | - |
| spectacle | id, jour, lieu, codep, titre | id | - |
| choregraphie | nomchore, annee, choregraphe | nomchore | choregraphe → personne.id |
| participe | idpersonne, idspectacle, idchoregraphie | (idpersonne, idspectacle, idchoregraphie) | idpersonne → personne.id ; idspectacle → spectacle.id ; idchoregraphie → choregraphie.nomchore |
Le jour est au format AAAA-MM-JJ. Le nom de chorégraphie est unique ; participe associe une personne qui danse, une représentation et une chorégraphie. Le schéma nomme la ville du spectacle lieu, même si la phrase explicative parle d’adresse. Mots SQL autorisés : SELECT, DISTINCT, FROM, WHERE, JOIN ... ON, UPDATE ... SET, DELETE, INSERT INTO ... VALUES. COUNT(*) compte les lignes.
Question 1
#Ajouter la chorégraphie Tout autour, créée en 2015 par Rachid Ouramdane d’identifiant 9.
Indice
La dernière colonne attend une référence à personne.id.
Comprendre la correction
INSERT INTO choregraphie (nomchore, annee, choregraphe)
VALUES ('Tout autour', 2015, 9);Le chorégraphe est référencé par son identifiant, pas par son nom. La personne 9 doit exister pour respecter la clé étrangère. Le nom de chorégraphie doit être encore disponible puisqu’il constitue sa clé primaire.
Question 2
#Afficher le nombre de chorégraphies créées en 1983.
Indice
La question porte sur la table des œuvres, pas sur les représentations.
Comprendre la correction
SELECT COUNT(*)
FROM choregraphie
WHERE annee = 1983;On filtre les lignes sur l’année puis les compte. Il ne faut pas compter les participations à ces chorégraphies, qui pourraient se répéter pour plusieurs danseurs et spectacles.
SELECT choregraphie.nomchore, personne.nom
FROM choregraphie
JOIN personne ON choregraphie.choregraphe = personne.id;Question 3
#Décrire le résultat de la requête donnée.
Indice
Le rôle de la personne dépend de la clé utilisée pour la joindre.
Comprendre la correction
Elle renvoie le nom de chaque chorégraphie et le nom de la personne qui l’a chorégraphiée. La jointure suit choregraphe = personne.id. Elle ne renvoie pas tous les danseurs de cette œuvre : ce lien passerait par participe.
Question 4
#Afficher les noms des danseurs ayant dansé dans un spectacle de titre Tout autour.
SQLRetrouver les danseurs du spectacle Tout autourÉcrivez votre solution et mettez-la à l’épreuve
Renvoyez les noms des personnes ayant dansé dans une représentation dont le titre est Tout autour, sans répéter une personne pour chacune de ses chorégraphies. Le titre du spectacle ne se confond pas avec le nom d’une chorégraphie.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Jeu pédagogique sur le schéma de la compagnie : Le nom du spectacle, la chorégraphie Tout autour et son chorégraphe 9 sont issus du sujet. Les représentations, danseurs et participations sont des compléments pédagogiques explicitement ajoutés.
- Cas complémentaire : plusieurs représentations de même titre : Jeu complémentaire pédagogique, distinct des données officielles. Deux représentations portent le même titre. La recherche doit les couvrir toutes et éviter une répétition de Danseur C.
Indice
Suivez personne → participe → spectacle.
Comprendre la correction
SELECT DISTINCT p.nom
FROM personne AS p
JOIN participe AS pa ON pa.idpersonne = p.id
JOIN spectacle AS s ON s.id = pa.idspectacle
WHERE s.titre = 'Tout autour';Le titre demandé est celui du spectacle, pas le nom de la chorégraphie. Une personne peut participer à plusieurs chorégraphies dans la même représentation ; DISTINCT évite de répéter son nom dans le résultat. Si plusieurs spectacles ont ce titre, toutes leurs participations sont concernées. Une projection sur le seul nom peut confondre des homonymes ; afficher aussi l’identifiant serait utile pour les distinguer, mais la question demande les noms.
Question 5
#Quelles précautions prendre avant de supprimer un spectacle ? Sans écrire la requête.
Indice
Une suppression de parent ne doit pas laisser de participations orphelines.
Comprendre la correction
Vérifier les lignes de participe qui référencent ce spectacle et les supprimer ou les archiver selon le besoin avant d’effacer la ligne de spectacle, sauf cascade prévue. Sinon une clé étrangère pointerait vers un spectacle absent et le SGBD refuserait l’opération. Il faut cibler l’identifiant exact et préserver les autres personnes et chorégraphies, qui peuvent servir à d’autres représentations.
Partie B : chargement du véhicule
Chaque sous-dictionnaire contient nb, volume unitaire et poids unitaire :
dmateriel = {
"costume_A": {
"nb": 3,
"volume": 2,
"poids": 1
},
"costume_B": {
"nb": 1,
"volume": 6,
"poids": 2
},
"chaise_bois": {
"nb": 40,
"volume": 50,
"poids": 4
},
"chapeau_A": {
"nb": 2,
"volume": 3,
"poids": 1
},
"chapeau_B": {
"nb": 4,
"volume": 7,
"poids": 1
}
}n = 0
for clef in dmateriel:
n += dmateriel[...][...]Question 6
#Compléter le total du nombre d’éléments nécessaires.
Indice
Le dictionnaire externe et les sous-dictionnaires ont des rôles différents.
Comprendre la correction
n += dmateriel[clef]['nb']La première clé sélectionne le type de matériel ; la seconde sélectionne sa quantité. Le total vaut 3+1+40+2+4 = 50 objets. len(dmateriel) vaudrait seulement 5, nombre de types distincts, et ne répondrait pas à la question.
def tab_clefs_tab_volumes(dmatos):
tab_clefs = []
tab_volumes = []
for clef, dico in dmatos.items():
tab_clefs.append(...)
tab_volumes.append(...)
return tab_clefs, tab_volumesRésultat attendu : ([costume_A,costume_B,chaise_bois,chapeau_A,chapeau_B], [2,6,50,3,7]), avec les noms représentés comme chaînes.
Question 7
#Compléter tab_clefs_tab_volumes pour obtenir les clés et leurs volumes unitaires dans deux listes correspondantes.
Indice
Les deux listes doivent conserver la même correspondance d’indices.
Comprendre la correction
tab_clefs.append(clef)
tab_volumes.append(dico["volume"])items() fournit à chaque tour une clé et le sous-dictionnaire associé. Les deux append sont effectués dans le même tour : les indices restent alignés entre les listes. Le volume demandé est unitaire, pas nb × volume. Sur l’exemple, les volumes sont [2,6,50,3,7].
Question 8
#Charger les objets les plus volumineux d’abord puis les petits relève de quel type d’algorithme ?
Indice
Le choix se fait localement sans explorer toutes les combinaisons.
Comprendre la correction
Une stratégie gloutonne : à chaque étape, on fait un choix local immédiat, ici le plus gros objet qui peut encore entrer. Elle ne revient pas sur les objets déjà chargés pour réorganiser le contenu. Une telle stratégie peut être rapide et intuitive sans garantir le minimum global de véhicules.
clefs_triees_selon_volume(dmatos) est supposée disponible et trie les clés par volumes unitaires croissants. Pour dmateriel : [costume_A,chapeau_A,costume_B,chapeau_B,chaise_bois].
Question 9
#Citer un tri vu en cours et un ordre de grandeur de son coût en fonction de n.
Indice
Un nom de tri doit être accompagné du bon ordre de grandeur.
Comprendre la correction
Par exemple le tri par insertion, O(n²) dans le pire cas. Autre réponse valable au programme de Terminale : le tri fusion, O(n log n) dans le pire cas. Il faut préciser à quel cas correspond le coût : le tri par insertion est linéaire sur une liste déjà triée, mais ce n’est pas son pire cas.
def remplissage(dmatos, vmax):
vol = 0
clefs_triees = clefs_triees_selon_volume(dmatos)
i = len(clefs_triees) - 1
elements = []
while i >= ... and vol < ...:
clef = clefs_triees[i]
if dmatos[clef]["nb"] == 0:
i -= 1
elif vol + dmatos[clef]["volume"] <= vmax:
vol += ...
elements.append(...)
dmatos[clef]["nb"] -= 1
else:
...
return elementsExemple attendu : remplissage(dmateriel,160) renvoie [chaise_bois,chaise_bois,chaise_bois,chapeau_B,chapeau_A].
Question 10
#Compléter remplissage, qui charge sans dépasser vmax et met à jour les quantités restantes.
PythonCharger le décor et mettre à jour le stockÉcrivez votre solution et mettez-la à l’épreuve
Complétez remplissage(dmatos, vmax). Chaque type de matériel possède nb et volume. Le helper fournit les clés par volume croissant ; chargez donc les types en commençant par la fin de cette liste. Prenez autant d’exemplaires que possible avant de passer au type suivant, sans dépasser vmax. Renvoyez la liste des noms chargés et décrémentez les stocks dans dmatos. Le poids n’intervient pas dans cette question.
def remplissage(dmatos, vmax):
# À vous de jouer
passLes cas de test proposés :
- Le chargement de 160 du sujet : 150 de chaises, puis 7 et 3 de chapeaux remplissent la voiture. Seuls les stocks chargés diminuent.
- Un objet trop gros ne bloque pas les petits : Refuser le plus gros doit faire avancer vers un type plus petit.
- Une égalité exacte avec la capacité : Le dernier objet peut atteindre exactement vmax.
- Un type épuisé est sauté : Une quantité nulle ne doit pas devenir négative.
- Ce glouton n’optimise pas toutes les combinaisons : La règle imposée retient 8, même si 6 + 4 remplirait mieux. Il faut appliquer le sujet, sans inventer une autre stratégie.
- Aucune capacité et aucun stock : Une boucle sans candidat ou sans place termine sans modifier le matériel.
Indice
Après un ajout réussi, ne diminuez pas i : il peut rester plusieurs exemplaires du même type.
Comprendre la correction
def remplissage(dmatos, vmax):
vol = 0
clefs_triees = clefs_triees_selon_volume(dmatos)
i = len(clefs_triees) - 1
elements = []
while i >= 0 and vol < vmax:
clef = clefs_triees[i]
if dmatos[clef]['nb'] == 0:
i -= 1
elif vol + dmatos[clef]['volume'] <= vmax:
vol += dmatos[clef]['volume']
elements.append(clef)
dmatos[clef]['nb'] -= 1
else:
i -= 1
return elementsLe tri est croissant mais l’indice commence à la fin : les plus gros objets sont donc examinés d’abord. Si un objet entre, on l’ajoute, réduit sa quantité et garde le même indice pour tenter un autre exemplaire. Si son stock est nul ou s’il ne tient plus, on passe au type suivant, plus petit. Chaque itération réduit une quantité ou diminue i, donc la boucle progresse. L’invariant de volume est 0 ≤ vol ≤ vmax.
Pour vmax = 160, trois chaises occupent 150 ; un chapeau_B ajoute 7 et un chapeau_A ajoute 3, soit exactement 160. Les quantités restantes deviennent 37 chaises, 3 chapeaux_B et 1 chapeau_A, les costumes restant inchangés. La fonction ne vérifie pas une capacité en poids : le champ poids est fourni mais la consigne de cette partie ne porte que sur le volume.
Question 11
#Écrire nb_voitures(dmatos,vmax), nombre de véhicules nécessaires selon remplissage.
Indice
Il faut une condition garantissant qu’une voiture vide signifie réellement un stock épuisé.
Comprendre la correction
def nb_voitures(dmatos, vmax):
for clef in dmatos:
if dmatos[clef]["nb"] > 0 and dmatos[clef]["volume"] > vmax:
raise ValueError("Un objet dépasse la capacité du véhicule")
nombre = 0
while remplissage(dmatos, vmax) != []:
nombre += 1
return nombreChaque appel remplit un nouveau véhicule et retire les objets chargés du stock. La première liste vide indique que plus rien ne reste à charger, sous la condition que chaque objet puisse entrer seul. La vérification initiale explicite cette condition ; sans elle, un objet trop volumineux pourrait rester alors que la fonction renvoie []. On suppose aussi des volumes strictement positifs, des quantités non négatives et une capacité positive.
Le nombre obtenu est celui de cette stratégie gloutonne, pas une garantie de minimum absolu. La fonction consomme le stock en place : pour conserver l’inventaire initial, il faudrait travailler sur une copie des sous-dictionnaires, pas seulement sur une copie superficielle du dictionnaire externe.
Remplir la camionnette et observer ce qui resteUn atelier pour expérimenter
Réglez la capacité d’un véhicule. Le modèle recommence toujours avec le stock original, applique exactement le chargement glouton du sujet et affiche les objets retenus.
Lire le résultat de l’expérience initiale
160 / 160 unités chargées
La stratégie prend autant de gros objets que possible puis passe aux plus petits. Les objets de volume supérieur à la capacité resteront impossibles à transporter dans ce type de véhicule.
| Matériel | Quantité restante | Volume unitaire |
|---|---|---|
| costume_A | 3 | 2 |
| costume_B | 1 | 6 |
| chaise_bois | 37 | 50 |
| chapeau_A | 1 | 3 |
| chapeau_B | 3 | 7 |
Un algorithme glouton fournit une solution selon sa règle locale. Il faut vérifier séparément la faisabilité de tous les objets et l’optimalité globale.
Exercice 2 · 6 points
Perceptron : de la somme pondérée à l’apprentissage supervisé
Dans le scénario du concours Jeunes Talents IA 2025, une équipe de Terminale construit un perceptron pour classer des emails. L’apprentissage est supervisé : chaque exemple vient avec une réponse cible. Un perceptron reçoit n entrées xᵢ, possède n poids pᵢ et un biais b, puis calcule somme = Σ xᵢpᵢ + b. La fonction seuil vaut 1 si cette somme est positive ou nulle, 0 sinon.
Entrées x1, x2 → multiplication par p1, p2
↓
somme x1*p1 + x2*p2 + b
↓
seuil à zéro
↓
sortie 0 ou 1Le schéma original illustre deux entrées et un biais ajouté avant le seuil. On considère d’abord p1=0,7, p2=-0,2 et b=0,1.
Question 1
#Calculer la somme puis la sortie pour x1=1 et x2=3.
Indice
Le produit associé à x2 vaut -0,6.
Comprendre la correction
Somme = 1×0,7 + 3×(-0,2) + 0,1 = 0,2. Elle est positive, donc la sortie vaut 1. Le biais est ajouté une fois, après la somme des produits. Les entrées ne sont pas les poids : x2=3 multiplie bien le poids négatif -0,2.
class DetecteurSpam:
def __init__(self, nb_entrees):
self.biais = 0.0
self.poids = [0.0 for i in range(nb_entrees)]
def get_biais(self):
return self.biais
def set_biais(self, nouveau_biais):
self.biais = nouveau_biais
def get_poids(self):
return self.poids
def set_poids(self, nouveaux_poids):
assert len(nouveaux_poids) == len(self.poids)
self.poids = nouveaux_poidsQuestion 2
#Donner un nom de méthode et un nom d’attribut de DetecteurSpam.
Indice
Repérez les affectations self.nom et les définitions def.
Comprendre la correction
set_biais est une méthode ; poids est un attribut. biais est également un attribut ; get_biais, get_poids et set_poids sont d’autres méthodes. Le paramètre nb_entrees n’est pas conservé comme attribut portant ce nom.
Question 3
#Créer detecteur à deux entrées et fixer ses poids à [0.7,-0.2] et son biais à 0.1 en utilisant les méthodes.
Indice
Initialisez la bonne dimension avant de remplacer le vecteur de poids.
Comprendre la correction
detecteur = DetecteurSpam(2)
detecteur.set_poids([0.7, -0.2])
detecteur.set_biais(0.1)Le constructeur initialise deux poids nuls ; les méthodes les remplacent ensuite. Le nombre 2 indique la dimension du modèle, pas le nombre d’exemples de son entraînement.
Question 4
#Expliquer le rôle de l’assertion de longueur dans set_poids.
Indice
Une somme pondérée demande une correspondance un-à-un entre entrées et poids.
Comprendre la correction
Elle vérifie que le nouveau vecteur possède autant de poids que le modèle a d’entrées. Si les longueurs diffèrent, une AssertionError interrompt l’affectation. Elle ne vérifie ni le type numérique des poids ni leur pertinence pour classer des emails. Le corps de la méthode utilise le nom nouveaux_poids ; la variante singulière dans la phrase du sujet est une coquille.
Question 5
#Écrire fonction_activation(self,valeur), seuil renvoyant 1 pour valeur≥0, 0 sinon.
Indice
L’égalité à zéro doit être incluse.
Comprendre la correction
def fonction_activation(self, valeur):
if valeur >= 0:
return 1
return 0Le cas zéro appartient à la classe 1. Ce choix influence les premiers exemples puisque le constructeur crée des poids et un biais nuls : au départ, toutes les sommes valent 0 et toutes les prédictions valent donc 1.
def prediction(self, entrees):
somme = 0
for i in range(len(entrees)):
somme += ...
somme += ...
sortie = self.fonction_activation(...)
return ...Exemple fourni à trois entrées : biais 0.2, poids [0.5,-0.1,0.2], prediction([0.1,0.3,0.2]) vaut 1.
Question 6
#Compléter prediction(entrees).
Indice
Ne confondez pas la somme réelle et la sortie binaire.
Comprendre la correction
def prediction(self, entrees):
somme = 0
for i in range(len(entrees)):
somme += entrees[i] * self.poids[i]
somme += self.biais
sortie = self.fonction_activation(somme)
return sortieChaque entrée est multipliée par son poids au même indice. Le biais est ajouté hors de la boucle, exactement une fois. L’activation est ensuite appelée sur la somme complète. Pour les poids [0.5,-0.1,0.2], le biais 0.2 et les entrées [0.1,0.3,0.2], la somme vaut 0.05-0.03+0.04+0.2 = 0.26, donc la sortie est 1. Le sujet garantit ici l’égalité des dimensions.
Question 7
#Donner le coût temporel de prediction en fonction de n entrées.
Indice
La méthode parcourt une fois la liste des entrées.
Comprendre la correction
O(n), linéaire : n produits et n accumulations dans la boucle, suivis d’un ajout et d’un test de coût constant. Le nombre d’exemples d’entraînement n’intervient pas dans le coût d’une seule prédiction.
echantillon = [[0.5,0.3], [0.2,0.1], [0.4,0.8], [0.7,0.9]]
cibles = [0,1,1,1]Question 8
#Donner echantillon[1] et echantillon[2][1].
Indice
Les deux niveaux d’indices commencent à zéro.
Comprendre la correction
echantillon[1] = [0.2,0.1] et echantillon[2][1] = 0.8. Le premier accès choisit le deuxième exemple, le second la deuxième caractéristique du troisième exemple.
Une époque parcourt tous les exemples. En cas d’erreur : nouveau poids = ancien poids + taux×erreur×entrée ; nouveau biais = ancien biais + taux×erreur.
def entrainement(self, echantillon, cibles, taux=0.1):
nb_erreurs = 0
for i in range(len(echantillon)):
entrees = echantillon[i]
cible = ...
predict = ...
if ...:
nb_erreurs += 1
erreur = cible - predict
for j in range(len(self.poids)):
self.poids[j] += ... * ... * entrees[j]
self.biais += taux * erreur
return nb_erreursQuestion 9
#Compléter les lignes 5,7,8,13 de entrainement.
PythonApprendre en corrigeant une erreur à la foisÉcrivez votre solution et mettez-la à l’épreuve
Complétez entrainement dans DetecteurSpam. Une époque parcourt les vecteurs echantillon et leurs cibles 0 ou 1 de même indice. Le helper prediction utilise les poids courants et un seuil qui renvoie 1 dès que la somme vaut zéro ou davantage. À chaque erreur, ajoutez taux × (cible - prédiction) × entrée à chaque poids et taux × (cible - prédiction) au biais. Renvoyez le nombre d’erreurs rencontrées pendant ce passage, pas un nouveau score calculé après toutes les mises à jour.
class DetecteurSpam(DetecteurSupport):
def entrainement(self, echantillon, cibles, taux=0.1):
# Une époque d’apprentissage
passLes cas de test proposés :
- Une prédiction déjà correcte : Les paramètres nuls prédisent 1, selon le seuil du sujet. Un exemple correct ne modifie rien.
- Une erreur vers la classe zéro : L’erreur cible-prédiction vaut -1 ; chaque poids reçoit une correction adaptée à son entrée.
- Une erreur vers la classe un : Une entrée nulle ne change pas son poids, mais le biais reçoit tout de même la correction.
- Le deuxième exemple voit les nouveaux paramètres : Le premier exemple fait passer le score à -2. Le second devient alors une erreur lui aussi ; les prédictions ne doivent pas être pré-calculées.
- Utiliser le taux par défaut : Le taux par défaut vaut 0.1. Les comparaisons de flottants tolèrent ici uniquement l’arrondi machine.
- Une époque sans exemple : Sans exemple, aucune erreur ni mise à jour : la fonction doit quand même retourner un entier.
Indice
Utilisez la prédiction avant mise à jour pour calculer cible - predict.
Comprendre la correction
def entrainement(self, echantillon, cibles, taux=0.1):
nb_erreurs = 0
for i in range(len(echantillon)):
entrees = echantillon[i]
cible = cibles[i]
predict = self.prediction(entrees)
if predict != cible:
nb_erreurs += 1
erreur = cible - predict
for j in range(len(self.poids)):
self.poids[j] += taux * erreur * entrees[j]
self.biais += taux * erreur
return nb_erreursLa cible et l’exemple partagent le même indice i. L’erreur signée vaut +1 si l’on aurait dû répondre 1, -1 si l’on aurait dû répondre 0. Elle oriente la mise à jour du biais et de chaque poids. Un exemple correctement classé ne change rien. Les poids modifiés sont immédiatement utilisés pour l’exemple suivant : l’ordre des exemples influence donc la trajectoire de l’apprentissage.
Le compteur indique les erreurs rencontrées pendant cette époque, avant chaque correction locale, pas forcément le nombre d’exemples mal classés par le modèle final de l’époque. En revanche, une époque sans erreur ne modifie aucun poids et prouve que le modèle courant classe correctement tous les exemples parcourus.
| URGENT | GRATUIT | SPAM attendu |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Question 10
#Créer un détecteur apprenant URGENT ET GRATUIT en au plus 1000 époques ; afficher le nombre nécessaire pour une époque sans erreur, ou -1.
Indice
Arrêtez-vous sur une époque entière sans mise à jour, pas après un seul exemple correct.
Comprendre la correction
emails = [[0,0], [0,1], [1,0], [1,1]]
spams = [0,0,0,1]
detecteur = DetecteurSpam(2)
for epoque in range(1, 1001):
erreurs = detecteur.entrainement(emails, spams)
if erreurs == 0:
print(epoque)
break
else:
print(-1)range(1,1001) autorise exactement mille époques, comptées à partir de 1. Le else de la boucle ne s’exécute que si aucun break n’a été rencontré : -1 signifie alors « pas d’époque sans erreur dans la limite fixée ». Il ne prouve pas une impossibilité mathématique d’apprentissage. On peut aussi écrire la même logique avec while et un compteur.
La cible correspond à une porte ET, qui est linéairement séparable. Par exemple poids [1,1], biais -1.5 distinguent le seul point (1,1) des trois autres. Ce petit modèle n’est pas un détecteur fiable du spam réel : il apprend uniquement la règle et les exemples choisis. Près du seuil zéro, les arrondis flottants peuvent changer le nombre exact d’époques observé ; on évite donc de confondre ce nombre dépendant de l’implémentation avec une propriété universelle de ET.
Faire apprendre la porte ET, une époque après l’autreUn atelier pour expérimenter
Faites varier le nombre d’époques. Pour lire les mises à jour exactement sans arrondi décimal, cet atelier utilise un taux de 1. Les règles de mise à jour et le seuil sont ceux du sujet.
Lire le résultat de l’expérience initiale
Poids [1, 1], biais 0
Le seuil renvoie 1 pour une somme égale à zéro. Une époque avec zéro erreur confirme que tous les exemples sont corrects et qu’aucun paramètre n’a changé.
| Époque | Erreurs rencontrées | Poids URGENT | Poids GRATUIT | Biais |
|---|---|---|---|---|
| 1 | 2 | 1 | 1 | 0 |
Une règle d’apprentissage ajuste des paramètres sur des exemples étiquetés. La qualité se mesure sur la règle et les données, pas sur le nom « IA ».
Exercice 3 · 8 points
Simuler RIP : masques, files et échanges de vecteurs
On simule la construction des tables de routage RIP. Le réseau de la figure 1 forme une chaîne R1-R2-R3 :
| Segment | Interfaces et machines |
|---|---|
| LAN 192.168.20.0/24 | PC1 192.168.20.1, switch, R1 192.168.20.254 |
| Lien 172.24.0.0/30 | R1 172.24.0.1 ; R2 172.24.0.2 |
| Lien 172.24.1.0/30 | R2 172.24.1.2 ; R3 172.24.1.1 |
| LAN 192.168.40.0/24 | R3 192.168.40.254, switch, PC2 192.168.40.1 |
Lire les connexions du schéma
- R1 relié à R2 : 172.24.0.0/30
- R2 relié à R3 : 172.24.1.0/30
Les adresses IP sont des suites de 32 bits. Le suffixe S de IP/S fixe les S premiers bits du réseau ; deux machines d’un même sous-réseau partagent ces bits. Deux adresses sont réservées au réseau et à la diffusion.
Question 1
#Identifier la partie fixe dans 192.168.20.0/24.
Indice
Un octet contient huit bits.
Comprendre la correction
Les trois premiers octets : 192.168.20, soit 24 bits. Le dernier octet est la partie machine et ne fait pas partie du préfixe fixe.
Question 2
#Quel nombre maximal de machines peut-on brancher sur un /30 ?
Indice
Calculez 2² puis retirez réseau et diffusion.
Comprendre la correction
2 : 32-30 = 2 bits machine, donc 4 adresses dont deux réservées. Un /30 est ainsi adapté au lien point à point entre deux interfaces.
RIP minimise les routeurs traversés. Table fournie pour R1 :
| Destination | Interface | Passerelle | Distance |
|---|---|---|---|
| 192.168.20.0/24 | 192.168.20.254 | - | 0 |
| 172.24.0.0/30 | 172.24.0.1 | - | 0 |
| 172.24.1.0/30 | 172.24.0.1 | 172.24.0.2 | 1 |
| 192.168.40.0/24 | 172.24.0.1 | 172.24.0.2 | 2 |
Question 3
#Donner la table RIP de R2.
Indice
Interface désigne la sortie locale ; passerelle désigne le prochain routeur.
Comprendre la correction
| Destination | Interface de R2 | Passerelle | Distance |
|---|---|---|---|
| 172.24.0.0/30 | 172.24.0.2 | - | 0 |
| 172.24.1.0/30 | 172.24.1.2 | - | 0 |
| 192.168.20.0/24 | 172.24.0.2 | 172.24.0.1 | 1 |
| 192.168.40.0/24 | 172.24.1.2 | 172.24.1.1 | 1 |
Les deux réseaux de liaison sont directement connectés à R2, donc sans passerelle intermédiaire et à distance 0 dans la convention du sujet. Pour les LAN distants, l’interface est celle de R2, tandis que la passerelle est l’adresse du voisin sur le lien partagé. Ces deux colonnes ne doivent pas être échangées.
Partie B : comparer des préfixes avec un masque
Les adresses et masques sont des tuples de quatre entiers. Le masque place à 1 les bits réseau et à 0 les bits machine.
Question 4
#Donner le tuple du masque de suffixe 30.
Indice
Le dernier octet contient six 1 suivis de deux 0.
Comprendre la correction
(255, 255, 255, 252)Les 24 premiers bits sont trois octets à 255. Le dernier octet a six bits réseau à 1 puis deux bits machine à 0 : 11111100₂ = 252.
01110110 (118)
& 11010010 (210)
= 01010010 (82)L’opérateur Python & applique ce ET directement aux entiers : 118 & 210 vaut 82.
Question 5
#Pourquoi 255 & A = A pour un entier A entre 0 et 255 ?
Indice
Un bit ET 1 conserve sa valeur.
Comprendre la correction
255 s’écrit 11111111 sur huit bits. Le ET bit à bit conserve chaque bit de A, car 0 ET 1 = 0 et 1 ET 1 = 1. La restriction 0≤A≤255 garantit que A tient dans ces huit positions.
Question 6
#(172,20,189,8) et (172,20,191,203) sont-elles dans le même réseau avec (255,255,252,0) ? Justifier.
Indice
Le masque peut fixer seulement une partie du troisième octet.
Comprendre la correction
Oui. Les deux premiers octets sont conservés et identiques ; le dernier est annulé par &0. Pour le troisième : 189 = 10111101, 191 = 10111111 et 252 = 11111100. Dans les deux cas, le ET donne 10111100 = 188. Les deux adresses masquées sont donc (172,20,188,0), réseau /22.
def meme_reseau(adresse1, adresse2, masque):
for i in range(4):
octet1 = adresse1[...] & masque[...]
octet2 = ...
if ...:
return ...
return ...Question 7
#Compléter meme_reseau pour comparer les deux adresses masquées.
Indice
Comparer l’adresse brute serait trop strict : les bits machine peuvent différer.
Comprendre la correction
def meme_reseau(adresse1, adresse2, masque):
for i in range(4):
octet1 = adresse1[i] & masque[i]
octet2 = adresse2[i] & masque[i]
if octet1 != octet2:
return False
return TrueUne seule différence de préfixe suffit à conclure que les réseaux diffèrent. True est renvoyé après les quatre octets, sinon on ne contrôlerait que le premier. Les entrées sont supposées des tuples d’octets valides et le masque un masque réseau cohérent.
Partie C : simulation objet
File fournit enfiler, defiler et est_vide. Reseau.connexion(file) attribue une adresse et associe cette adresse à la file reçue. Un paquet est le couple (orig,donnees). Tout paquet du réseau est distribué à toutes ses machines sauf l’origine.
class Reseau:
def __init__(self, adresse, masque):
self.adresse = adresse
self.masque = masque
self.machines = {}
self.file = File()
def diffuser(self):
while not ...:
paquet = ...
orig, donnees = paquet
for m in self.machines:
if ...:
self.machines[m].enfiler(...)Question 8
#Compléter Reseau.diffuser pour distribuer chaque paquet à toutes les machines sauf son émettrice.
Indice
Les routeurs doivent connaître l’émetteur du vecteur reçu.
Comprendre la correction
def diffuser(self):
while not self.file.est_vide():
paquet = self.file.defiler()
orig, donnees = paquet
for m in self.machines:
if m != orig:
self.machines[m].enfiler(paquet)On vide progressivement la file du réseau. machines associe une adresse à la file de réception de cette machine. Le paquet complet doit être envoyé, en conservant l’adresse orig dont le routeur aura besoin comme passerelle dans sa mise à jour. Envoyer seulement donnees ferait perdre cette origine. Le réseau diffusant à toutes les machines est une simplification explicite du simulateur, pas une description générale de toute communication Ethernet.
class Interface:
def __init__(self, reseau):
self.sortie = reseau.file
self.file = File()
self.adresse = reseau.connexion(self.file)
self.masque = reseau.masque
class Routeur:
def __init__(self, nom):
self.nom = nom
self.interfaces = []
self.table = {}
def branchement(self, reseau):
k = len(self.interfaces)
self.table[reseau.adresse] = (k, None, 0)
self.interfaces.append(Interface(reseau))Les vecteurs sont des couples (adresse réseau,distance). Initialement seules les destinations connectées sont présentes à distance 0. Une information voisine ajoute un réseau inconnu ou remplace une route strictement plus longue, selon Bellman-Ford. La table associe chaque destination à (indice interface,passerelle,distance). Une passerelle None indique une connexion directe. Si la table change, envoyer_vecteurs est appelé sur les autres interfaces.
routeur_m.table = {
(172,64,0,0): (1, None, 0),
(172,60,0,0): (0, (172,58,0,2), 1),
(192,168,47,0): (0, (172,58,0,2), 2),
(172,62,0,0): (0, (172,58,0,2), 2)
}
vecteurs = [((192,168,72,0),0), ((172,62,0,0),0),
((172,64,0,0),0), ((172,60,0,0),1),
((192,168,47,0),1)]Question 9
#Donner routeur_m.table après m_a_j((172,64,0,2),1,vecteurs).
Indice
Toute distance annoncée par le voisin doit augmenter de 1 avant comparaison.
Comprendre la correction
{
(172,64,0,0): (1, None, 0),
(172,60,0,0): (0, (172,58,0,2), 1),
(192,168,47,0): (0, (172,58,0,2), 2),
(172,62,0,0): (1, (172,64,0,2), 1),
(192,168,72,0): (1, (172,64,0,2), 1)
}Le réseau 192.168.72.0 est nouveau : on l’ajoute avec la distance annoncée 0 plus un saut vers le voisin. Pour 172.62.0.0, la nouvelle distance 1 améliore l’ancienne 2 : on remplace interface et passerelle. Le réseau directement connecté 172.64.0.0 garde sa distance 0. Les propositions vers 172.60.0.0 (coût 2 contre 1) et 192.168.47.0 (2 contre 2) n’améliorent rien, donc sont ignorées. Une égalité ne modifie pas le choix existant dans ce code.
def m_a_j(self, passerelle, i_interf, vecteurs):
a_ete_modif = False
for adresse, d in vecteurs:
if ... or ...:
self.table[adresse] = (i_interf, passerelle, d + 1)
a_ete_modif = ...
if a_ete_modif:
self.envoyer_vecteurs(i_interf)Question 10
#Compléter m_a_j aux lignes 4 et 6.
Indice
Comparez la distance candidate d+1 à la troisième composante de la route existante.
Comprendre la correction
if adresse not in self.table or d + 1 < self.table[adresse][2]:
self.table[adresse] = (i_interf, passerelle, d + 1)
a_ete_modif = TrueL’ordre des deux termes de OR protège l’accès à une clé absente : si l’adresse est nouvelle, le second terme n’est pas évalué. La composante d’indice 2 contient la distance. Le drapeau conserve True dès qu’une modification intervient, afin de n’envoyer qu’une annonce après avoir traité tous les vecteurs. Le modèle n’accepte que les améliorations ; il ne suffit pas à gérer le retrait ou la dégradation d’une route après panne.
def vecteurs(self, i):
res = []
for adresse in self.table:
i_interf, passerelle, dist = self.table[adresse]
if ...:
res.append(...)
return resQuestion 11
#Compléter vecteurs(i), en excluant les routes passant par l’interface de sortie i.
Indice
L’interface stockée dans la route est à comparer à l’interface d’envoi.
Comprendre la correction
if i_interf != i:
res.append((adresse, dist))La méthode ne renvoie que destination et distance, pas la passerelle ni l’indice local, qui n’auraient pas le même sens chez le voisin. L’exclusion correspond au principe de split horizon dans ce modèle : ne pas annoncer sur une interface une route que l’on emprunte par cette même interface. Cela réduit les retours d’informations inutiles, sans prouver à lui seul l’absence de toute boucle dans toutes les variantes de RIP.
def envoyer_vecteurs(self, i_interf):
for i in range(len(self.interfaces)):
if ...:
adresse = self.interfaces[i].adresse
v = self.vecteurs(...)
paquet = (adresse, v)
... .enfiler(paquet)Question 12
#Compléter envoyer_vecteurs(i_interf), qui envoie par toutes les autres interfaces.
Indice
L’annonce doit être déposée sur le réseau pour qu’il la diffuse à ses machines.
Comprendre la correction
def envoyer_vecteurs(self, i_interf):
for i in range(len(self.interfaces)):
if i != i_interf:
adresse = self.interfaces[i].adresse
v = self.vecteurs(i)
paquet = (adresse, v)
self.interfaces[i].sortie.enfiler(paquet)Le paramètre i_interf indique l’interface par laquelle l’information vient d’arriver ; cette sortie est ignorée pour l’annonce déclenchée. Pour chaque autre interface, vecteurs(i) applique son propre filtrage. L’origine du paquet est l’adresse de l’interface émettrice. On enfile dans sortie, la file du réseau, et non dans file, la file de réception de l’interface. Lorsque i_interf vaut -1, aucun indice valide n’est exclu.
def traitement(self):
for i in range(len(self.interfaces)):
file = self.interfaces[i].file
while not file.est_vide():
orig, donnees = file.defiler()
self.m_a_j(orig, i, donnees)Algorithme demandé : tous les routeurs annoncent avec -1 ; diffuser toutes les files réseau ; traiter tous les routeurs ; recommencer tant qu’un réseau contient encore un paquet. Les collections sont routeurs et reseaux.
Question 13
#Compléter le script simulant la construction des tables jusqu’à absence de paquets dans les réseaux.
Indice
Regardez les files réseau après le traitement, car de nouvelles annonces peuvent juste y être ajoutées.
Comprendre la correction
for routeur in routeurs:
routeur.envoyer_vecteurs(-1)
continuer = True
while continuer:
for reseau in reseaux:
reseau.diffuser()
for routeur in routeurs:
routeur.traitement()
continuer = False
for reseau in reseaux:
if not reseau.file.est_vide():
continuer = TrueUn tour distribue d’abord les annonces en attente des réseaux vers les interfaces, puis chaque routeur traite ses réceptions et peut produire de nouvelles annonces. La condition de poursuite est examinée après ces deux phases. Si tous les réseaux sont vides à ce moment, toutes les réceptions ont été traitées et plus aucune amélioration ne produit de paquet. Mettre continuer=False à la fin de chaque réseau sans conserver un True précédent pourrait arrêter à tort alors qu’un autre réseau contient encore des paquets.
Le modèle suppose une topologie fixe et ne traite que des distances nouvellement découvertes ou strictement améliorées, ce qui conduit à un état stable sur cet exemple fini. Un protocole réel comporte aussi annonces périodiques, temporisations et gestion des routes devenues inaccessibles ; ces mécanismes ne doivent pas être inventés dans ce script simplifié.
Une annonce du voisin mérite-t-elle une mise à jour ?Un atelier pour expérimenter
Choisissez la distance actuelle d’une route et celle annoncée par le voisin. Le simulateur applique le +1 et la règle d’amélioration stricte ; le mode réseau inconnu force une première insertion.
Lire le résultat de l’expérience initiale
Table modifiée, annonce à préparer
Le voisin est à un saut : sa distance annoncée augmente de 1. Une égalité ne remplace pas la route actuelle dans l’algorithme fourni.
| Réseau connu | Ancienne distance | Annonce voisine | Candidate | Décision |
|---|---|---|---|---|
| Oui | 2 | 0 | 1 | Insérer ou remplacer |
Une route est améliorée en comparant son coût complet via le voisin, pas la distance du voisin seule.
Du sujet à la méthode
Votre prochaine séance de révision
- Pour une structure imbriquée, donnez un sens à chaque niveau de clé ou d’indice.
- Expliquez les effets de bord : remplissage consomme le stock, entrainement change les poids, diffuser vide une file.
- Dans un simulateur réseau, distinguez file locale de réception, file du réseau et interface de sortie.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26-NSIJ1JA1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
