Corrigé

Corrigé sujet 2 / Épreuve pratique NSI 2026 : dictionnaires, moyennes et k plus proches voisins

Le sujet 2 de l'épreuve pratique NSI 2026 met en scène les écarts de salaires entre femmes et hommes dans un jeu de 2000 employés. Quatre questions corrigées pas à pas, du parcours de liste de dictionnaires au débogage de l'algorithme des k plus proches voisins.

NSI · Terminale · Épreuve pratique 2026 · 23 sept. 2026

  • assertion
  • tests
Corrigé sujet 2 / Épreuve pratique NSI 2026 : dictionnaires, moyennes et k plus proches voisins

Ce sujet 2 de la session 2026 reprend un sujet zéro et part d'une question de société : l'écart de salaire entre les femmes et les hommes dans une entreprise. Tu vas parcourir des listes de dictionnaires, calculer des moyennes sous condition, débusquer trois bugs dans une fonction fournie, puis découvrir qu'un algorithme des k plus proches voisins peut reproduire une discrimination sans que personne ne l'ait programmée. Une heure d'épreuve pratique, aussi appelée ECE de NSI, pour quatre questions et un fichier analyse.py à compléter.

{{pj:UUID-SUJET|sujet-ep-nsi-2026-sujet-02.pdf}} {{pj:UUID-FICHIERS|fichiers-de-depart-ep-nsi-2026-sujet-02.zip}}

Question 1 / Salaire moyen sous condition

Tu dois écrire une fonction qui calcule le salaire moyen des employés dont un champ donné vaut une valeur donnée, et qui renvoie un nombre flottant, ou None quand personne ne correspond.

Avant de coder, mets-toi les données sous les yeux. Chaque employé est un dictionnaire à quatre champs :

Champ Type Signification
'experience' int années d'expérience professionnelle
'etudes' int années d'études après le bac
'sexe' str 'F' ou 'M'
'salaire' int en euros

Et le jeu de test du fichier donnees.py tient en six lignes :

experience etudes sexe salaire
5 3 F 2400
3 3 M 2550
5 5 F 2500
3 5 M 2800
2 5 F 2300
2 3 M 2700

Le truc, c'est que la fonction reçoit le nom du champ en paramètre. Tu ne peux donc pas écrire employe['sexe'] : tu dois écrire employe[champ], où champ est une variable qui contient une chaîne. Pour Python, c'est pareil. Dans ta tête, pas du tout.

Le reste, c'est une moyenne : un total, un compteur. Et une règle :

La fonction doit renvoyer None s'il n'y a pas d'employés ayant la valeur recherchée pour le champ donné.

Traduite pour la machine : si le compteur est resté à zéro à la fin du parcours, alors on renvoie None, sinon on divise.

La fonction de test est fournie. Qu'est-ce qu'elle attend, au juste ? Je t'encourage à le poser au brouillon avant de coder :

Appel Opération Attendu
tableau vide personne à moyenner None
'sexe', 'F' (2400 + 2500 + 2300) / 3 2400.0
'etudes', 3 (2400 + 2550 + 2700) / 3 2550.0
'etudes', 12 personne n'a 12 ans d'études None
Python
def salaire_moyen_condition(employes, champ, valeur):
    total = 0  # la somme des salaires retenus
    nombre = 0  # le nombre d'employés retenus
    for employe in employes:
        if employe[champ] == valeur:  # champ sans guillemets : c'est le paramètre
            total = total + employe['salaire']  # on ajoute son salaire...
            nombre = nombre + 1  # ... et on le compte
    if nombre == 0:  # personne ne correspond
        return None
    return total / nombre  # '/' renvoie toujours un float

Le if nombre == 0 couvre d'un seul coup le tableau vide et la valeur absente. Et / renvoie toujours un flottant : le type demandé est respecté sans rien ajouter.

Une autre forme beaucoup plus compacte consiste à construire d'abord la liste des salaires retenus :

Python
def salaire_moyen_condition(employes, champ, valeur):
    salaires = [e['salaire'] for e in employes if e[champ] == valeur]
    if len(salaires) == 0:
        return None
    return sum(salaires) / len(salaires)

Elle est jolie. Elle n'est pas plus juste. Le jour J, je te conseille celle que tu sais écrire du premier coup : mieux vaut 'fait' que 'parfait'.

L'énoncé demande aussi les salaires moyens des femmes et des hommes sur le jeu complet. Et comme toujours... on teste son code !

Python
test_salaire_moyen_condition()

complet = donnees_completes.employes
print("Femmes :", round(salaire_moyen_condition(complet, 'sexe', 'F'), 2))
print("Hommes :", round(salaire_moyen_condition(complet, 'sexe', 'M'), 2))
Code
Femmes : 2229.2
Hommes : 2438.04

À l'appel professeur, tu lui dis que ta fonction passe les quatre assertions fournies, et que sur le jeu complet le salaire moyen est de 2229,20 € pour les femmes contre 2438,04 € pour les hommes. Tu lui montres ton terminal : l'appel au test sans aucune erreur, puis les deux valeurs.

Les pièges classiques

  • Ne pas lancer la fonction de test fournie. Elle vérifie justement le tableau vide et la valeur absente, et elle est déjà écrite.
  • Confondre les deux jeux de données. Les deux fichiers exposent une variable employes : si tu annonces 2400 € pour les femmes, tu as répondu sur le jeu de test, pas sur le jeu complet.
  • Oublier la deuxième partie de la consigne. Écrire la fonction ne suffit pas, l'énoncé demande aussi les deux moyennes du jeu complet, et il y a des points dessus.

Question 2 / Compter les femmes et les hommes

On te demande une fonction qui renvoie un dictionnaire à deux clés, 'F' et 'M', associées aux effectifs. Le tableau reçu est non vide : pas de cas particulier à gérer cette fois.

L'idée la plus tentante est de partir d'un dictionnaire vide et d'ajouter 1 au bon sexe à chaque employé :

Python
def effectif_par_sexe(employes):
    effectif = {}
    for employe in employes:
        effectif[employe['sexe']] = effectif[employe['sexe']] + 1
    return effectif

Ça a l'air juste... mais ça plante dès le premier employé, avec KeyError: 'F'. Pour ajouter 1 à effectif['F'], il faut que la clé 'F' existe déjà. Or le dictionnaire est vide.

La réparation est simple : on crée les deux clés, à zéro, avant la boucle.

Python
def effectif_par_sexe(employes):
    effectif = {'F': 0, 'M': 0}  # les deux clés existent dès le départ
    for employe in employes:
        if employe['sexe'] == 'F':
            effectif['F'] = effectif['F'] + 1  # une femme de plus
        else:
            effectif['M'] = effectif['M'] + 1  # un homme de plus
    return effectif  # aligné avec le for, pas dedans

C'est exactement le compteur de la question 1, rangé dans un dictionnaire au lieu d'une variable. Deux clés, deux compteurs : ici, la réponse la plus bête est la bonne.

Et comme toujours... on teste son code !

Python
test_effectif_par_sexe()
print(effectif_par_sexe(donnees.employes))
print(effectif_par_sexe(donnees_completes.employes))
Code
{'F': 3, 'M': 3}
{'F': 984, 'M': 1016}

À l'appel professeur, tu lui dis que ta fonction renvoie {'F': 3, 'M': 3} sur le jeu de test, comme dans l'énoncé, et 984 femmes pour 1016 hommes sur le jeu complet. Tu lui montres les deux lignes affichées.

Les pièges classiques

  • Recopier l'exemple de l'énoncé tel quel dans une assertion. Il se termine par une parenthèse au lieu d'une accolade : Python refuse le fichier, et tu cherches l'erreur dans ta fonction alors qu'elle est dans ton test.
  • Se contenter du jeu de test. Avec 3 femmes et 3 hommes, une fonction qui inverse les deux compteurs passe quand même. Le jeu complet, lui, n'est pas symétrique : c'est lui qui te dit si tu as compté dans le bon sens.
  • Créer le dictionnaire à l'intérieur de la boucle. Il est remis à zéro à chaque tour et tu finis avec un seul employé compté. Invisible tant que tu n'affiches pas le résultat.

Question 3 / Trois bugs dans une fonction

Qu'est-ce qui peut bien clocher dans une fonction de trois lignes ? Ici, trois choses. Mais avant de lire le code fourni, pose d'abord ce qu'il devrait calculer :

écart = (salaire moyen hommes - salaire moyen femmes) / salaire moyen hommes x 100

Sur le jeu de test, au brouillon :

Grandeur Opération Valeur
moyenne hommes (2550 + 2800 + 2700) / 3 2683.33
moyenne femmes (2400 + 2500 + 2300) / 3 2400
écart (2683.33 - 2400) / 2683.33 x 100 10.56

Voici maintenant la fonction telle qu'elle est donnée :

Python
def calcul_ecart_sexe(employes):
    moy_h = salaire_moyen_condition(employes, 'sexe', 'M')
    moy_f = salaire_moyen_condition('employes', 'sexe', 'F')
    return moy_h - moy_f

Premier bug : 'employes' est entre guillemets. Ce n'est plus le tableau, c'est une chaîne de caractères. Ta fonction de la question 1 parcourt donc les lettres e, m, p... et tente 'e'['sexe']. Python répond TypeError: string indices must be integers.

Deuxième bug : la formule. moy_h - moy_f donne une différence en euros, 283.33 sur le jeu de test, pas un pourcentage. Il manque la division par moy_h et la multiplication par 100.

Troisième bug, invisible : aucun garde-fou. Si le tableau ne contient qu'un seul sexe, l'une des deux moyennes vaut None, et la soustraction plante.

Les tests demandés visent exactement les deux derniers : le cas d'un seul sexe présent, et un écart toujours compris entre 0 et 100.

Python
def test_calcul_ecart_sexe():
    e = donnees.employes
    que_des_femmes = [e[0], e[2], e[4]]  # les lignes F du jeu de test
    que_des_hommes = [e[1], e[3], e[5]]  # les lignes M du jeu de test
    assert calcul_ecart_sexe(que_des_femmes) == None
    assert calcul_ecart_sexe(que_des_hommes) == None
    assert calcul_ecart_sexe([]) == None  # aucun des deux sexes
    ecart = calcul_ecart_sexe(e)
    assert 0 <= ecart <= 100  # un pourcentage, pas des euros

Je fabrique les deux sous-tableaux à la main, en piochant les bons indices. Moins élégant qu'un filtrage, mais bien plus rapide à écrire sous pression.

La version corrigée ne touche qu'aux trois défauts :

Python
def calcul_ecart_sexe(employes):
    moy_h = salaire_moyen_condition(employes, 'sexe', 'M')
    moy_f = salaire_moyen_condition(employes, 'sexe', 'F')  # plus de guillemets
    if moy_h == None or moy_f == None:  # un seul sexe présent...
        return None  # ... pas d'écart possible
    return (moy_h - moy_f) / moy_h * 100  # la formule de l'énoncé

Et comme toujours... on teste son code ! On relance aussi le test de la question 1 : calcul_ecart_sexe repose entièrement sur ta première fonction, et si un résultat est faux, tu veux savoir tout de suite si c'est la formule ou la moyenne qui coince.

Python
test_salaire_moyen_condition()
test_calcul_ecart_sexe()
print(round(calcul_ecart_sexe(donnees.employes), 2))
print(round(calcul_ecart_sexe(donnees_completes.employes), 2))
Code
10.56
8.57

Le 10.56 est celui du brouillon : la fonction calcule bien ce qu'on attendait.

À l'appel professeur, tu lui dis que la fonction avait trois défauts, les guillemets autour de employes, des euros au lieu d'un pourcentage et le cas d'un seul sexe non géré, et que l'écart corrigé vaut environ 8,57 % sur les données complètes. Tu lui montres tes assertions qui passent et les deux valeurs affichées.

Les pièges classiques

  • Lancer calcul_ecart_sexe avant d'avoir fini la question 1. Le pass laissé dans le fichier fait renvoyer None aux deux appels, et l'erreur parle de NoneType, sans aucun rapport avec le vrai bug. Tu passes dix minutes à chercher au mauvais endroit.
  • Corriger seulement la ligne qui plante. Le premier bug lève une erreur, il saute aux yeux. Les deux autres sont silencieux : la fonction rend un nombre, mais le mauvais.
  • Oublier la dernière phrase. « En déduire l'écart de salaire moyen dans les données complètes » est une consigne à part entière.

Question 4 / Quand la distance discrimine

Deux candidats se présentent, même expérience, mêmes études :

Python
candidate = {'experience': 3, 'etudes': 3, 'sexe': 'F'}
candidat = {'experience': 3, 'etudes': 3, 'sexe': 'M'}

Quel salaire l'algorithme des k plus proches voisins va-t-il leur proposer ? Lance le fichier tel qu'il t'est fourni, sur le jeu de test :

Python
print(round(salaire_par_proximite(donnees.employes, candidate), 2))
print(round(salaire_par_proximite(donnees.employes, candidat), 2))
Code
2416.67
2683.33

Plus de 266 € d'écart pour deux profils identiques. L'énoncé te demande d'en trouver la source dans le programme.

D'où vient l'écart ?

Les trois voisins retenus pour la candidate sont deux femmes et un homme (2400, 2550 et 2300 €). Ceux du candidat sont trois hommes (2550, 2700 et 2800 €). Les voisins se choisissent donc en partie selon le sexe. Or c'est la fonction distance qui décide qui est proche :

Python
def distance(e1, e2):
    s = 0
    s = s + (sexe_vers_entier(e1) - sexe_vers_entier(e2))**2
    s = s + (e1['experience'] - e2['experience'])**2
    s = s + (e1['etudes'] - e2['etudes'])**2
    return sqrt(s)

sexe_vers_entier renvoie 1 pour une femme et -1 pour un homme. Entre deux personnes de sexe différent, ce terme vaut (1 - (-1))**2, c'est-à-dire 4 : autant que deux ans d'expérience d'écart. Comme les femmes sont déjà moins payées dans les données, la candidate hérite de voisins moins payés. Un algorithme n'invente pas l'injustice : il la recopie.

La fausse bonne idée, c'est de réduire ce poids, par exemple en renvoyant 1 et 0 au lieu de 1 et -1. Ça marche... mais à moitié : sur le jeu de test, la candidate reçoit 2550 € et le candidat toujours 2683,33 €. L'écart passe de 267 à 133 €, il ne disparaît pas. Le sexe n'a pas à peser, même un peu.

La vraie correction tient donc en une ligne supprimée :

Python
def distance(e1, e2):
    s = 0
    s = s + (e1['experience'] - e2['experience'])**2  # l'expérience
    s = s + (e1['etudes'] - e2['etudes'])**2  # les études, et plus rien d'autre
    return sqrt(s)

La fonction sexe_vers_entier n'est plus appelée nulle part. Laisse-la où elle est : l'énoncé ne demande pas de faire le ménage.

Et comme toujours... on teste son code !

Python
assert salaire_par_proximite(donnees.employes, candidate) == salaire_par_proximite(donnees.employes, candidat)
print(round(salaire_par_proximite(donnees.employes, candidate), 2))
print(round(salaire_par_proximite(donnees.employes, candidat), 2))
Code
2550.0
2550.0

À l'appel professeur, tu lui dis que la distance tenait compte du sexe, avec un poids équivalent à deux ans d'expérience, et qu'une fois ce terme retiré les deux candidats reçoivent la même proposition, 2550 € sur le jeu de test. Tu lui montres les deux sorties, avant et après.

Les pièges classiques

  • Chercher le bug dans k_plus_proches ou salaire_par_proximite. Elles ne font que faire confiance à distance. Remonte la chaîne des appels jusqu'à la donnée qui ne devrait pas y être.
  • Ne tester que sur le jeu complet. Des dizaines de profils y sont à distance nulle et sort les départage par leur ordre dans le fichier : la candidate reçoit 2229,67 € avant comme après correction, et tu crois que ta correction n'a rien changé.
  • Corriger sans justifier. La question demande d'identifier la source et de la corriger : l'explication fait partie de la réponse.

Récapitulatif des notions

  • Liste de dictionnaires : parcours, accès par clé variable employe[champ].
  • Accumulateurs : total et compteur dans un même parcours.
  • Dictionnaire de compteurs : clés créées avant la boucle.
  • Assertions : tableau vide, valeur absente, encadrement d'un résultat.
  • Débogage de code fourni : lire toutes les lignes, pas seulement celle qui plante.
  • k plus proches voisins : distance, choix des champs, biais recopié depuis les données.

{{pj:UUID-SUJET|sujet-ep-nsi-2026-sujet-02.pdf}}

{{pj:UUID-FICHIERS|fichiers-de-depart-ep-nsi-2026-sujet-02.zip}}

{{pj:UUID-CORRIGE|analyse_corrige.py}}

Sujet issu de la banque officielle des épreuves pratiques NSI.

Toutes les corrections des épreuves pratiques NSI

Si tu veux travailler ces réflexes avec quelqu'un plutôt que seul devant ton écran, on peut en parler.

Mis à jour le 24 sept. 2026