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
Une classe Date : bissextiles, différences de dates et timestamp
On crée une classe Date pour stocker un jour, un mois et une année sous forme d’entiers. Le tableau des longueurs des mois commence à janvier d’indice 0.
class Date:
def __init__(self, jour, mois, annee):
self.jour = ...
self.mois = ...
self.annee = ...
self.nb_jours_par_mois = [31,28,31,30,31,30,31,31,30,31,30,31]
def get_jour(self):
return self.jour
def get_mois(self):
return self.mois
def get_annee(self):
return ...
def set_jour(self, jour):
self.jour = jour
def set_mois(self, mois):
self.mois = ...
def set_annee(self, annee):
self.annee = annee
def est_bissextile(self):
...Partie A : accès et modification des données
Question 1
#Compléter les lignes 3 à 5 du constructeur pour affecter les trois paramètres aux attributs correspondants.
Comprendre la correction
self.jour = jour
self.mois = mois
self.annee = anneeÀ gauche, self désigne l’objet que l’on initialise ; à droite, on utilise les arguments reçus. Le paramètre et l’attribut peuvent avoir le même nom sans être la même variable.
Question 2
#À quelle date correspond d = Date(1, 5, 2000) ?
Comprendre la correction
Au 1 er mai 2000. L’ordre des arguments est jour, mois, année ; 5 désigne mai, pas le jour du mois.
Question 3
#Créer une instance d représentant le 19 juin 2024.
Comprendre la correction
d = Date(19, 6, 2024)Le mois est passé comme entier 6 ; on ne passe pas le texte « juin » au constructeur défini.
Question 4
#Compléter get_annee, qui renvoie l’attribut annee.
Comprendre la correction
def get_annee(self):
return self.anneeUn accesseur retourne la donnée sans modifier l’objet. Écrire print(self.annee) afficherait une valeur mais ne la renverrait pas au programme appelant.
Question 5
#Compléter set_mois, qui remplace le mois par la valeur reçue.
Comprendre la correction
def set_mois(self, mois):
self.mois = moisIl s’agit d’une modification de l’objet. Le sujet suppose des données valides ; cette méthode seule ne vérifie pas que le jour existe dans le nouveau mois.
Février a généralement 28 jours, mais 29 dans les années bissextiles. On dispose de est_bissextile, qui utilise uniquement l’année de l’objet.
self.nb_jours_par_mois = [31,28,31,30,31,30,31,31,30,31,30,31]
if ...:
self.nb_jours_par_mois[...] = 29Question 6
#Compléter les lignes 7 et 8 pour ajuster février à 29 jours si l’année est bissextile.
Comprendre la correction
if self.est_bissextile():
self.nb_jours_par_mois[1] = 29Février est le deuxième mois, donc l’élément d’indice 1. On appelle la méthode sur l’instance déjà initialisée, afin qu’elle lise sa valeur d’année.
La liste étant créée dans chaque constructeur, chaque objet possède ses propres longueurs de mois. En revanche, le set_annee imprimé ne les recalcule pas : après un changement d’année, une implémentation complète devrait remettre février à 28 ou 29 selon la nouvelle valeur.
Partie B : dans l’année courante
Question 7
#Écrire est_bissextile. Une année est divisible par 4 mais pas par 100, ou divisible par 400. Rappel : a % n == 0 teste la divisibilité.
Indice
Traitez séparément les multiples de 100, puis ceux de 400.
Comprendre la correction
def est_bissextile(self):
return (self.annee % 4 == 0 and self.annee % 100 != 0) or self.annee % 400 == 0Les parenthèses rendent les deux possibilités visibles. 2024 est divisible par 4 et pas par 100 : vrai. 1900 est divisible par 100 mais pas par 400 : faux. 2000 satisfait le second cas : vrai. Ces trois tests évitent la règle incomplète « divisible par 4 ».
def nb_jours_passes(self):
nb_jours = self.jour
mois = self.mois - 2
while mois >= 0:
nb_jours = nb_jours + self.nb_jours_par_mois[mois]
mois = mois - 1
return nb_joursQuestion 8
#Quel sera le résultat de d1 = Date(20, 3, 2001) puis d1.nb_jours_passes() ?
Comprendre la correction
79 : 20 jours de mars + 28 de février + 31 de janvier. 2001 n’est pas bissextile. La méthode inclut le jour courant : le 1 er janvier renverrait 1, pas 0.
def nb_jours_restants(self):
j = 365
if ...:
j = 366
return j - ...Question 9
#Compléter nb_jours_restants, qui renvoie 365 ou 366 moins le nombre de jours déjà passés.
Comprendre la correction
def nb_jours_restants(self):
j = 365
if self.est_bissextile():
j = 366
return j - self.nb_jours_passes()Le nombre total dépend de l’année bissextile. Comme les jours passés incluent aujourd’hui, les jours restants commencent demain : le 31 décembre donne 0. Ces deux méthodes utilisent donc des conventions complémentaires.
Partie C : entre deux dates
nb_jours_depuis(other) doit renvoyer le nombre de jours depuis other jusqu’à l’instance courante, ou -1 si other est postérieure. Le code du sujet est :
def nb_jours_depuis(self, other):
if other.get_annee() > self.get_annee():
return -1
if other.get_annee() == self.get_annee():
if other.nb_jours_passes() > self.nb_jours_passes():
return -1
if other.nb_jours_passes() == self.nb_jours_passes():
return 0
nb_jours = self.nb_jours_passes() + other.nb_jours_restants()
for annee in range(other.get_annee() + 1, self.get_annee()):
d_suivant = date(1, 1, annee)
if d_suivant.est_bissextile():
nb_jours += 366
else:
nb_jours += 365
return nb_joursd1 = Date(15, 6, 2024)
d2 = Date(15, 6, 2024)
d3 = Date(15, 7, 2024)
d4 = Date(15, 6, 2025)
d5 = Date(15, 6, 2022)Question 10
#Avec les instances fournies, donner les résultats de d1.nb_jours_depuis(d2), d1.nb_jours_depuis(d3), d1.nb_jours_depuis(d4) et d1.nb_jours_depuis(d5). 2024 est bissextile.
Indice
Repérez quels retours arrivent avant l’appel de la classe mal orthographiée.
Comprendre la correction
| Appel | Résultat visé | Pourquoi |
|---|---|---|
| d1 depuis d2 | 0 | Dates égales |
| d1 depuis d3 | -1 | Le 15 juillet 2024 est après le 15 juin 2024 |
| d1 depuis d4 | -1 | L’année 2025 est après 2024 |
| d1 depuis d5 | 731 | 199 jours après le 15 juin 2022 + 365 jours de 2023 + 167 jours de 2024 |
Attention au code réellement imprimé : la quatrième instruction appelle date(1, 1, annee) alors que la classe définie est Date. Sans autre définition, elle produit donc une NameError. Le résultat 731 correspond à la correction de cette majuscule ; les trois premiers retours arrivent avant cette ligne.
Une autre limite apparaît pour deux dates différentes d’une même année, si other est antérieure : le code additionne à tort le début et la fin de l’année. Pour un objet pleinement fonctionnel, voici la méthode corrigée, distincte du programme fourni :
def nb_jours_depuis(self, other):
if other.get_annee() > self.get_annee():
return -1
if other.get_annee() == self.get_annee():
difference = self.nb_jours_passes() - other.nb_jours_passes()
return difference if difference >= 0 else -1
nb_jours = self.nb_jours_passes() + other.nb_jours_restants()
for annee in range(other.get_annee() + 1, self.get_annee()):
d_suivant = Date(1, 1, annee)
nb_jours += 366 if d_suivant.est_bissextile() else 365
return nb_joursDans le cas d’années différentes, on additionne la fin de la première année, les années complètes intermédiaires et le début de la dernière. Dans la même année, une simple différence de numéros de jour suffit.
Le sujet appelle timestamp le nombre de secondes depuis le 1 er janvier 1970 à 00:00 et donne pour exemple 1704063600 pour le 01/01/2024 à 00:00.
def timestamp(self):
d = ...
return self.nb_jours_depuis(d) * 24 * 3600Question 11
#Compléter la création de d dans timestamp, qui renvoie le nombre de secondes depuis le 1 er janvier 1970.
Comprendre la correction
def timestamp(self):
d = Date(1, 1, 1970)
return self.nb_jours_depuis(d) * 24 * 3600La référence est une autre instance de Date. On convertit les jours en secondes avec 24 × 3600 = 86400. Il faut la méthode nb_jours_depuis corrigée ci-dessus pour un fonctionnement général.
Le 1 er janvier 1970 est une origine conventionnelle de temps Unix, pas la date de démarrage de tous les systèmes Unix. La classe ne gère ni fuseau ni heure d’été. Pour le 1 er janvier 2024 à minuit UTC, ce calcul donne 1704067200. Le nombre 1704063600 imprimé dans le sujet correspond à une heure plus tôt en UTC, donc à minuit dans un fuseau UTC+1 ; sans précision de fuseau, les deux notions ne doivent pas être confondues.
Le jour de plus qui change les calculsUn atelier pour expérimenter
Changez l’année, le mois et le jour. Observez le rang dans l’année et les jours restant après la date. Février réagit aux exceptions de siècle.
Lire le résultat de l’expérience initiale
20/3/2024 : jour 80 de l’année
Année bissextile : février compte 29 jours.
| Mois | Jours dans le mois | Contribution au cumul |
|---|---|---|
| 1 | 31 | 31 |
| 2 | 29 | 29 |
| 3 | 31 | 20 |
| 4 | 30 | 0 |
| 5 | 31 | 0 |
| 6 | 30 | 0 |
| 7 | 31 | 0 |
| 8 | 31 | 0 |
| 9 | 30 | 0 |
| 10 | 31 | 0 |
| 11 | 30 | 0 |
| 12 | 31 | 0 |
La divisibilité par 4 seule ne suffit pas. Les conventions « jour inclus » et « jours restant après » doivent rester cohérentes pour que les différences de dates soient justes.
Exercice 2 · 6 points
Ordonnancement à files multiples et prévention de la famine
On souhaite gérer l’ordre d’exécution des processus sur un processeur. Une valeur numérique plus petite donne une priorité plus élevée : passer de 2 à 3 rend moins prioritaire. Une liste contient une file par valeur, de 0 vers les priorités décroissantes.
À l’arrivée, un nouveau processus reçoit la valeur 0 et entre dans la première file. À chaque cycle : si le CPU est libre, élire le premier processus de la file non vide la plus prioritaire ; s’il contient un processus terminé, le retirer ; sinon augmenter son temps d’utilisation d’une unité. S’il existe un processus en attente de priorité supérieure ou égale, retirer le processus courant, diminuer sa priorité d’un cran, le remettre dans la file correspondante et élire le meilleur disponible. Sinon, diminuer sa priorité d’un cran et le laisser s’exécuter.
Question 1
#Donner le nom du programme qui gère l’ordre d’exécution des processus.
Comprendre la correction
Il s’agit de l’ordonnanceur, composant du système d’exploitation chargé de choisir quel processus prêt obtient le processeur.
Question 2
#Donner les différents états possibles d’un processus.
Comprendre la correction
Les états centraux sont prêt (il peut s’exécuter mais attend le CPU), élu ou en exécution (il utilise le CPU), et bloqué ou en attente (il attend un événement, par exemple une entrée-sortie). Selon le diagramme de cycle de vie utilisé, on ajoute nouveau et terminé.
Attendre son tour dans une file de l’ordonnanceur correspond à prêt, pas à bloqué : le processus ne manque alors que du processeur.
Question 3
#Choisir la structure la plus adaptée aux processus d’une même priorité : liste, file ou pile.
Comprendre la correction
La file (proposition 2) assure le premier arrivé, premier servi. Une pile favoriserait le dernier arrivé et pourrait faire attendre indéfiniment les plus anciens. Une liste Python peut implémenter cette file, mais le comportement abstrait recherché est FIFO.
class Processus:
...(self, ..., priorite, temps_CPU):
...priorite = priorite
...PID = ...
self.temps_utilisation = 0
self.temps_CPU = temps_CPUQuestion 4
#Compléter le constructeur de Processus, qui stocke PID, priorite, temps_utilisation et temps_CPU.
Comprendre la correction
class Processus:
def __init__(self, PID, priorite, temps_CPU):
self.priorite = priorite
self.PID = PID
self.temps_utilisation = 0
self.temps_CPU = temps_CPUtemps_utilisation commence à 0 tandis que temps_CPU désigne la quantité totale de CPU nécessaire. La priorité reçue est stockée telle quelle ; le programme d’arrivée lui passe 0.
P1 = Processus(PID=1, priorite=0, temps_CPU=10)
P2 = Processus(PID=2, priorite=0, temps_CPU=7)
P3 = Processus(PID=3, priorite=0, temps_CPU=5)Question 5
#Compléter la simulation jusqu’au cycle 5. Au départ liste_files = [[P3, P2, P1], [], []] ; cycle 1 : CPU=P1 et [[P3,P2],[],[]] ; cycle 2 : CPU=P2 et [[P3],[P1],[]].
Indice
La tête de file se trouve à droite dans les états donnés.
Comprendre la correction
| Cycle | CPU après gestion | Files, tête à droite | Raison |
|---|---|---|---|
| 3 | P3 | [[], [P2, P1], []] | P2 consomme une unité puis cède à P3 de priorité 0. |
| 4 | P3 | [[], [P2, P1], []] | P3, priorité 0 au test, est plus prioritaire que les attentes de valeur 1. Il reste élu et passe à 1. |
| 5 | P1 | [[], [P2], [P3]] | P3, valeur 1 au test, doit céder ; il passe à 2. P1 est le plus ancien de la file 1. |
Les listes du sujet sont écrites avec la tête de file à droite, comme le montre le retrait de P1 au cycle 1. Le test des attentes compare à la priorité de p avant de la diminuer. Au cycle 4, la troisième file demeure vide : les points de suspension du modèle n’imposent pas qu’elle contienne un processus.
Question 6
#Expliquer pourquoi un processus nécessitant 1000 unités de CPU risque de ne jamais terminer face à de nombreux processus de 4 unités, chaque court processus terminé étant remplacé par un nouveau. Préciser l’évolution de la priorité du processus long.
Comprendre la correction
À mesure qu’il consomme du CPU, le processus long voit sa valeur numérique de priorité augmenter : il devient de moins en moins prioritaire. Les nouveaux processus courts arrivent toujours avec la valeur 0. S’ils alimentent continuellement les files prioritaires, le processus long peut rester dans une file de faible priorité sans être réélu.
C’est une famine : le système continue à travailler, mais un processus ne reçoit pas assez de CPU pour terminer. Il ne faut pas confondre ce phénomène avec un interblocage, où plusieurs processus s’attendent mutuellement.
Question 7
#On ajoute un compteur temps_d_attente et un seuil Max_Temps. Le compteur augmente à chaque cycle d’attente ; au-delà du seuil, la priorité remonte. Pourquoi ce mécanisme évite-t-il la famine du processus long ?
Comprendre la correction
Le vieillissement rétablit progressivement la priorité d’un processus qui attend. Sa valeur numérique diminue jusqu’à rejoindre les files servies ; dans une même priorité, l’ordre FIFO empêche les nouveaux arrivants de passer systématiquement devant lui. Il reçoit de nouvelles tranches de CPU et peut accumuler les 1000 unités nécessaires.
Cette conclusion suppose que la remontée déplace effectivement le processus dans la bonne file et que le compteur est géré de manière cohérente. Un simple attribut qui augmente sans modifier l’ordonnancement ne suffit pas.
Question 8
#Écrire meilleur_priorite(liste_files), qui renvoie None si toutes les files sont vides, sinon la valeur de priorité la plus élevée. Pour [[], [p2], [p3,p1]], elle doit renvoyer 1.
Comprendre la correction
def meilleur_priorite(liste_files):
for priorite in range(len(liste_files)):
if len(liste_files[priorite]) > 0:
return priorite
return NoneOn explore les indices dans l’ordre croissant : le premier indice non vide est la plus petite valeur, donc la meilleure priorité. Le retour None vient après la boucle pour ne pas s’arrêter dès la première file vide. Une liste de files vide est traitée naturellement.
Question 9
#Écrire prioritaire, qui renvoie et retire le premier processus parmi les plus prioritaires, ou None si tout est vide. On peut utiliser liste.pop(i).
Comprendre la correction
def prioritaire(liste_files):
priorite = meilleur_priorite(liste_files)
if priorite is None:
return None
return liste_files[priorite].pop()La fonction précédente donne la file à utiliser. La tête est à droite dans la représentation de la simulation : pop(), équivalent à pop(-1), enlève donc le bon processus. Le cas None doit être testé avant d’indexer la liste.
Question 10
#Écrire gerer(p, liste_files), qui récupère le processus élu et les files d’attente, et implémente le programme donné au début de l’exercice.
PythonFaire céder le processeur au bon momentÉcrivez votre solution et mettez-la à l’épreuve
Écrivez gerer(p, liste_files), qui renvoie le prochain processus élu. Si p vaut None, élisez le prioritaire. Si p a déjà terminé, renvoyez None sans élire immédiatement son successeur. Sinon, exécutez une unité, comparez la meilleure priorité en attente à l’ancienne priorité de p, puis augmentez la priorité numérique de p de 1. S’il doit céder (priorité en attente inférieure ou égale), replacez-le à gauche de sa nouvelle file et élisez le prioritaire. Les files sortent par la droite. Créez les files manquantes si nécessaire. Aucun vieillissement global n’est ajouté.
def gerer(p, liste_files):
# À vous de jouer
passLes cas de test proposés :
- CPU libre : élire sans exécuter : L’élection consomme la tête de file mais ne réalise pas encore d’unité de calcul.
- Un processus déjà terminé : La fin libère le processeur ; l’élection suivante aura lieu au prochain appel.
- Aucune attente : Le processus continue, mais son temps et sa priorité numérique évoluent.
- Comparer avant de dégrader la priorité : B est moins prioritaire que l’ancienne priorité 0 de A. Comparer après incrément ferait céder A trop tôt.
- À égalité, céder et créer la nouvelle file : Le processus exécuté rejoint la priorité 1, même si cette file n’existait pas encore.
- Respecter FIFO dans la file de retour : Insérer A à gauche conserve C à droite, donc C sera servi avant A.
Indice 1
Traitez d’abord CPU vide et processus terminé.
Indice 2
Comparez les priorités avant de dégrader celle du processus courant.
Comprendre la correction
def gerer(p, liste_files):
if p is None:
return prioritaire(liste_files)
if p.temps_utilisation >= p.temps_CPU:
return None
p.temps_utilisation += 1
attente = meilleur_priorite(liste_files)
doit_ceder = attente is not None and attente <= p.priorite
p.priorite += 1
if doit_ceder:
while len(liste_files) <= p.priorite:
liste_files.append([])
liste_files[p.priorite].insert(0, p)
return prioritaire(liste_files)
return pLa fonction renvoie le nouveau processus élu, ou None si le CPU devient libre. Elle respecte la succession du texte : un cycle qui constate la fin retire le processus ; l’élection suivante interviendra au prochain appel. Lors d’un travail réel, elle mémorise la nécessité de céder avant d’augmenter la valeur de priorité.
L’insertion à gauche, insert(0, p), place le nouvel arrivant derrière les éléments qui sortiront à droite. Si la priorité atteint un indice encore absent, on ajoute des files vides : sans cela, un processus long finirait par provoquer une erreur d’indice. Le vieillissement de Q7 n’est pas demandé dans cette fonction et n’est pas ajouté implicitement.
Le CPU change de main, les priorités changent de sensUn atelier pour expérimenter
Avancez cycle par cycle dans la simulation des trois processus. La priorité numérique augmente quand le processus devient moins prioritaire ; les files sont lues de droite à gauche comme dans le sujet.
Lire le résultat de l’expérience initiale
CPU = P1, valeur de priorité 1
Le temps est incrémenté seulement dans la branche « processus en cours et non terminé ». Un cycle d’élection ou de retrait n’ajoute pas d’unité de CPU dans le pseudo-algorithme du sujet.
| Cycle | CPU | Valeur priorité | Files, tête à droite | Action |
|---|---|---|---|---|
| 1 | P1 | 0 | 0: [P3, P2] | Élection sans consommation dans ce cycle |
| 2 | P2 | 0 | 0: [P3] ; 1: [P1] | P1 cède le CPU |
| 3 | P3 | 0 | 1: [P2, P1] | P2 cède le CPU |
| 4 | P3 | 1 | 1: [P2, P1] | Le processus conserve le CPU |
| 5 | P1 | 1 | 1: [P2] ; 2: [P3] | P3 cède le CPU |
Le sens numérique de la priorité, le côté de sortie de la file et le moment où l’on teste chaque condition font partie de l’algorithme. Les changer modifie la simulation.
Revoir les notions de cet exercice
Exercice 3 · 8 points
Base hospitalière, réduction d’arbres et contrôle modulo 97
Cet exercice comporte trois parties indépendantes. Le contexte hospitalier sert à étudier des données et un arbre de décision fictif : les résultats de cet arbre sont ceux du modèle scolaire, sans validation clinique.
Partie A : base de données
| Table | Attributs et clés |
|---|---|
| Patient | nom_patient TEXT (clé primaire), prenom TEXT, numero_secu INT, age INT |
| Symptome | nom_patient TEXT (clé primaire et clé étrangère vers Patient.nom_patient), toux TEXT, fievre TEXT, nausee TEXT, anosmie TEXT |
| Maladie | nom_maladie TEXT (clé primaire) ; autres attributs inutiles à cet exercice |
| Diagnostic | nom_patient TEXT (clé primaire et clé étrangère vers Patient.nom_patient), nom_maladie TEXT (clé étrangère vers Maladie.nom_maladie) |
| Patient.nom_patient | prenom | numero_secu | age |
|---|---|---|---|
| Heartman | Alice | 207053523800187 | 17 |
| Douglas | Bob | 100017500155572 | 24 |
| Woods | Caroll | 258125930610747 | 65 |
| Symptome.nom_patient | toux | fievre | nausee | anosmie |
|---|---|---|---|---|
| Heartman | Oui | Non | Non | Oui |
| Douglas | Non | Oui | Oui | Non |
| Woods | Oui | Oui | Non | Non |
| Maladie.nom_maladie |
|---|
| Covid-19 |
| Gastroentérite |
Les lignes de Diagnostic sont laissées non renseignées dans le sujet. On peut utiliser les mots SQL AND, FROM, INSERT, INTO, JOIN, ON, SELECT, SET, UPDATE, VALUES, WHERE et la fonction COUNT.
Question 1
#Écrire une requête donnant les noms et prénoms des patients ayant strictement plus de 60 ans.
Comprendre la correction
SELECT nom_patient, prenom
FROM Patient
WHERE age > 60;Le filtre exclut une personne de 60 ans exactement. Dans l’extrait, la seule ligne renvoyée est Woods, Caroll, âgée de 65 ans.
Question 2
#Alice Heartman ne tousse plus. Écrire la mise à jour correspondante.
Comprendre la correction
UPDATE Symptome
SET toux = 'Non'
WHERE nom_patient = 'Heartman';Le symptôme est stocké dans Symptome et non dans Patient. La colonne contient les chaînes Oui et Non ; on conserve ce codage au lieu d’y introduire le booléen Python False.
Question 3
#Écrire une requête comptant les patients diagnostiqués Covid-19 qui toussent.
SQLCompter les patients qui toussent avec le diagnostic demandéÉcrivez votre solution et mettez-la à l’épreuve
Comptez les patients dont le diagnostic est Covid-19 et dont le champ toux vaut Oui. Les données servent uniquement à travailler les relations SQL du sujet.
SELECT ...
FROM ...
WHERE ...;Les cas de test proposés :
- Patients officiels, diagnostics pédagogiques :
Patient,Symptomeet les noms de maladies viennent du sujet. Sa tableDiagnosticest vide dans l’extrait : les trois diagnostics de ce test sont ajoutés explicitement pour vérifier la requête, sans portée médicale. - Cas complémentaire : chaque filtre compte : Jeu complémentaire pédagogique, distinct des données officielles. Deux lignes satisfont les deux conditions, une seule la maladie, une seule la toux. Le total doit compter uniquement leur intersection.
Comprendre la correction
SELECT COUNT(*)
FROM Diagnostic JOIN Symptome
ON Diagnostic.nom_patient = Symptome.nom_patient
WHERE Diagnostic.nom_maladie = 'Covid-19'
AND Symptome.toux = 'Oui';Le diagnostic et le symptôme appartiennent à deux tables : la jointure les associe par le patient. Puis les deux filtres doivent être vrais. Comme chaque table admet au plus une ligne par patient selon ses clés, la jointure ne multiplie pas ici les lignes d’un même patient.
On ne peut pas donner un résultat numérique à partir du document : la table Diagnostic n’y est pas remplie.
INSERT INTO Patients VALUES ('Douglas', 'Patrick', 168077230253829, 55);Question 4
#Pourquoi l’insertion proposée produit-elle une erreur ?
Comprendre la correction
INSERT INTO Patients VALUES ('Douglas', 'Patrick', 168077230253829, 55);Le problème de modèle visé est le doublon de clé primaire : Douglas existe déjà pour Bob, donc une seconde ligne de même nom_patient ne peut pas être insérée.
À la lettre, la requête emploie aussi Patients au pluriel, alors que la table du schéma est Patient. Sans table supplémentaire, cette différence produit d’abord une erreur de table inconnue. Corriger le nom en Patient laisse subsister le conflit de clé, qui motive la question suivante.
Question 5
#Proposer une modification du schéma relationnel qui résout ce problème.
Comprendre la correction
Ajouter un identifiant unique id_patient à Patient et en faire la clé primaire. Le nom devient un attribut ordinaire, autorisant les homonymes. Les tables Symptome et Diagnostic doivent alors remplacer leurs références par id_patient, vers la nouvelle clé.
| Table | Partie du schéma modifiée |
|---|---|
| Patient | id_patient (PK), nom_patient, prenom, numero_secu, age |
| Symptome | id_patient (PK et FK vers Patient), toux, fievre, nausee, anosmie |
| Diagnostic | id_patient (PK et FK vers Patient), nom_maladie (FK vers Maladie) |
Le numéro de sécurité sociale pourrait être envisagé si son unicité, sa présence et sa validité sont imposées. Un identifiant interne évite de dépendre de ces hypothèses externes.
Partie B : arbre de décision binaire
Un nœud interne porte un symptôme, une feuille un résultat + ou -. Si le symptôme est présent, on descend à droite ; sinon à gauche. Le sujet donne comme exemple un patient sans toux ni anosmie, avec fièvre et nausées : le chemin non, oui, oui, non aboutit à -.
Lire les connexions du schéma
- anosmie relié à - : Non
- anosmie relié à + : Oui
- anosmie relié à - : Non
- anosmie relié à - : Oui
- nausee relié à anosmie : Non
- nausee relié à anosmie : Oui
- anosmie relié à + : Non
- anosmie relié à + : Oui
- anosmie relié à - : Non
- anosmie relié à - : Oui
- nausee relié à anosmie : Non
- nausee relié à anosmie : Oui
- fievre relié à nausee : Non
- fievre relié à nausee : Oui
- anosmie relié à + : Non
- anosmie relié à + : Oui
- anosmie relié à - : Non
- anosmie relié à + : Oui
- nausee relié à anosmie : Non
- nausee relié à anosmie : Oui
- anosmie relié à + : Non
- anosmie relié à + : Oui
- anosmie relié à + : Non
- anosmie relié à + : Oui
- nausee relié à anosmie : Non
- nausee relié à anosmie : Oui
- fievre relié à nausee : Non
- fievre relié à nausee : Oui
- toux relié à fievre : Non
- toux relié à fievre : Oui
Question 6
#D’après l’arbre de la figure 1, donner le diagnostic pour un patient qui tousse et a de la fièvre, mais sans nausée ni anosmie.
Comprendre la correction
Le résultat du modèle est positif (+). On suit oui à toux, oui à fièvre, non à nausée et non à anosmie. La feuille correspondante est étiquetée +.
class Noeud:
def __init__(self, valeur, gauche=None, droit=None):
self.valeur = valeur
self.gauche = gauche
self.droit = droit
def est_feuille(self):
return self.gauche == None and self.droit == None
def symptome(self):
assert not self.est_feuille()
return self.valeur
def diagnostic(self):
assert self.est_feuille()
return self.valeurQuestion 7
#Préciser la signification de l’assertion de la méthode symptome.
Comprendre la correction
assert not self.est_feuille() exige un nœud interne. Une feuille contient un diagnostic, pas un symptôme à consulter. Avec les assertions actives, appeler cette méthode sur une feuille lève une AssertionError au lieu d’interpréter le résultat comme une question.
Question 8
#Nommer un attribut et une méthode de Noeud.
Comprendre la correction
valeur est un attribut. est_feuille est une méthode. Les attributs gauche et droit, ou les méthodes symptome et diagnostic, sont d’autres réponses possibles.
Les symptômes sont un dictionnaire de booléens. Exemple :
patient = {'toux': True, 'fievre': True, 'nausee': False, 'anosmie': False}def applique(arbre, patient):
if arbre.est_feuille():
...
else:
if patient[arbre.symptome()]:
...
else:
...Question 9
#Compléter récursivement applique(arbre, patient), qui renvoie le diagnostic du modèle.
Indice 1
Le cas de base utilise diagnostic, pas symptome.
Indice 2
La convention associe True au fils droit.
Comprendre la correction
def applique(arbre, patient):
if arbre.est_feuille():
return arbre.diagnostic()
else:
if patient[arbre.symptome()]:
return applique(arbre.droit, patient)
else:
return applique(arbre.gauche, patient)Le cas d’arrêt est une feuille : son résultat est déjà disponible. Sinon, le dictionnaire dit si le symptôme du nœud est présent. La branche vraie descend à droit, la branche fausse à gauche, conformément à la convention du sujet. Chaque appel doit être retourné pour que le résultat remonte jusqu’au premier appel.
Question 10
#Donner la taille de l’arbre de la figure 1. Une feuille seule compte pour 1.
Comprendre la correction
Il comporte 31 nœuds : 1 + 2 + 4 + 8 + 16, des niveaux de la racine aux feuilles. Les 16 résultats doivent être comptés ; ne donner que les 15 nœuds de symptômes oublierait les feuilles.
Règle de la figure 2 : un nœud dont les deux enfants sont des feuilles portant le même diagnostic est remplacé par une feuille portant ce diagnostic.
Lire les connexions du schéma
- Question relié à + : Non
- Question relié à + : Oui
Question 11
#Appliquer la règle de réduction à la figure 1 et dessiner le nouvel arbre.
Comprendre la correction
Lire les connexions du schéma
- anosmie relié à - : Non
- anosmie relié à + : Oui
- nausee relié à anosmie : Non
- nausee relié à - : Oui
- nausee relié à + : Non
- nausee relié à - : Oui
- fievre relié à nausee : Non
- fievre relié à nausee : Oui
- anosmie relié à - : Non
- anosmie relié à + : Oui
- nausee relié à + : Non
- nausee relié à anosmie : Oui
- fievre relié à nausee : Non
- fievre relié à + : Oui
- toux relié à fievre : Non
- toux relié à fievre : Oui
On commence par les nœuds anosmie dont les deux feuilles sont identiques. Six de ces huit nœuds disparaissent, chacun remplacé par une feuille, soit 12 nœuds retirés. Dans la branche toux oui et fièvre oui, les deux nouveaux résultats sont tous deux + : le nœud nausée disparaît à son tour avec deux nœuds supplémentaires.
La taille passe ainsi de 31 à 17, sans changer aucun des 16 résultats possibles. Il faut recommencer vers le haut après les premières réductions, car elles peuvent rendre un parent réductible.
def reduire(self):
if self.est_feuille():
return
self.gauche.reduire()
self....
if self.gauche.est_feuille() and ... \
and ... == ...:
self.valeur = ...
self.gauche = ...
self.droite = ...Question 12
#Compléter la méthode reduire, qui applique cette règle récursivement sans changer les décisions.
Indice
Une simplification des enfants peut rendre le parent simplifiable.
Comprendre la correction
def reduire(self):
if self.est_feuille():
return
self.gauche.reduire()
self.droit.reduire()
if (self.gauche.est_feuille() and self.droit.est_feuille()
and self.gauche.diagnostic() == self.droit.diagnostic()):
self.valeur = self.gauche.diagnostic()
self.gauche = None
self.droit = NoneOn réduit d’abord les deux sous-arbres, puis on inspecte le nœud courant : c’est une logique de parcours postfixe. Tester les deux enfants avant de les avoir réduits manquerait des simplifications devenues possibles plus bas.
Le sujet imprime self.droite dans sa dernière ligne, mais le constructeur crée self.droit. Il faut conserver droit partout. Lors de la réduction, on copie la valeur avant de supprimer les références aux enfants, sinon on perdrait l’accès au diagnostic.
Partie C : contrôle d’intégrité
Le sujet représente le numéro de sécurité sociale par 15 chiffres : les 13 premiers forment n, les deux derniers la clé k. On impose que n + k soit multiple de 97. Exemple :
2070535238001 + 87 = 97 × 21345724104% donne le reste et // le quotient entier : 13 % 3 = 1 et 13 // 3 = 4.
def verifie(num_secu):
n = num_secu // 100
k = num_secu % 100
return ...Question 13
#Compléter verifie(num_secu), qui indique si la somme des treize premiers chiffres n et des deux derniers chiffres k est multiple de 97.
Comprendre la correction
def verifie(num_secu):
n = num_secu // 100
k = num_secu % 100
return (n + k) % 97 == 0La division entière supprime les deux derniers chiffres ; le reste modulo 100 les extrait. Le test final porte ensuite sur la somme, pas sur le numéro complet. Il vérifie la relation arithmétique demandée, sans garantir qu’un numéro existe ou correspond à une personne.
Question 14
#Écrire cle(n), qui renvoie la clé à partir des treize premiers chiffres.
Comprendre la correction
def cle(n):
return 97 - n % 97Si n a pour reste r modulo 97, ajouter 97 - r atteint le multiple suivant. La clé est comprise entre 1 et 97 ; quand r vaut 0, elle vaut 97.
La seule équation énoncée admettrait aussi 0 lorsque n est déjà multiple de 97. La formule ci-dessus choisit la convention usuelle de clé 1 à 97. Ne pas confondre cette clé de contrôle, calculable par tous, avec une clé secrète de chiffrement.
Simplifier un arbre sans changer sa réponseUn atelier pour expérimenter
Choisissez les quatre booléens du modèle scolaire et comparez les parcours dans l’arbre complet et l’arbre réduit. Les questions supprimées ne changeaient aucune décision.
Lire le résultat de l’expérience initiale
Résultat du modèle : + dans les deux arbres
Il s’agit uniquement du modèle fictif de l’exercice. Une réduction remplace une question devenue inutile par le résultat commun à ses deux branches.
| Étape | Arbre complet | Arbre réduit |
|---|---|---|
| 1 | toux : Oui | toux : Oui |
| 2 | fievre : Oui | fievre : Oui |
| 3 | nausee : Non | Résultat déjà atteint |
| 4 | anosmie : Non | Résultat déjà atteint |
Réduire depuis les feuilles vers la racine permet de propager les simplifications. La preuve de correction repose sur l’égalité des réponses des deux branches supprimées.
Revoir les notions de cet exercice
Du sujet à la méthode
Votre prochaine séance de révision
- Lisez les conventions avant tout calcul : ici, les jours passés incluent aujourd’hui et la tête de file est dessinée à droite.
- Une simulation d’ordonnanceur doit noter séparément priorité, temps consommé et processus élu.
- Sur une récursion qui modifie l’arbre, sauvegardez la valeur utile avant de couper les références aux enfants.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 25-NSIJ2JA1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
