Adloun

Corrigé bac NSI 2026 Métropole jour 1 — Exercice 3 : Plateforme de débats : arbre des arguments en POO et base de données SQL

Sujet officiel du baccalauréat, spécialité numérique et sciences informatiques, session 2026. Corrigé rédigé par Ibrahim Alame.

Travailler ce sujet sur Adloun Sujet officiel (PDF) Corrigé complet (PDF)

Énoncé

Cet exercice porte sur les arbres, l'algorithmique des arbres, et les bases de données.

On s'intéresse au fonctionnement interne d'une plateforme en ligne dédiée spécifiquement aux débats. Plutôt que de lister des messages des utilisateurs dans l'ordre chronologique, les différentes contributions sont structurées de façon arborescente afin de mieux comprendre les liens entre les arguments. Chaque contribution vient se rattacher à une affirmation, soit afin d'appuyer cette affirmation avec un argument pour, soit afin de l'attaquer avec un argument contre.

Cet exercice contient deux parties indépendantes l'une de l'autre.

Partie A : structure arborescente des arguments

Dans cette partie on modélise un arbre de débat pour un sujet de débat. La figure 1 montre un exemple d'arbre de débat.

Figure 1. Arbre de débat sur l'objectivité de la science

Dans un arbre de débat, chaque nœud est une affirmation, et il y en a de trois sortes :

De plus, chaque affirmation peut être likée (j'aime) ou dislikée (je n'aime pas) par les utilisateurs.

1. Indiquer le nom donné au nœud « point de départ du débat » dans le vocabulaire des arbres.

2. Indiquer en justifiant si un arbre de débat est un arbre binaire ou non.

On modélise la structure d'arbre de débat à l'aide de la classe suivante :


class Affirmation:
    def __init__(self, phrase):
        """
        Création d'un objet Affirmation à partir d'une
        phrase (str).
        """
        self.contenu = phrase
        self.sorte = "" # vaudra "sujet", "pour" ou "contre"
                        # vaut "" avant ajout dans l'arbre
        self.arguments = []
        self.nb_likes = 0
        self.nb_dislikes = 0

3. Donner le nom et le type de chaque attribut de la classe Affirmation.

On souhaite ajouter à cette classe une méthode soutenir qui permet d'ajouter un argument pour à l'affirmation.

On suppose à disposition une méthode contrer similaire qui permet d'ajouter un argument contre à l'affirmation.


def soutenir(self, argument):
    """
    Ajoute l'Affirmation `argument` comme argument pour de
    l'Affirmation `self`.

    Précondition : l'Affirmation ne doit pas avoir déjà été
    utilisée, ni comme sujet, ni comme pour, ni comme
    contre, ce qu'on vérifie grâce à son attribut `sorte`.
    """
    assert argument.sorte == ...
    ... .append(argument)
    argument.sorte = "..."

4. Recopier et compléter les lignes 10 à 12 de la méthode soutenir, en respectant sa documentation.

Pour mesurer les débats les plus vifs, la plateforme de débats dispose de plusieurs mesures.

5. Écrire la méthode nb_contre qui permet d'obtenir le nombre d'arguments contre directement rattachés à cette Affirmation.

On s'intéresse à la méthode mystere suivante, qui utilise la méthode nb_contre :


def mystere(self):
    m = self.nb_contre()
    for arg in self.arguments:
        candidat = arg.mystere()
        if candidat > m:
            m = candidat
    return m

6. Expliquer par une phrase ce que renvoie la méthode mystere.

Afin de trancher le débat dans sa globalité, on souhaite évaluer une Affirmation, en tenant compte de la totalité des likes et des dislikes de chaque argument, et en tenant compte du fait que ces arguments soient pour ou contre. Si l'évaluation est positive le pour l'emporte, si elle est négative le contre l'emporte.

La formule pour l'évaluation d'une affirmation correspond à la somme de ses likes et des évaluations de ses arguments pour, à laquelle on soustrait ses dislikes et les évaluations de ses arguments contre.

7. Écrire, en utilisant uniquement des lignes de code choisies parmi les lignes suivantes, une méthode evaluation qui renvoie le résultat du calcul décrit dans la formule ci-dessus. Les lignes ne sont pas toutes à utiliser et l'indentation est à adapter.


def evaluation(self):
def evaluation():
total = self.nb_likes + self.nb_dislikes
total = self.nb_likes - self.nb_dislikes
total = self.nb_dislikes - self.nb_likes
total = self.nb_likes * self.nb_dislikes
for arg in self.arguments:
for arg in self.arguments_pour:
for arg in self.arguments_contre:
if arg.sorte == "sujet":
if arg.sorte == "pour":
if arg.sorte == "contre":
else:
total = arg.evaluation()
total = total + arg.evaluation()
total = total - arg.evaluation()
total = total * arg.evaluation()
return total
return 1 + total
Partie B : base de données des utilisateurs et de leurs contributions

Dans cette partie, on pourra utiliser les clauses du langage SQL pour :

Pour manipuler toutes les informations sur plusieurs sujets de débats, la plateforme dispose d'un Système de Gestion de Bases de Données (SGBD).

8. Parmi les propositions suivantes, indiquer celles qui font partie des rôles d'un SGBD :

Pour gérer les données, le schéma relationnel utilisé est représenté sur la figure 2 (transcrit ici en notation textuelle : clés primaires soulignées, clés étrangères précédées d'un « # ») :

utilisateur(<u>pseudo</u>, email, nom, prenom)

affirmation(<u>id_aff</u>, contenu, #auteur, sorte, #aff_repondue, nb_likes, nb_dislikes)

reaction(<u>#aff_reagie</u>, <u>#utilisateur</u>, sorte)

Flèches du schéma : affirmation.auteur et reaction.utilisateur référencent utilisateur.pseudo ; affirmation.aff_repondue et reaction.aff_reagie référencent affirmation.id_aff.

Figure 2. Schéma relationnel de la plateforme de débat

La clé primaire de chaque table correspond à l'ensemble de ses attributs soulignés. Chaque attribut précédé d'un « # » est une clé étrangère.

L'attribut sorte de la table affirmation peut valoir 'sujet' quand c'est l'affirmation initiale d'un sujet de débat, ou bien 'pour' ou 'contre' quand c'est une réponse à une autre affirmation (référencée par l'attribut aff_repondue). L'attribut contenu correspond à la phrase elle-même de l'affirmation.

L'attribut sorte de la table reaction peut valoir 'like' ou 'dislike', et s'applique à l'affirmation référencée par l'attribut aff_reagie.

Voici des exemples d'extraits des tables affirmation et reaction. On a omis, pour ces exemples, les attributs auteur et contenu de la table affirmation :

`affirmation`
`id_aff``sorte``aff_repondue``nb_likes``nb_dislikes`
0`'sujet'``NULL`4217
1`'pour'`02020
2`'pour'`04010
3`'contre'`01312
4`'contre'`0186
7`'contre'`11516
`reaction`
`aff_reagie``utilisateur``sorte`
0`'alice'``'like'`
1`'alice'``'dislike'`
0`'bob'``'dislike'`
1`'bob'``'dislike'`

9. Indiquer en justifiant quel autre attribut de la table utilisateur aurait aussi pu servir de clé primaire.

On rappelle que la fonction d'agrégation COUNT permet de compter les éléments du résultat d'une requête en plaçant COUNT(*) dans la clause SELECT.

10. Écrire une requête SQL permettant d'obtenir le nombre d'affirmations qui ont 50 likes ou plus.

11. Écrire une requête qui permet d'obtenir les contenus des sujets et leur nombre de likes, créés par les utilisateurs dont les prénom et nom sont respectivement 'Pierre' et 'Durand'.

Le mot-clé AS permet de donner un autre nom à une table au sein d'une requête. Il est notamment nécessaire lorsqu'on a besoin de faire la jointure d'une table avec elle-même. On considère la requête SQL suivante :


SELECT aff.contenu, rep.contenu
FROM affirmation AS aff JOIN affirmation AS rep
  ON rep.affirmation_repondue = aff.id_affirmation
WHERE rep.nb_likes >= 2 * aff.nb_likes
  AND rep.sorte = 'contre'

12. Donner une interprétation en français de ce que permet d'obtenir la requête ci-dessus.

L'utilisateur au pseudo 'i&lt;3descartes' approuve par un like l'affirmation 'Cogito ergo sum' qui a l'id_aff 108.

13. Recopier et compléter les deux requêtes SQL suivantes, permettant de mettre à jour les informations de la base de données suite à cette action.


... reaction
VALUES (..., ..., ...);;

... affirmation
SET nb_likes = ...
WHERE ... = ...;;

L'utilisateur au pseudo 'i&lt;3rgpd' souhaite faire valoir son droit à l'effacement, et supprimer son compte utilisateur et toutes les données qui y sont liées. Dans cette situation, la plateforme de débat, qui souhaite maintenir la qualité des échanges, suit la procédure suivante :

14. Expliquer pourquoi il est nécessaire de supprimer les réactions de l'utilisateur avant de supprimer l'utilisateur.

15. Écrire les trois requêtes qui permettent la suppression des données du compte de 'i&lt;3rgpd' en suivant la procédure décrite ci-dessus.

Corrigé

Partie A : structure arborescente des arguments

1. Le point de départ du débat est la racine de l'arbre : l'unique nœud sans parent, dont tous les autres nœuds descendent. Sur la figure 1, c'est le parallélogramme « La Science est une activité humaine objective. » ; les arguments sans réponse (par exemple « Les géants pétroliers… ») en sont les feuilles.

2. Un arbre binaire est un arbre dont chaque nœud possède au plus deux enfants (un sous-arbre gauche et un sous-arbre droit). Ce n'est pas le cas d'un arbre de débat : une affirmation peut recevoir autant d'arguments qu'on veut. Sur la figure 1, la racine a quatre enfants (deux arguments pour et deux arguments contre) et l'affirmation « La recherche financée par des organismes commerciaux peut tordre les faits. » en a trois. Un arbre de débat est donc un arbre général (dit « quelconque »), et non un arbre binaire. Dans la classe Affirmation, cela se traduit par l'attribut arguments, une liste d'enfants de longueur quelconque, là où un arbre binaire aurait deux attributs gauche et droit.

3. Les attributs sont ceux créés dans le constructeur __init__ :

AttributTypeRôle
`contenu``str`la phrase de l'affirmation
`sorte``str``""`, puis `"sujet"`, `"pour"` ou `"contre"`
`arguments``list` (d'objets `Affirmation`)les arguments rattachés, vide au départ
`nb_likes``int`nombre de likes, 0 au départ
`nb_dislikes``int`nombre de dislikes, 0 au départ

4. La documentation impose de vérifier que l'argument n'a encore jamais été utilisé : avant tout ajout, son attribut sorte vaut la chaîne vide (ligne 8 du constructeur). On l'ajoute ensuite à la liste des arguments de self, puis on le marque comme argument pour :


    assert argument.sorte == ""
    self.arguments.append(argument)
    argument.sorte = "pour"

Testé : après s.soutenir(a), a figure dans s.arguments et a.sorte vaut &quot;pour&quot; ; un second s.soutenir(a) lève une AssertionError, conformément à la précondition.

Ce que le correcteur attend : c'est self.arguments qui reçoit l'argument (et non argument.arguments), et c'est argument.sorte qui change (pas self.sorte, qui reste ce qu'il était).

5. On parcourt les arguments directement rattachés (la liste arguments, sans descendre plus bas dans l'arbre) et l'on compte ceux dont la sorte est &quot;contre&quot; :


def nb_contre(self):
    n = 0
    for arg in self.arguments:
        if arg.sorte == "contre":
            n = n + 1
    return n

Sur l'arbre de la figure 1 (reconstruit et exécuté) : la racine renvoie 2, « La Science se base sur les faits… » renvoie 1, et une feuille renvoie 0.

6. La méthode mystere renvoie le plus grand nombre d'arguments contre directement rattachés à une même affirmation, parmi l'affirmation self et toutes celles de son sous-arbre (ses arguments, les arguments de ses arguments, etc.) : c'est le « maximum de contre-attaques directes » subies par une affirmation du débat.

En effet, m part de self.nb_contre(), puis, pour chaque argument, l'appel récursif arg.mystere() fournit le maximum sur le sous-arbre de cet argument, et m ne conserve que le plus grand de tous ces candidats. La récursion s'arrête d'elle-même sur les feuilles, dont la boucle ne fait aucun tour. Sur la figure 1, mystere appelée sur la racine renvoie 2 (la racine subit deux arguments contre, aucune autre affirmation n'en subit plus d'un).

7. L'évaluation vaut (likes dislikes) de l'affirmation, plus l'évaluation de chaque argument pour, moins l'évaluation de chaque argument contre ; c'est une définition récursive, l'appel arg.evaluation() descendant dans le sous-arbre :


def evaluation(self):
    total = self.nb_likes - self.nb_dislikes
    for arg in self.arguments:
        if arg.sorte == "pour":
            total = total + arg.evaluation()
        else:
            total = total - arg.evaluation()
    return total

Le else est légitime car un argument rattaché ne peut être que &quot;pour&quot; ou &quot;contre&quot; ; on peut aussi remplacer else: par if arg.sorte == &quot;contre&quot;:. Les autres lignes proposées sont à écarter : def evaluation(): n'a pas le paramètre self ; nb_likes + nb_dislikes et le produit ne suivent pas la formule ; arguments_pour et arguments_contre ne sont pas des attributs de la classe ; if arg.sorte == &quot;sujet&quot; ne peut jamais être vrai pour un argument ; total = arg.evaluation() écraserait le total au lieu de l'accumuler ; et return 1 + total ajouterait 1 à chaque nœud.

Vérification par exécution sur l'extrait de table de la partie B, qui décrit un petit arbre (0 est le sujet, 1 et 2 ses arguments pour, 3 et 4 ses arguments contre, 7 un argument contre de 1) : ; ; ; ; ; d'où : le pour l'emporte.

Partie B : base de données des utilisateurs et de leurs contributions

8. Les rôles d'un SGBD sont a), c) et d).

9. L'attribut email aurait aussi pu servir de clé primaire : une adresse électronique est propre à une personne, deux comptes ne peuvent pas partager la même, et elle est nécessairement renseignée à l'inscription ; elle identifie donc chaque n-uplet de façon unique et sans valeur NULL, ce qu'exige une clé primaire. Ni nom ni prenom ne conviennent : plusieurs utilisateurs peuvent s'appeler Pierre Durand.

10. On compte les lignes de affirmation dont le nombre de likes est au moins 50 :


SELECT COUNT(*)
FROM affirmation
WHERE nb_likes >= 50;

Sur l'extrait donné, le résultat serait 0 (le maximum est 42) ; vérifiée sur une base d'essai, la requête renvoie bien le nombre de lignes à nb_likes .

11. Le nom et le prénom sont dans utilisateur, le contenu et les likes dans affirmation : il faut une jointure sur la clé étrangère auteur, qui référence pseudo, puis filtrer les sujets et l'identité de l'auteur :


SELECT affirmation.contenu, affirmation.nb_likes
FROM affirmation
JOIN utilisateur ON affirmation.auteur = utilisateur.pseudo
WHERE affirmation.sorte = 'sujet'
  AND utilisateur.prenom = 'Pierre'
  AND utilisateur.nom = 'Durand';

Ce que le correcteur attend : la condition sorte = 'sujet' (on veut les sujets, pas tous les messages de Pierre Durand), la jointure (le nom n'est pas dans affirmation) et les deux conditions liées par AND.

12. La table affirmation est jointe avec elle-même : aff désigne une affirmation et rep une réponse qui lui est rattachée (sa clé étrangère « affirmation répondue » vaut l'identifiant de aff). Les conditions gardent les réponses de sorte 'contre' dont le nombre de likes est au moins le double de celui de l'affirmation attaquée. La requête renvoie donc, pour chaque argument contre au moins deux fois plus liké que l'affirmation qu'il attaque, le contenu de cette affirmation et le contenu de l'argument contre : elle repère les contre-arguments nettement plus populaires que ce qu'ils contredisent.

Remarque : la requête imprimée nomme les attributs affirmation_repondue et id_affirmation, alors que le schéma de la figure 2 les appelle aff_repondue et id_aff ; avec les noms du schéma, la jointure s'écrit ON rep.aff_repondue = aff.id_aff. Sur l'extrait de table, aucune ligne ne ressort : l'argument 7 (15 likes) attaque l'affirmation 1 (20 likes), et 3 et 4 (13 et 18 likes) attaquent 0 (42 likes) ; aucun n'atteint le double.

13. On ajoute une réaction (les valeurs dans l'ordre des attributs de la table : aff_reagie, utilisateur, sorte) puis on incrémente le compteur de likes de l'affirmation 108 :


INSERT INTO reaction
VALUES (108, 'i<3descartes', 'like');

UPDATE affirmation
SET nb_likes = nb_likes + 1
WHERE id_aff = 108;

Testé sur une base d'essai : la ligne (108, 'i&lt;3descartes', 'like') apparaît dans reaction et nb_likes de l'affirmation 108 passe de 60 à 61.

Ce que le correcteur attend : nb_likes = nb_likes + 1 (on ne connaît pas la valeur actuelle, on ne peut pas écrire un nombre), et la clause WHERE sur id_aff, sans laquelle toutes les affirmations gagneraient un like.

14. Dans la table reaction, l'attribut utilisateur est une clé étrangère qui référence utilisateur.pseudo. La contrainte d'intégrité référentielle impose que toute valeur d'une clé étrangère corresponde à une clé primaire existante. Si l'on supprimait d'abord l'utilisateur, ses réactions référenceraient un pseudo qui n'existe plus : le SGBD refuse cette suppression (erreur de contrainte de clé étrangère, ce que nous avons observé sur une base d'essai). Il faut donc d'abord faire disparaître toutes les références au pseudo (les réactions, et l'attribut auteur des affirmations, mis à NULL), puis seulement supprimer l'utilisateur.

15. Les trois requêtes, dans l'ordre de la procédure :


UPDATE affirmation
SET auteur = NULL
WHERE auteur = 'i<3rgpd';

DELETE FROM reaction
WHERE utilisateur = 'i<3rgpd';

DELETE FROM utilisateur
WHERE pseudo = 'i<3rgpd';

La première anonymise les affirmations sans les supprimer (leurs nb_likes et nb_dislikes sont intacts) ; la deuxième supprime les réactions sans toucher aux compteurs des affirmations, comme demandé ; la troisième supprime enfin le compte, ce que le SGBD accepte maintenant que plus rien ne le référence. Exécutées dans cet ordre sur une base d'essai, elles laissent les affirmations de 'i&lt;3rgpd' avec un auteur NULL et leurs compteurs inchangés, et suppriment l'utilisateur.

Poser une question au tuteur sur ce sujet