NSI Première • Programme français, lycées de Montréal

Fiche de révision : les données en tables en NSI Première

Le chapitre des données en tables est celui où les copies se ressemblent toutes et où le programme donne pourtant trois résultats différents. Presque toutes les fautes du contrôle viennent de là : un nom recopié qu'on croyait indépendant, une colonne restée en texte après la lecture du fichier, un tri qui écrase le précédent.

Cette fiche liste les huit erreurs qui reviennent dans les copies, avec la phrase exacte à écrire à la place et ce que chacune coûte au barème.

Le fil du chapitre

Un nom Python ne contient pas une table, il la DÉSIGNE : tant qu'on n'a pas décidé si l'on copie ou si l'on partage, aucune ligne de traitement de données n'est sûre.

Ce chapitre fait partie de NSI en Première

Avant ce chapitre

Cette fiche suppose ces notions acquises. Si une méthode ci-dessous reste opaque, c'est presque toujours l'une d'elles qui manque, pas la fiche.

Remonter plus loin : la chaîne complète (4 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique et PythonSeconde, Mathématiques
  2. 2Python : types, contrôle, fonctions et tableaux
  3. 3Algorithmique et ScratchQuatrième, Mathématiques
  4. 4Algorithmique et ScratchTroisième, Mathématiques

L'essentiel

Les trois types construits, et ce qui les sépare

  • Le tuple est IMMUABLE : une fois écrit, aucune case ne change. La liste et le dictionnaire sont muables.
  • Seul un objet immuable peut servir de clé de dictionnaire : un tuple oui, une liste non.
  • Une table de données est une LISTE de DICTIONNAIRES : la liste porte les enregistrements, chaque dictionnaire associe un descripteur à une valeur.
  • Un dictionnaire n'a pas d'ordre utile : on n'accède jamais à une colonne par son rang, toujours par son nom.
alias : b = aablistelignecopie de surfaceablistelistelignecopie profondeablistelisteligneligne
À gauche un seul objet pour deux noms, au milieu deux listes mais la même ligne partagée, à droite deux listes et deux lignes distinctes : seul le troisième geste protège les valeurs.

Nommer les objets dans la copie rapporte : « la table est une liste de dictionnaires, un par relevé » vaut souvent le premier demi-point de la question.

Copier : trois gestes, trois résultats

  • b = a ne copie RIEN : c'est un second nom pour le même objet, et toute modification par l'un se voit par l'autre.
  • list(a), a[:] et a.copy() créent une nouvelle liste, mais les éléments restent les mêmes objets : c'est une copie de SURFACE.
  • Sur une table, la copie qui protège les valeurs est [dict(l) for l in table], une copie de surface de chaque ligne.
  • Test simple : si l'on ajoute ou supprime des LIGNES, la copie de surface suffit ; si l'on modifie une VALEUR dans une ligne, elle ne suffit pas.

Écrire en français ce qu'on protège avant de choisir le geste, sinon on empile les copies au hasard.

Les quatre gestes du traitement

  • Filtrer : [l for l in table if condition(l)], une liste plus courte, mêmes descripteurs.
  • Projeter : [{c: l[c] for c in colonnes} for l in table], même longueur, moins de descripteurs.
  • Trier : sorted renvoie une NOUVELLE liste, sort trie en place et renvoie None.
  • Agréger : un dictionnaire dont la clé est le groupe et la valeur l'accumulateur.

Filtrer avant de projeter coûte moins cher, et devient obligatoire dès que la projection supprime la colonne du filtre.

Les coûts qu'on doit savoir citer

  • Accès à une clé de dictionnaire : coût constant, indépendant de la taille.
  • Recherche d'une valeur dans une liste : coût proportionnel à la longueur.
  • Fusionner deux tables de tailles nn et mm avec deux boucles : n×mn \times m comparaisons. Avec un dictionnaire d'index : n+mn + m opérations.
  • Trier : nlog2nn \log_{2} n comparaisons. Trier pour ensuite ne garder que le maximum est un gaspillage, car max coûte nn.

Les pièges qui coûtent des points

Les erreurs ci-dessous sont celles que je corrige le plus souvent en séance. Chacune coûte des points sur une copie, même quand le raisonnement est juste.

1. Croire que b = a recopie la table

2 points, et toutes les questions suivantes qui utilisent la table d'origine

Ce qu'il ne faut pas écrire

« Je fais copie = table puis je modifie copie, comme ça la table d'origine n'est pas touchée. »

Ce qu'il faut écrire

« copie = table ne crée aucun objet : les deux noms désignent la même liste. Pour dupliquer les lignes, j'écris copie = [dict(l) for l in table]. »

Pourquoi : En Python, une affectation lie un nom à un objet, elle ne duplique jamais l'objet. C'est vrai pour les listes, les dictionnaires et les objets, jamais pour les nombres et les chaînes, qui sont immuables et donnent l'illusion inverse.

2. Prendre list(table) pour une vraie copie

toute la question, car le résultat affiché est juste et le contrôle suivant est faux

Ce qu'il ne faut pas écrire

« copie = list(table), donc modifier copie[0]['ville'] ne change pas table. »

Ce qu'il faut écrire

« list(table) copie la LISTE, pas les lignes : copie[0] et table[0] sont le même dictionnaire. Je passe par copie = [dict(l) for l in table]. »

Pourquoi : La copie de surface protège la structure et non le contenu. C'est le piège le plus coûteux du chapitre parce qu'il ne provoque aucune erreur : le programme tourne et donne un mauvais résultat plus loin.

3. Oublier que tout ce qui sort d'un fichier est du TEXTE

2 points, et le podium de la question suivante est faux

Ce qu'il ne faut pas écrire

« sorted(table, key = lambda l: l['temps']) me donne les temps du plus petit au plus grand. »

Ce qu'il faut écrire

« Les valeurs lues valent '1013' et '9', ce sont des chaînes : je convertis la colonne, ou j'écris key = lambda l: float(l['temps']). »

tri sur le texte1013979859tri sur le nombre9979851013même colonne
La même colonne triée deux fois : à gauche comme du texte, où 1013 arrive avant 9, à droite comme des nombres. Rien dans l'affichage ne dit lequel des deux tris a été fait.

Pourquoi : Une chaîne se compare caractère par caractère : 1013 passe avant 9 parce que le caractère 1 précède le caractère 9. Rien ne signale l'erreur, le classement est simplement absurde.

4. Confondre sort et sorted

1 point, plus l'erreur d'exécution qui bloque la suite du programme

Ce qu'il ne faut pas écrire

« classement = table.sort(key = ...) puis j'affiche classement[0]. »

Ce qu'il faut écrire

« table.sort(...) trie en place et renvoie None : soit j'écris table.sort(...) seul, soit classement = sorted(table, key = ...). »

Pourquoi : sort modifie l'argument de l'appelant, ce qui casse aussi toute fonction censée ne rien changer. Une fonction qui trie et qui renvoie doit utiliser sorted.

5. Projeter avant de filtrer

toute la question : le programme lève une KeyError

Ce qu'il ne faut pas écrire

« Je garde d'abord les colonnes nom et note, puis je filtre sur la ville. »

Ce qu'il faut écrire

« Je filtre sur la ville tant que la colonne existe encore, et je projette ensuite : [{c: l[c] for c in garder} for l in table if l['ville'] == 'Québec']. »

Pourquoi : La projection détruit l'information qui n'est pas retenue. Filtrer d'abord est aussi moins coûteux, puisque la projection s'applique alors à moins de lignes.

6. Trier à deux clés en écrasant la première passe

2 points, et le podium est faux dès qu'il y a une égalité

Ce qu'il ne faut pas écrire

« Je trie par temps, puis je trie par nombre d'essais, et j'obtiens le classement à deux critères. »

Ce qu'il faut écrire

« Le tri de Python est STABLE : je commence par la clé la MOINS prioritaire, donc les essais, puis je trie par temps. »

Pourquoi : Chaque tri réordonne tout, mais la stabilité conserve l'ordre relatif des ex aequo. Trier dans le mauvais ordre efface le critère de départage au lieu de l'appliquer.

7. Faire la moyenne des pourcentages de plusieurs groupes

2 points sur la question d'agrégation, qui vaut souvent le quart du devoir

Ce qu'il ne faut pas écrire

« Le groupe A est à 40 pour cent et le groupe B à 80 pour cent, donc le taux global est de 60 pour cent. »

Ce qu'il faut écrire

« Le taux global est la somme des réussites divisée par la somme des effectifs : avec 2 élèves en A et 8 en B, il vaut (0,8+6,4)/10=0,72(0{,}8 + 6{,}4)/10 = 0{,}72, soit 72 pour cent. »

Pourquoi : Un rapport ne s'additionne pas. On somme toujours les numérateurs et les dénominateurs séparément, puis on divise. La moyenne des rapports ne serait juste que si tous les groupes avaient le même effectif.

8. Fusionner deux tables avec deux boucles imbriquées

2 points sur la question de coût, la seule où le raisonnement est noté

Ce qu'il ne faut pas écrire

« Pour chaque ligne de la première table, je parcours toute la seconde jusqu'à trouver la même clé. »

Ce qu'il faut écrire

« Je construis d'abord un index = {l['id']: l for l in table2}, puis je lis index[l['id']] pour chaque ligne : n+mn + m au lieu de n×mn \times m. »

deux boucles imbriquéesA1B1A2B2A3B33 x 3 = 9 comparaisonsun index par cléA1B1A2B2A3B3index3 + 3 = 6 opérations
À gauche chaque ligne de A regarde toutes les lignes de B, à droite chacune passe par l'index construit une seule fois : le nombre de traits est exactement le coût annoncé.

Pourquoi : Une recherche dans une liste coûte la longueur de la liste, alors qu'elle est constante dans un dictionnaire. Sur deux tables de 1 200 et 800 lignes, on passe de 960 000 comparaisons à 2 000 opérations.

9. Utiliser une liste comme clé de dictionnaire

toute la question : TypeError à la première exécution

Ce qu'il ne faut pas écrire

« groupes[[ville, annee]] = ... pour regrouper sur deux colonnes. »

Ce qu'il faut écrire

« Une clé doit être immuable : j'écris groupes[(ville, annee)] = ... avec un TUPLE. »

Pourquoi : Un dictionnaire range ses clés d'après une empreinte calculée sur leur contenu ; si ce contenu pouvait changer, l'entrée deviendrait introuvable. C'est pour cela que seuls les objets immuables sont acceptés.

Quelle méthode choisir

Quelle copie faire, selon ce que le code va modifier

Regarder ce que les lignes suivantes vont modifier, jamais ce que la question a l'air de demander.

  • Si on lit seulement la table aucune copie, on travaille sur la table

    Exemple : compter les lignes d'une ville

  • Si on ajoute ou on supprime des LIGNES copie de surface, list(table) suffit

    Exemple : retirer les enregistrements incomplets

    les lignes restent partagées, mais on ne les modifie pas

  • Si on modifie une VALEUR dans une ligne [dict(l) for l in table]

    Exemple : convertir la colonne des températures en nombres

  • Si une valeur est elle-même une liste copier aussi cette liste, ligne par ligne

    Exemple : une colonne qui contient la liste des notes

    seul cas où une copie profonde complète est nécessaire

Si rien n'est modifié, ne copier surtout pas : une copie inutile double la mémoire et n'est jamais demandée.

Quel geste pour quelle question

Le verbe de l'énoncé, pas le thème des données.

  • Si « combien de lignes vérifient... » filtre par compréhension, puis len

    Exemple : len([l for l in t if l['ville'] == 'Québec'])

  • Si « pour chaque X, le total de Y » dictionnaire d'accumulation indexé par X

    Exemple : le total des ventes par magasin

  • Si « les kk meilleurs » ou « le classement » sorted avec une clé, puis une tranche

    Exemple : sorted(t, key = lambda l: float(l['temps']))[:3]

  • Si « le maximum » ou « le minimum » seulement max ou min avec une clé, jamais un tri

    Exemple : max(t, key = lambda l: float(l['note']))

    coût nn au lieu de nlog2nn \log_{2} n, et le jury le remarque

  • Si « associer les deux tables » index par dictionnaire sur la clé commune

    Exemple : {l['id']: l for l in table2}

  • Si « le taux global » ou « la proportion sur l'ensemble » somme des numérateurs divisée par somme des dénominateurs

    Exemple : (0,8+6,4)/10(0{,}8 + 6{,}4)/10, jamais (0,4+0,8)/2(0{,}4 + 0{,}8)/2

Si aucune branche ne s'applique, c'est presque toujours une composition de deux gestes : filtrer puis agréger.

La rédaction attendue

Le correcteur coche des étapes. Les voici dans l'ordre, avec la phrase de conclusion qu'il attend mot pour mot.

Écrire une fonction qui répond à une question sur une table

Quand l'utiliser : L'énoncé dit « écrire une fonction qui renvoie... » et fournit la structure de la table.

  1. 1 Écrire la signature et la spécification en commentaire : ce que la fonction reçoit, ce qu'elle renvoie, et si elle modifie son argument.
  2. 2 Décider tout de suite si l'on travaille sur la table ou sur une copie, et l'écrire : « la fonction ne modifie pas la table reçue ».
  3. 3 Filtrer, en gardant la colonne du filtre jusqu'à ce qu'elle ait servi.
  4. 4 Traiter, en une compréhension ou en une boucle d'accumulation selon que la sortie est une liste ou un dictionnaire.
  5. 5 Renvoyer, et donner le type de la valeur renvoyée dans la phrase de conclusion.

Phrase de conclusion

« La fonction reçoit la table sous forme de liste de dictionnaires, ne la modifie pas, et renvoie une nouvelle liste de dictionnaires contenant les lignes qui vérifient la condition. »

Le piège : L'étape 2 est celle que tout le monde saute, et c'est exactement celle que le correcteur cherche dans la fonction suivante.

Barème : 1 point de spécification, 2 points de traitement, 1 point pour le type de retour annoncé.

Justifier le coût d'un traitement

Quand l'utiliser : L'énoncé demande « quel est le coût » ou « comparer les deux méthodes ».

  1. 1 Nommer les tailles : nn pour la première table, mm pour la seconde, et dire ce qu'elles comptent.
  2. 2 Dire ce qui est répété et combien de fois : « pour chacune des nn lignes, on parcourt les mm lignes de la seconde table ».
  3. 3 Donner le coût de l'opération élémentaire : constant pour un accès à une clé de dictionnaire, proportionnel à la longueur pour une recherche dans une liste.
  4. 4 Conclure par un ordre de grandeur chiffré sur les tailles de l'énoncé.

Phrase de conclusion

« La première méthode effectue n×mn \times m comparaisons, soit 960 000 pour n=1200n = 1\,200 et m=800m = 800 ; la seconde en effectue n+mn + m, soit 2 000, car chaque accès au dictionnaire d'index est à coût constant. »

Le piège : Écrire « c'est plus rapide » sans chiffrer ne rapporte rien : c'est la comparaison chiffrée qui est notée.

Barème : 1 point pour le coût de chaque méthode, 1 point pour la justification par le coût de l'accès.

Vérifier avant de rendre

Cinq minutes de vérification récupèrent plus de points qu'un exercice de plus commencé à la hâte.

L'exercice type décortiqué

Le podium d'une course, à partir d'un fichier lu ligne à ligne

Un fichier a été lu et transformé en une table resultats, liste de dictionnaires aux descripteurs nom, club, temps et essais. Toutes les valeurs sont des chaînes. Un coureur disqualifié a un temps vide.

Établir le podium : les trois meilleurs temps, et à temps égal le coureur qui a le moins d'essais. La table d'origine ne doit pas être modifiée.

Étape 1

valides = [dict(l) for l in resultats if l['temps'] != '']

Pourquoi

Une seule ligne fait les deux choses attendues : elle écarte les disqualifiés et elle duplique chaque ligne, ce qui autorise la conversion de l'étape suivante sans toucher à la table d'origine.

Étape 2

Pour chaque ligne l de valides : l['temps'] = float(l['temps']) et l['essais'] = int(l['essais'])

Pourquoi

Les valeurs viennent d'un fichier, donc du texte. Sans cette conversion, le tri comparerait des chaînes et placerait 1013 avant 9. C'est ici que se joue le point le plus souvent perdu.

Étape 3

par_essais = sorted(valides, key = lambda l: l['essais'])

Pourquoi

On commence par la clé la MOINS prioritaire. Ce tri n'est pas le résultat, c'est la préparation du départage : il place les ex aequo dans le bon ordre avant que le tri suivant ne les regroupe.

Étape 4

classement = sorted(par_essais, key = lambda l: l['temps'])

Pourquoi

Le tri de Python est stable : à temps égal, l'ordre du tri précédent est conservé, donc le coureur au moins d'essais reste devant. C'est la seule raison d'avoir trié deux fois.

Étape 5

podium = classement[:3]

Pourquoi

Une tranche, pas une boucle : elle ne lève aucune erreur si la table compte moins de trois coureurs, ce qu'une boucle sur range(3) ferait.

Étape 6

Vérification : resultats[0]['temps'] est-il encore une chaîne ?

Pourquoi

C'est le contrôle qui prouve que la copie de l'étape 1 était bien une copie des LIGNES. Si la table d'origine affiche maintenant un nombre, la copie était une copie de surface et la consigne n'est pas respectée.

Conclusion rédigée

« Le podium est classement[:3] : les trois premiers du classement obtenu en triant d'abord par nombre d'essais, puis par temps, la stabilité du tri assurant le départage des ex aequo. La table resultats n'a pas été modifiée. »

L'erreur classique sur cet exercice : Trier d'abord par temps puis par essais : le second tri réordonne toute la table sur les essais et le classement final n'a plus rien à voir avec les temps.

À savoir par cœur

  • Une affectation ne copie JAMAIS : b = a donne deux noms pour un seul objet.
  • list(table) copie la liste, pas les lignes. [dict(l) for l in table] copie les lignes.
  • Tout ce qui sort d'un fichier est du TEXTE : convertir chaque colonne numérique avant de trier ou de sommer.
  • sorted renvoie une nouvelle liste, sort renvoie None et modifie l'argument.
  • Tri à deux clés en deux passes : commencer par la clé la MOINS prioritaire, la stabilité fait le reste.
  • Filtrer AVANT de projeter, toujours.
  • Un taux global est une somme sur une somme, jamais une moyenne de taux.
  • Accès par clé de dictionnaire : coût constant. Recherche dans une liste : coût proportionnel à sa longueur.

Questions fréquentes

Quelle est la différence entre une copie de surface et une copie profonde en Python ?

Une copie de surface crée une nouvelle liste mais garde les mêmes éléments : sur une table, les lignes restent partagées, donc modifier une valeur se voit dans les deux tables. Une copie profonde recrée aussi les éléments. Pour une table, une compréhension qui reconstruit chaque ligne avec dict suffit, sauf si une valeur est elle-même une liste.

Pourquoi mes nombres se trient dans le mauvais ordre après la lecture d'un fichier ?

Parce que les valeurs lues dans un fichier sont des chaînes de caractères, et qu'une chaîne se compare caractère par caractère : 1013 arrive alors avant 9, puisque le chiffre 1 précède le chiffre 9. La correction est de convertir la colonne avec float ou int juste après la lecture, ou de passer la conversion dans la clé de tri.

Faut-il utiliser sort ou sorted pour trier une table en NSI ?

sorted renvoie une nouvelle liste et laisse la table d'origine intacte : c'est ce qu'il faut dans une fonction qui doit renvoyer un résultat. sort trie en place et renvoie None, donc écrire classement égale table point sort donne None. On réserve sort au cas où l'on veut vraiment modifier la table que l'on possède.

Comment trier une table sur deux critères en Python ?

En deux passes, en commençant par le critère le MOINS prioritaire, parce que le tri de Python est stable et conserve l'ordre des ex aequo. Pour classer par temps puis départager par nombre d'essais, on trie d'abord par essais, ensuite par temps. Faire l'inverse efface le départage au lieu de l'appliquer.

Pourquoi fusionner deux tables avec un dictionnaire plutôt qu'avec deux boucles ?

Parce que la recherche d'une clé dans un dictionnaire est à coût constant, alors que la recherche dans une liste parcourt la liste. Avec deux boucles imbriquées sur des tables de 1200 et 800 lignes, on effectue 960000 comparaisons ; en construisant d'abord un index par la clé commune, on n'en effectue que 2000.

Passer à la pratique

Exercices corrigés : Traitement de données en tables et types construits

Une méthode se prouve sur une copie, pas sur une fiche. La série du même chapitre reprend chacun de ces pièges dans un exercice, avec le corrigé rédigé étape par étape.

  • 15 exercices corrigés
  • 150 points
  • 225 minutes
Faire les exercices
Fiche précédente Algorithmique : preuve, terminaison et coût Fiche suivante Interactions homme-machine sur le Web

Ce chapitre resservira dans

Les chapitres qui le réclament en amont, plus tard dans l'année ou dans les années suivantes.

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. On reprend les points de méthode qui font perdre des points en évaluation, puis on les met à l'épreuve sur des exercices du niveau réel de l'examen.

Site par Studio Squalli