Épreuve écrite · 2025 · Jour 2 - 18 juin 2025

Bac NSI 2025 Métropole jour 2

Le sujet du 18 juin 2025 relie compression, gestion de jeux et sécurité des communications. Son intérêt dépasse les résultats numériques : pourquoi la récursivité s’arrête-t-elle, comment garder tous les ex aequo d’un classement et que prouve réellement un ping ? Le corrigé suit les données officielles et signale les limites de certains programmes ou formulations, sans les masquer.

Trois exercices indépendants, 6, 6 et 8 points, à traiter en 3 h 30 sans calculatrice. Les 39 questions disposent de corrections progressives ; les ateliers aident à construire et vérifier les mécanismes.

Dans ce sujet

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

Arbres de codage et compression de Shannon-Fano

Le codage de Shannon-Fano, mis au point par Robert Fano à partir d’une idée de Claude Shannon, compresse des données sans perte. Dans un arbre de codage binaire, chaque feuille porte un symbole ; les bits du chemin depuis la racine forment son code. Pour décoder, on descend en suivant les bits jusqu’à une feuille, puis on repart de la racine pour le symbole suivant.

Partie A : utiliser un arbre

Dans la figure 1, c est codé 1101 et d 11000. Les branches de gauche portent ici 1, celles de droite 0.

1111001010011001001100••••iu•c•od••,p•nj••sSPe
Figure 1 : arbre de codage, branche gauche 1 et branche droite 0
Lire les connexions du schéma
  • • relié à • : 1
  • • relié à • : 1
  • • relié à • : 1
  • • relié à i : 1
  • • relié à u : 0
  • • relié à • : 0
  • • relié à c : 1
  • • relié à • : 0
  • • relié à o : 1
  • • relié à d : 0
  • • relié à • : 0
  • • relié à • : 1
  • • relié à , : 1
  • • relié à p : 0
  • • relié à • : 0
  • • relié à n : 1
  • • relié à j : 0
  • • relié à • : 0
  • • relié à • : 1
  • • relié à s : 1
  • • relié à SP : 0
  • • relié à e : 0

Question 1

#

Écrire le mot binaire utilisé pour encoder l’espace, représenté par _ dans l’arbre.

Comprendre la correction

010. Depuis la racine : droite (0), gauche (1), droite (0). L’espace n’est pas un séparateur implicite : c’est une feuille à part entière.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)

Question 2

#

Déterminer le texte codé par 0001110101111110011001.

Comprendre la correction
Bits lus jusqu’à une feuilleSymbole
00e
011s
1010p
1111i
11001o
1001n

Le texte est espion. On ne découpe pas les bits en blocs de taille fixe : chaque feuille rencontrée indique la fin du code courant. Le dernier bit est consommé exactement à la feuille n, sans reste.

Voir la question dans le sujet PDF, p. 2 (nouvel onglet)

Question 3

#

Citer le type de parcours qui permettrait d’obtenir les symboles classés par taille d’encodage croissante.

Comprendre la correction

Un parcours en largeur visite les niveaux dans l’ordre des profondeurs. On ne retient que les feuilles rencontrées : leur profondeur est précisément la longueur de leur code. Un parcours en profondeur pourrait atteindre une longue branche avant une feuille plus proche de la racine.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

Partie B : construire l’arbre

Le texte est je pense, donc je suis. Étape 1 : classer les symboles par effectifs croissants. Étape 2 : couper cette liste en deux groupes contigus dont les effectifs totaux sont aussi proches que possible. Étape 3 : premier groupe à gauche, étiquette 1, second groupe à droite, étiquette 0. Étape 4 : recommencer dans chaque groupe jusqu’à n’avoir qu’un symbole.

Symboleiucod,pnjs_e
Occurrences111111122344
10i u c o d , p n j s_ e : 22i u c o d , p n j :11s _ e : 11
Figure 2 : premier partage du tableau trié
Lire les connexions du schéma
  • i u c o d , p n j s _ e : 22 relié à i u c o d , p n j : 11 : 1
  • i u c o d , p n j s _ e : 22 relié à s _ e : 11 : 0

Question 4

#

Justifier par le calcul que l’étape 2 mène à la séparation de la figure 2.

Comprendre la correction

Le premier groupe contient i, u, c, o, d, virgule, p, n, j : 7 × 1 + 2 + 2 = 11 occurrences. Le second contient s, espace et e : 3 + 4 + 4 = 11. Les deux sommes sont égales ; l’écart nul est le plus petit possible.

Voir la question dans le sujet PDF, p. 3 (nouvel onglet)

La figure 3 détaille les groupes de l’arbre obtenu :

PréfixeGroupe et effectifs
racinei1 u1 c1 o1 d1 ,1 p1 n2 j2 s3 _4 e4
1i1 u1 c1 o1 d1 ,1 p1 n2 j2
0s3 _4 e4
11i1 u1 c1 o1 d1
10,1 p1 n2 j2
01s3 _4
111i1 u1
110c1 o1 d1
101,1 p1
100n2 j2
1100o1 d1
1111001010011001001100••••iu•c•od••,p•nj••sSPe
Figure 3 : arbre de Shannon-Fano du texte, groupes décrits ci-dessus
Lire les connexions du schéma
  • • relié à • : 1
  • • relié à • : 1
  • • relié à • : 1
  • • relié à i : 1
  • • relié à u : 0
  • • relié à • : 0
  • • relié à c : 1
  • • relié à • : 0
  • • relié à o : 1
  • • relié à d : 0
  • • relié à • : 0
  • • relié à • : 1
  • • relié à , : 1
  • • relié à p : 0
  • • relié à • : 0
  • • relié à n : 1
  • • relié à j : 0
  • • relié à • : 0
  • • relié à • : 1
  • • relié à s : 1
  • • relié à SP : 0
  • • relié à e : 0

Question 5

#

Donner la hauteur de l’arbre de la figure 3 et préciser ce qu’elle représente ici. Une feuille seule est de hauteur 0.

Comprendre la correction

La hauteur vaut 5 : il y a cinq arêtes sur le plus long chemin racine-feuille, pour o ou d. C’est la longueur maximale d’un code de symbole. Il faut compter les arêtes, pas les six nœuds de ce chemin.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 6

#

Justifier, en comparant ASCII et Shannon-Fano, que ce second codage utilise environ deux fois moins d’octets pour je pense, donc je suis. Le sujet compte un octet par symbole ASCII.

Indice

Additionnez occurrence×longueur de code pour chaque symbole.

Comprendre la correction
Longueur du codeSymboles et occurrencesContribution
2e : 48
3s : 3 ; espace : 421
4i, u, c, virgule, p : 1 chacun ; n, j : 2 chacun36
5o, d : 1 chacun10

Le texte contient 22 caractères, donc 22 octets ou 176 bits avec la convention ASCII du sujet. Shannon-Fano utilise 8 + 21 + 36 + 10 = 75 bits, soit 9,375 octets de données binaires, arrondis à 10 octets pour les stocker dans des octets complets.

Le rapport est 22 / 10 = 2,2 en comptant cet arrondi : l’ordre de grandeur « environ deux fois moins » est justifié. Le calcul ne comprend pas le coût de transmission de l’arbre ou de la table de codage.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Question 7

#

Dessiner un arbre de codage permettant d’encoder « chiffrer » avec Shannon-Fano.

Comprendre la correction
GroupePremier sous-groupe, bit 1Second sous-groupe, bit 0
c1 h1 i1 e1 f2 r2c1 h1 i1 e1 : 4f2 r2 : 4
c1 h1 i1 e1c1 h1 : 2i1 e1 : 2
c1 h1c1h1
i1 e1i1e1
f2 r2f2r2
1110010010•••ch•ie•fr
Un arbre de Shannon-Fano pour chiffrer
Lire les connexions du schéma
  • • relié à • : 1
  • • relié à • : 1
  • • relié à c : 1
  • • relié à h : 0
  • • relié à • : 0
  • • relié à i : 1
  • • relié à e : 0
  • • relié à • : 0
  • • relié à f : 1
  • • relié à r : 0

Les quatre lettres de fréquence 1 peuvent être ordonnées autrement au départ. Le code reste valide si chaque partage respecte cet ordre et rapproche les poids. Ici, les lettres fréquentes f et r ont des codes plus courts de deux bits.

Voir la question dans le sujet PDF, p. 4 (nouvel onglet)

Partie C : programmer l’encodage

def creer_dico_occ(texte):
    """Renvoie le nombre d’occurrences de chaque symbole."""
    dico = {}
    for symbole in texte:
        if symbole in dico:
            dico[symbole] = ...
        else:
            dico[symbole] = ...
    return dico

Question 8

#

Compléter les lignes 8 et 10 de creer_dico_occ.

Comprendre la correction
def creer_dico_occ(texte):
    dico = {}
    for symbole in texte:
        if symbole in dico:
            dico[symbole] = dico[symbole] + 1
        else:
            dico[symbole] = 1
    return dico

Si le symbole est connu, on augmente son compteur ; sinon son premier passage donne le compteur 1. La boucle examine chaque caractère, y compris les espaces et la virgule. Une chaîne vide produit naturellement un dictionnaire vide.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

La fonction fournie creer_tab_trie transforme le dictionnaire en une liste de couples classés par effectifs croissants. Pour le texte étudié, elle renvoie :

[('i', 1), ('u', 1), ('c', 1), ('o', 1), ('d', 1), (',', 1),
 ('p', 1), ('n', 2), ('j', 2), ('s', 3), (' ', 4), ('e', 4)]

Question 9

#

Écrire somme_occ(tab), qui prend un tableau de tuples (symbole, nb_occ) et renvoie la somme des nombres d’occurrences.

Comprendre la correction
def somme_occ(tab):
    total = 0
    for symbole, nb_occ in tab:
        total = total + nb_occ
    return total

On additionne le second élément de chaque tuple. Le symbole n’intervient pas dans ce calcul. Pour les douze couples du tableau donné, on obtient 22 ; pour une liste vide, 0.

Voir la question dans le sujet PDF, p. 5 (nouvel onglet)

La fonction de séparation est fournie par le sujet :

def separe(tab):
    moitie = somme_occ(tab) // 2
    somme = 0
    i = 0
    while moitie > somme:
        somme = somme + tab[i][1]
        i = i + 1
    tab1 = [tab[k] for k in range(0, i)]
    tab2 = [tab[k] for k in range(i, len(tab))]
    return tab1, tab2
def shannon(symbole, tab):
    if len(tab) == 1:
        return ""
    else:
        t1, t2 = separe(tab)
        if symbole in [elt[0] for elt in t1]:
            return "1" + ...
        else:
            return "0" + ...

Question 10

#

Compléter les lignes 9 et 11 de la fonction récursive shannon(symbole, tab), qui renvoie l’écriture binaire associée au symbole présent dans le tableau trié.

PythonRetrouver le chemin binaire d’un symboleÉcrivez votre solution et mettez-la à l’épreuve

Complétez shannon(symbole, tab). tab est une liste non vide de couples (symbole, fréquence), triée par fréquence croissante, et le symbole cherché y figure. separe est fourni : le premier groupe porte le bit 1, le second le bit 0. Une feuille ajoute une chaîne vide. Pour respecter le code imprimé, les tests utilisent des fréquences dont tous les partages sont non vides. La séparation officielle peut échouer à réduire d’autres tableaux : cette limite est étudiée dans la question suivante.

def shannon(symbole, tab):
    # À vous de jouer
    pass

Les cas de test proposés :

  • Un groupe réduit à une feuille : Aucune arête ne reste à parcourir. Le code demandé renvoie une chaîne vide, pas le caractère ni le bit 0.
  • Respecter les étiquettes des branches : Le sujet attribue 1 au premier groupe et 0 au second, même si d’autres conventions existent.
  • Deux niveaux de récursion : Le bit choisi à la racine précède celui choisi dans le sous-groupe.
  • Une feuille plus proche de la racine : La longueur des codes n’est pas fixe. c est seul dès le premier partage.
  • Des fréquences différentes, tableau inchangé : Le tableau sert à déterminer les groupes ; la recherche du code ne doit ni le vider ni le réordonner.
Indice 1

L’appel récursif conserve le symbole et réduit le tableau au groupe choisi.

Indice 2

Le code d’un chemin est un bit suivi du code du reste du chemin.

Comprendre la correction
def shannon(symbole, tab):
    if len(tab) == 1:
        return ""
    else:
        t1, t2 = separe(tab)
        if symbole in [elt[0] for elt in t1]:
            return "1" + shannon(symbole, t1)
        else:
            return "0" + shannon(symbole, t2)

Le test détermine dans quel groupe se trouve la feuille cherchée. On ajoute le bit de la branche avant le suffixe calculé récursivement sur ce groupe. Une seule branche est explorée. Lorsque le groupe contient une seule feuille, il ne reste aucune arête à parcourir, d’où la chaîne vide.

Exemple pour e : le premier partage l’envoie dans le groupe droit, préfixe 0 ; le partage suivant à droite encore, préfixe 00 ; le groupe contient alors seulement e.

Voir la question dans le sujet PDF, p. 5, 6 (nouvel onglet)

Question 11

#

Décrire ce qui garantit la terminaison de la fonction récursive shannon.

Indice

La longueur ne décroît que si chaque sous-tableau est strictement plus court.

Comprendre la correction

Pour les partages non vides utilisés dans l’exemple, la longueur du tableau est un entier strictement décroissant à chaque appel récursif. Elle atteint 1, le cas d’arrêt. C’est le variant attendu dans le raisonnement.

Limite du code fourni : sa fonction separe ne garantit pas deux groupes non vides pour toute fréquence possible. Avec [('b', 1), ('a', 5)], la moitié vaut 3 ; la boucle cumule 1 puis 6 et renvoie le tableau entier et une liste vide. Le prochain appel reprend alors exactement le même tableau. On ne peut donc pas affirmer une terminaison générale sans une hypothèse supplémentaire ou une correction.

Voici une variante qui cherche la meilleure coupure parmi les seules coupures intérieures ; elle assure à la fois deux groupes non vides et l’écart minimal :

def separe_robuste(tab):
    assert len(tab) >= 2
    total = somme_occ(tab)
    cumul = 0
    meilleur_ecart = total + 1
    coupure = 1
    for i in range(1, len(tab)):
        cumul = cumul + tab[i - 1][1]
        ecart = abs(total - 2 * cumul)
        if ecart < meilleur_ecart:
            meilleur_ecart = ecart
            coupure = i
    return tab[:coupure], tab[coupure:]

La quantité abs(total - 2 * cumul) est la différence absolue entre les deux sommes. En limitant i entre 1 et len(tab) - 1, aucune branche ne reçoit le tableau entier. Cette amélioration est distincte du code officiel à compléter.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)

Question 12

#

Écrire encode_shannon(texte), qui renvoie le mot binaire encodé. On pourra utiliser creer_dico_occ, creer_tab_trie, separe et shannon.

Comprendre la correction
def encode_shannon(texte):
    dico = creer_dico_occ(texte)
    tab = creer_tab_trie(dico)
    resultat = ""
    for symbole in texte:
        resultat = resultat + shannon(symbole, tab)
    return resultat

On calcule les fréquences et le tableau une seule fois pour tout le texte. Chaque caractère utilise ensuite ce même arbre ; recalculer un arbre pour chaque lettre ferait perdre le code commun nécessaire au décodage.

Cette version correspond à l’exercice et fonctionne sur son exemple. Le départage des coupures ex aequo du code fourni diffère de la figure 3 : il donne par exemple j = 100 et p = 10110, tout en conservant 75 bits pour le texte. Pour décoder, on doit utiliser la table réellement produite, pas mélanger ces deux arbres. Pour une utilisation générale, il faut employer une séparation toujours non vide comme celle expliquée en Q11. Un texte composé d’un seul symbole réclame aussi une convention supplémentaire : avec le cas de base fourni, son code est vide. On peut choisir le code 0 et conserver la longueur du message dans le format. Le sujet ne demande pas de définir ce format de fichier.

Voir la question dans le sujet PDF, p. 6 (nouvel onglet)
Coupez les fréquences, puis suivez un symboleUn atelier pour expérimenter

Choisissez un message et une lettre. L’atelier construit des groupes non vides aux sommes les plus proches, puis montre tous les partages conduisant à cette lettre. Le cas dominant révèle pourquoi il faut protéger la séparation.

Lire le résultat de l’expérience initiale

i se code 1111

La branche gauche vaut 1. L’atelier exclut les coupures vides ; contrairement au code separe imprimé, il termine aussi pour un symbole dominant.

Groupe avant partagePoids totalBit choisiGroupe conservé
i u c o d , p n j s SP e221i u c o d , p n j
i u c o d , p n j111i u c o d
i u c o d51i u
i u21i

Le variant de récursion est la taille du groupe. Il décroît uniquement si chaque partage conserve deux groupes non vides : une preuve dépend réellement de cette propriété.

Revoir les notions de cet exercice

Exercice 2 · 6 points

Ludothèque : emprunts SQL et podium avec ex aequo

Une ludothèque municipale gère les jeux, ses adhérents, les emprunts et les avis. Elle ne possède qu’un exemplaire de chaque jeu ; deux jeux ne peuvent donc pas avoir le même nom. La figure 1 indique les clés primaires soulignées et les clés étrangères par un croisillon.

nomJeuidAdherentnomJeuidAdherentjeuavisempruntadherent
Figure 1 : références entre les tables de la ludothèque
Lire les connexions du schéma
  • avis vers jeu : nomJeu
  • avis vers adherent : idAdherent
  • emprunt vers jeu : nomJeu
  • emprunt vers adherent : idAdherent
RelationAttributs (clé primaire indiquée)Clés étrangères
jeunomJeu (PK), editeur, anneeSortie, ageMinimum, categorieAucune
avisidAvis (PK), nomJeu, idAdherent, commentairenomJeu → jeu.nomJeu ; idAdherent → adherent.idAdherent
empruntidEmprunt (PK), nomJeu, idAdherent, dateEmprunt, dateRendunomJeu → jeu.nomJeu ; idAdherent → adherent.idAdherent
adherentidAdherent (PK), nom, prenom, dateNaissance, adresseAucune

On peut utiliser SELECT, FROM, WHERE, AND, OR, JOIN ... ON, UPDATE, INSERT, DELETE, DISTINCT, ORDER BY et COUNT. SELECT COUNT(nomJeu) FROM jeu donne le nombre de jeux.

Question 1

#

Expliquer pourquoi nom ne peut pas être la clé primaire de adherent.

Comprendre la correction

Plusieurs adhérents peuvent partager un nom de famille. La valeur de nom ne suffit donc pas à identifier une ligne de façon unique. idAdherent, choisi comme clé dans le schéma, distingue les homonymes sans supposer quoi que ce soit sur leurs noms.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)
SELECT nomJeu, editeur
FROM jeu
ORDER BY nomJeu;

Question 2

#

Décrire le résultat de la requête suivante.

Comprendre la correction

Elle renvoie le nom et l’éditeur de chaque jeu, classés par ordre croissant du nom du jeu. Elle ne regroupe pas les jeux par éditeur et ne modifie pas la table.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 3

#

Écrire une requête pour connaître le nom de tous les jeux en cours d’emprunt. Pour un jeu non rendu, dateRendu vaut NULL.

Comprendre la correction
SELECT nomJeu
FROM emprunt
WHERE dateRendu IS NULL;

NULL se teste avec IS NULL, pas avec = NULL : il représente l’absence de valeur. Le nom du jeu est déjà dans la table d’emprunts, donc aucune jointure n’est nécessaire.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 4

#

Écrire une requête affichant le nom et le prénom de tous les adhérents ayant emprunté « Catan ».

SQLRetrouver les emprunteurs de CatanÉcrivez votre solution et mettez-la à l’épreuve

Renvoyez le nom et le prénom des adhérents ayant emprunté Catan. Une personne qui l’a emprunté plusieurs fois ne doit apparaître qu’une fois dans cette projection.

SELECT ...
FROM ...
WHERE ...;

Les cas de test proposés :

  • Cas pédagogique autour de la visite citée : Le sujet cite Claire VOYANT, son emprunt 1538 de Catan et le retour du 3 juin 2025. Les identifiants d’adhérent, dates de départ, autres lignes et attributs absents sont des compléments pédagogiques.
  • Cas complémentaire : plusieurs personnes et plusieurs emprunts : Jeu complémentaire pédagogique, distinct des données officielles. Les emprunts passés et présents comptent tous ; seul le nom du jeu et l’identité de la personne importent.
Comprendre la correction
SELECT DISTINCT adherent.nom, adherent.prenom
FROM adherent JOIN emprunt
ON adherent.idAdherent = emprunt.idAdherent
WHERE emprunt.nomJeu = 'Catan';

La jointure relie chaque emprunt à l’adhérent correspondant. DISTINCT élimine les répétitions d’un même couple nom-prénom lorsqu’une personne a emprunté plusieurs fois Catan. Le sujet ne demande pas de limiter aux emprunts actuels.

Deux personnes ayant exactement le même nom et prénom deviennent indiscernables dans cette projection : pour les distinguer, on devrait aussi afficher leur identifiant, mais la question demande seulement nom et prénom.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 5

#

Claire VOYANT a rendu Catan le 3 juin 2025. L’identifiant de cet emprunt est 1538. Écrire la requête de mise à jour ; les dates sont au format AAAA-MM-JJ.

Indice

Le schéma et le texte n’écrivent pas l’identifiant exactement de la même façon.

Comprendre la correction
UPDATE emprunt
SET dateRendu = '2025-06-03'
WHERE idEmprunt = 1538;

Le schéma écrit idEmprunt ; le texte de la question imprime id_emprunt. La requête utilise le nom effectif de la colonne de la figure. L’identifiant permet de viser cet emprunt précis, même si Claire a emprunté plusieurs fois le même jeu.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

Question 6

#

Écrire une requête donnant le nom et la catégorie des jeux sortis à partir de 2010 et dont l’âge minimum est strictement inférieur à 10 ans.

Comprendre la correction
SELECT nomJeu, categorie
FROM jeu
WHERE anneeSortie >= 2010 AND ageMinimum < 10;

« À partir de 2010 » inclut 2010, alors que « strictement inférieur à 10 » exclut 10. Les deux conditions doivent être vraies simultanément, d’où AND.

Voir la question dans le sujet PDF, p. 8 (nouvel onglet)

La figure 2 ajoute evenement(nom, dateEvenement, heure), où nom est clé primaire, et participation(idParticipation, ...), où idParticipation est clé primaire. Les quatre relations initiales restent identiques. Les références manquantes de participation sont à concevoir.

adherentparticipationevenement
Figure 2 : ajout des événements et de la participation, liens à compléter
Lire les connexions du schéma

    Question 7

    #

    Proposer les clés étrangères de participation et préciser les attributs référencés.

    Comprendre la correction
    Nouvel attribut de participationRéférence
    idAdherentadherent.idAdherent
    nomEvenementevenement.nom

    Chaque participation relie un adhérent à un événement. idParticipation identifie la ligne, mais ne suffit pas à retrouver les deux entités concernées. Les noms des deux nouvelles colonnes sont proposés : le schéma les laisse à compléter.

    Voir la question dans le sujet PDF, p. 9 (nouvel onglet)

    Le programme fourni récupère la liste des jeux empruntés, avec une occurrence par emprunt :

    import sqlite3
    
    # Connexion à la base de données
    connection = sqlite3.connect("ludotheque.db")
    curseur = connection.cursor()
    
    # Exécution de la requête
    curseur.execute("SELECT nomJeu FROM emprunt")
    
    # Récupération des résultats
    jeux = curseur.fetchall()
    liste = []
    for jeu in jeux:
        liste.append(jeu[0])
    
    # Fermeture de la connexion
    curseur.close()
    connection.close()

    Question 8

    #

    Écrire un script créant dict_emprunts, qui associe à chaque jeu emprunté son nombre d’emprunts à partir de liste.

    Comprendre la correction
    dict_emprunts = {}
    for jeu in liste:
        if jeu in dict_emprunts:
            dict_emprunts[jeu] = dict_emprunts[jeu] + 1
        else:
            dict_emprunts[jeu] = 1

    La liste contient une occurrence par emprunt, pas seulement un exemplaire de chaque nom. On crée la clé au premier emprunt, puis on augmente le compteur aux passages suivants. Une liste vide donne un dictionnaire vide ; aucun jeu absent de la liste n’est inventé avec un compteur nul.

    Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
    dict_emprunts = {
        "Terraforming Mars": 25,
        "Codenames": 22,
        "Agricola": 18,
        "Puerto Rico": 18,
        "Caylus": 18,
        "Dominion": 22,
        "Dixit": 12
    }

    Le sujet donne le podium suivant :

    [["Agricola", "Puerto Rico", "Caylus"],
     ["Dominion", "Codenames"], ["Terraforming Mars"]]

    Question 9

    #

    Proposer un script générant un podium sous forme de trois listes. Garder tous les ex aequo aux trois meilleurs nombres d’emprunts, dans l’ordre troisième place, deuxième place, première place.

    Indice 1

    Il faut classer les nombres d’emprunts distincts, pas prendre seulement les trois premiers jeux.

    Indice 2

    Pour chaque niveau retenu, récupérez tous les jeux qui ont cet effectif.

    Comprendre la correction
    effectifs = []
    for nombre in dict_emprunts.values():
        if nombre not in effectifs:
            effectifs.append(nombre)
    effectifs.sort(reverse=True)
    meilleurs = effectifs[:3]
    podium = [[], [], []]
    for rang in range(len(meilleurs)):
        for jeu, nombre in dict_emprunts.items():
            if nombre == meilleurs[rang]:
                podium[2 - rang].append(jeu)

    On classe d’abord les effectifs distincts, puis on récupère tous les jeux associés aux trois plus grands. Cela évite qu’un ex aequo à la première place occupe artificiellement la deuxième place. L’indice 2 - rang place le maximum à droite, comme dans l’exemple.

    Avec les données fournies, les niveaux sont 25, 22 et 18. Le résultat est [['Agricola', 'Puerto Rico', 'Caylus'], ['Codenames', 'Dominion'], ['Terraforming Mars']]. L’ordre à l’intérieur d’une place n’est pas imposé ; la deuxième liste peut donc inverser Codenames et Dominion. Le script garde des listes vides si moins de trois effectifs distincts existent.

    Voir la question dans le sujet PDF, p. 10 (nouvel onglet)
    Un podium qui ne sacrifie aucun ex aequoUn atelier pour expérimenter

    Faites varier les emprunts de Dixit et de Codenames. Observez les trois niveaux distincts retenus et les jeux qui se partagent chaque marche.

    Lire le résultat de l’expérience initiale

    6 jeux sur trois marches

    Une marche correspond à un nombre d’emprunts distinct. Plusieurs jeux partagent la même marche ; ils ne consomment pas des rangs supplémentaires.

    PlaceEmpruntsJeux ex aequo
    125Terraforming Mars
    222Codenames, Dominion
    318Agricola, Puerto Rico, Caylus

    Un classement avec ex aequo doit définir ce qu’est une place. Ici, les trois plus grands effectifs distincts donnent trois groupes, potentiellement plus de trois jeux.

    Revoir les notions de cet exercice

    Exercice 3 · 8 points

    Masque jetable, HTTPS et segmentation IPv4

    Partie A : masque jetable

    Le sujet décrit un masque jetable : une clé au moins aussi longue que le message, choisie de manière totalement aléatoire et utilisée une seule fois. Dans ce modèle, les messages et clés ne contiennent que les lettres majuscules non accentuées A à Z. À chaque lettre on associe son rang de 0 à 25 ; le chiffrement ajoute les rangs du message et de la clé modulo 26.

    LettreABCDEFGHIJKLMNOPQRSTUVWXYZ
    Rang012345678910111213141516171819202122232425

    La figure 1, adaptée de l’article « Masque jetable » de Wikipédia cité dans le sujet, donne HELLO + WMCKL = DQNVZ :

    ÉtapeH / WE / ML / CL / KO / L
    Message74111114
    Masque221221011
    Somme2916132125
    Modulo 26316132125
    ChiffréDQNVZ

    Question 1

    #

    Chiffrer LIBRE avec la clé EYQMT par la méthode du masque jetable.

    Comprendre la correction
    PositionMessageCléSommeModulo 26Chiffré
    1L : 11E : 41515P
    2I : 8Y : 24326G
    3B : 1Q : 161717R
    4R : 17M : 12293D
    5E : 4T : 192323X

    Le message obtenu est PGRDX. Les sommes 32 et 29 dépassent 25 ; on leur retire une fois 26. Une seule soustraction suffit puisque la somme de deux rangs est au maximum 50.

    Voir la question dans le sujet PDF, p. 11 (nouvel onglet)

    La variable globale accessible à toutes les fonctions est :

    alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
                'U', 'V', 'W', 'X', 'Y', 'Z']

    Question 2

    #

    Écrire indice(L, element), qui renvoie l’indice de element dans L. Les éléments sont uniques et l’élément cherché est présent. Exemple : indice(alphabet, 'K') vaut 10.

    Comprendre la correction
    def indice(L, element):
        i = 0
        while L[i] != element:
            i = i + 1
        return i

    On part de l’indice 0 et on avance jusqu’à l’égalité. La précondition garantit que la recherche rencontre l’élément avant la fin de la liste. Sans cette précondition, il faudrait tester la borne et décider quoi renvoyer si l’élément est absent.

    Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

    Question 3

    #

    Écrire lettres_vers_indices, qui reçoit une chaîne et renvoie, dans l’ordre, la liste des indices de ses lettres. HELLO doit donner [7, 4, 11, 11, 14].

    Comprendre la correction
    def lettres_vers_indices(texte):
        resultat = []
        for lettre in texte:
            resultat.append(indice(alphabet, lettre))
        return resultat

    On parcourt les lettres dans l’ordre du message et on ajoute chaque rang en fin de liste. Des lettres répétées doivent donner des rangs répétés : on ne construit pas un ensemble.

    Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

    On dispose de indices_vers_lettres, qui transforme une liste d’entiers entre 0 et 25 en chaîne. Par exemple, [3, 16, 13, 21, 25] donne DQNVZ.

    def chiffrement(msg, cle):
        assert len(cle) >= len(msg), 'impossible'
        indices_msg = lettres_vers_indices(msg)
        indices_cle = lettres_vers_indices(cle)
        n = len(msg)
        indices_msg_chiffre = []
        for k in range(n):
            ind = ...
            if ind >= 26:
                ind = ...
            indices_msg_chiffre.append(ind)
        msg_chiffre = indices_vers_lettres(...)
        return msg_chiffre

    Question 4

    #

    Compléter les lignes 7 à 13 de chiffrement(msg, cle).

    Comprendre la correction
    def chiffrement(msg, cle):
        assert len(cle) >= len(msg), 'impossible'
        indices_msg = lettres_vers_indices(msg)
        indices_cle = lettres_vers_indices(cle)
        n = len(msg)
        indices_msg_chiffre = []
        for k in range(n):
            ind = indices_msg[k] + indices_cle[k]
            if ind >= 26:
                ind = ind - 26
            indices_msg_chiffre.append(ind)
        msg_chiffre = indices_vers_lettres(indices_msg_chiffre)
        return msg_chiffre

    Pour chaque position du message, on additionne les deux indices de cette même position. Si la somme atteint 26, on la ramène dans l’alphabet. On reconvertit la liste entière seulement après la boucle.

    Une clé plus longue est autorisée ; seuls ses premiers len(msg) caractères sont utilisés. La chaîne vide produit une chaîne vide, et aucun accès d’indice n’est effectué.

    Voir la question dans le sujet PDF, p. 12 (nouvel onglet)

    Question 5

    #

    Indiquer et justifier ce qu’on observe avec chiffrement('RESEAU', 'GFTZ').

    Comprendre la correction

    Une AssertionError portant le message impossible est levée dès la ligne 2 : la clé contient 4 caractères, alors que le message en contient 6. L’exécution s’arrête avant le chiffrement ; aucun message chiffré n’est renvoyé.

    Cette réponse suppose l’exécution habituelle de Python, où les assertions sont actives. Une validation de production devrait utiliser un contrôle explicite si elle doit subsister en mode optimisé.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 6

    #

    Déchiffrer GMEDH avec la clé FVEIT.

    Comprendre la correction
    ChiffréCléDifférenceModulo 26Clair
    G : 6F : 511B
    M : 12V : 21-917R
    E : 4E : 400A
    D : 3I : 8-521V
    H : 7T : 19-1214O

    Le message en clair est BRAVO. Une différence négative est augmentée de 26 pour revenir dans l’intervalle des rangs.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 7

    #

    Expliquer comment déchiffrer un message quand on connaît la clé.

    Comprendre la correction

    On associe leurs rangs aux lettres du texte chiffré et de la clé, puis on calcule position par position rang_chiffre - rang_cle modulo 26. On remplace chaque résultat par sa lettre. La soustraction inverse l’addition du chiffrement : (m + k - k) modulo 26 = m.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 8

    #

    Adapter les lignes 6 à 13 pour obtenir dechiffrement. Les paramètres et les lignes 2 à 5 restent inchangés ; le résultat intermédiaire s’appelle indices_msg_dechiffre.

    Indice

    Soustraire inverse l’addition ; un résultat négatif doit revenir dans 0 à 25.

    Comprendre la correction
    def dechiffrement(msg, cle):
        assert len(cle) >= len(msg), 'impossible'
        indices_msg = lettres_vers_indices(msg)
        indices_cle = lettres_vers_indices(cle)
        n = len(msg)
        indices_msg_dechiffre = []
        for k in range(n):
            ind = indices_msg[k] - indices_cle[k]
            if ind < 0:
                ind = ind + 26
            indices_msg_dechiffre.append(ind)
        msg_dechiffre = indices_vers_lettres(indices_msg_dechiffre)
        return msg_dechiffre

    La structure reste la même. Deux changements algorithmiques suffisent : soustraire au lieu d’additionner, puis ajouter 26 si le résultat est négatif. Tous les noms de variables désignant le résultat sont adaptés pour ne pas lire par erreur une ancienne liste.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Partie B : sécurisation des communications

    Question 9

    #

    Expliquer la différence entre chiffrement symétrique et asymétrique.

    Comprendre la correction

    Un chiffrement symétrique utilise un secret partagé entre les interlocuteurs, généralement la même clé pour chiffrer et déchiffrer. Un chiffrement asymétrique utilise une paire liée mathématiquement : une clé publique peut être diffusée, l’autre reste privée. Pour envoyer confidentiellement à Bob, on chiffre avec sa clé publique et il déchiffre avec sa clé privée.

    Cette distinction porte sur les clés et leur distribution. À elle seule, elle ne dit pas que l’identité d’un expéditeur a été prouvée.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 10

    #

    Bob transmet sa clé publique à Alice, qui chiffre son message avec cette clé puis lui envoie le résultat. Comment Bob le déchiffre-t-il ?

    Comprendre la correction

    Bob utilise sa clé privée, associée à la clé publique employée par Alice. Il ne doit pas transmettre cette clé privée à Alice : elle est précisément le secret qui réserve le déchiffrement à Bob.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 11

    #

    Expliquer comment une tierce personne pourrait se faire passer pour Alice sans que Bob s’en aperçoive.

    Comprendre la correction

    La clé publique de Bob est publique : un tiers peut donc chiffrer son propre message avec elle et prétendre être Alice. Le fait que Bob puisse déchiffrer le message prouve qu’il lui était destiné, pas qui l’a écrit.

    Il faut ajouter un mécanisme d’authentification de l’expéditeur, par exemple une signature vérifiée avec une clé publique d’Alice dont l’identité a été établie. La confidentialité et l’authenticité répondent à deux questions distinctes.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 12

    #

    Expliquer brièvement le fonctionnement de HTTPS.

    Comprendre la correction

    HTTPS est HTTP transporté dans une connexion TLS. Le client vérifie le certificat du serveur, notamment le nom de domaine et la chaîne de confiance, puis un échange cryptographique établit des clés de session. Les données HTTP circulent ensuite avec chiffrement symétrique authentifié, qui protège leur confidentialité et permet de détecter les modifications.

    Les mécanismes asymétriques servent notamment à l’authentification et à l’établissement des clés ; le trafic applicatif n’est pas chiffré lettre par lettre avec une clé publique. Les détails exacts de l’échange dépendent de la version et des paramètres TLS.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Question 13

    #

    Expliquer pourquoi on utilise HTTPS pour sécuriser les communications sur Internet plutôt qu’un seul chiffrement asymétrique.

    Comprendre la correction

    HTTPS/TLS assemble plusieurs fonctions nécessaires : authentifier le serveur, établir les clés, protéger la confidentialité et l’intégrité des échanges, tout en utilisant un chiffrement symétrique efficace pour les données. Un simple chiffrement avec une clé publique n’authentifie pas à lui seul l’expéditeur ni le propriétaire réel de cette clé.

    La formulation « intégralement » du sujet doit être nuancée : HTTPS ne garantit pas que le site est honnête et n’authentifie pas automatiquement Alice auprès de Bob. L’identité d’un utilisateur exige encore un mécanisme adapté, comme une connexion à un compte ou un certificat client.

    Voir la question dans le sujet PDF, p. 13 (nouvel onglet)

    Partie C : réseaux

    Bob et Marc travaillent sur 192.168.110.0/24, masque 255.255.255.0. Les trois premiers octets forment le réseau ; le dernier désigne l’hôte. Bob reçoit l’identifiant hôte 115 et Marc 153. Le bloc contient 256 adresses IPv4 au total.

    --- 192.168.100.115 ping statistics ---
    4 packets transmitted, 0 received, 100% packet loss, time 3060ms

    Question 14

    #

    Marc lance ping 192.168.100.115 pour joindre Bob et obtient 4 paquets transmis, 0 reçu, 100 % de perte. Expliquer l’affichage et corriger son erreur.

    Comprendre la correction

    Aucune réponse n’a été reçue pour les quatre requêtes envoyées à l’adresse visée. Marc s’est trompé de réseau : il faut lancer ping 192.168.110.115, puisque le troisième octet du réseau de l’entreprise est 110, et non 100.

    Une absence de réponse ne prouve pas à elle seule que la machine est éteinte : elle peut aussi venir d’un filtrage ou d’une erreur de routage. Ici, l’erreur d’adresse est directement visible dans les données de l’énoncé.

    Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

    L’administratrice sépare le réseau en sous-réseaux reliés par des routeurs. Elle remplace le masque par la valeur binaire indiquée.

    Question 15

    #

    Donner la représentation décimale du nouveau masque 11111111.11111111.11111111.11100000.

    Comprendre la correction

    255.255.255.224. Les trois premiers octets valent 255 ; le dernier vaut 128 + 64 + 32 = 224. Le masque contient 27 bits à 1, soit un préfixe /27.

    Voir la question dans le sujet PDF, p. 14 (nouvel onglet)

    Une adresse réseau s’obtient par ET bit à bit entre masque et adresse. Pour Bob, le dernier octet est 115 :

      11100000 (224, masque)
    & 01110011 (115, Bob)
    = 01100000 (96, réseau)

    Son sous-réseau est donc 192.168.110.96/27.

    Question 16

    #

    Indiquer le nombre total d’adresses IPv4 du sous-réseau 192.168.110.96 où se trouve Bob.

    Comprendre la correction

    Le /27 laisse 32 - 27 = 5 bits pour les hôtes : il contient donc 2⁵ = 32 adresses au total, de .96 à .127.

    Parmi elles, .96 est l’adresse réseau et .127 celle de diffusion : 30 sont attribuables à des interfaces hôtes. Le sujet emploie « total d’adresses pouvant être attribuées » après avoir compté 256 adresses pour un /24 ; on explicite donc les deux nombres pour éviter de confondre capacité totale et hôtes utilisables.

    Voir la question dans le sujet PDF, p. 15 (nouvel onglet)

    Question 18

    #

    Zoé lance n°1 ping 192.168.110.115 et n°2 ping 192.168.110.153. Indiquer et justifier laquelle a produit 4 paquets transmis, 4 reçus, 0 % de perte.

    Indice 1

    Comparez les adresses réseau obtenues par ET avec 224.

    Indice 2

    Être dans deux sous-réseaux distincts impose un routage, pas nécessairement un échec.

    Comprendre la correction
    PosteDernier octetET avec 224Sous-réseau
    Bob11596192.168.110.96/27
    Marc153128192.168.110.128/27
    Zoé134128192.168.110.128/27

    La commande n°2 vise Marc, dans le même sous-réseau que Zoé : c’est la réponse attendue si l’on raisonne sur la communication locale directe.

    Limite de l’énoncé : il dit aussi que les sous-réseaux sont reliés par des routeurs. Avec un routage et des règles de filtrage permettant la communication, la commande n°1 peut également réussir. Les seules adresses ne permettent donc pas de conclure qu’elle a échoué. Le succès n°2 est cohérent avec une liaison locale ; son exclusivité demanderait des informations supplémentaires sur le routage ou le filtrage.

    Voir la question dans le sujet PDF, p. 15 (nouvel onglet)
    Chiffrez, puis retrouvez le messageUn atelier pour expérimenter

    Choisissez une paire du sujet et passez du chiffrement au déchiffrement. Chaque ligne montre l’opération sur les rangs, le passage modulo 26 et la lettre obtenue.

    Lire le résultat de l’expérience initiale

    PGRDX

    Pour être un masque jetable sûr, cette clé devrait être aléatoire, secrète et jamais réutilisée. Les clés fixes du sujet sont seulement des exemples de calcul.

    PositionEntréeCléRésultat brutModulo 26Lettre
    1L (11)E (4)1515P
    2I (8)Y (24)326G
    3B (1)Q (16)1717R
    4R (17)M (12)293D
    5E (4)T (19)2323X

    Le calcul modulo 26 est réversible avec la clé. La sécurité du masque jetable dépend surtout de la génération, du secret et de l’usage unique de cette clé.

    Revoir les notions de cet exercice

    Du sujet à la méthode

    Votre prochaine séance de révision

    • Dessinez les bits sur chaque branche avant de coder : ce sujet emploie 1 à gauche, contrairement à beaucoup d’exemples usuels.
    • Pour un podium, séparez les niveaux d’effectifs des jeux qui appartiennent à chaque niveau.
    • Justifiez les limites de vos conclusions : une preuve de terminaison exige une vraie décroissance ; une adresse dans un autre sous-réseau ne prouve pas un ping impossible.

    Retrouver ces notions dans d’autres sujets

    Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027

    Énoncé : sujet 25-NSIJ2ME1 (PDF). Corrigé et explications pédagogiques proposés par Sofien.