{
  "chapter": {
    "id": "tables-de-donnees",
    "level": "premiere",
    "theme": "Données",
    "title": "Tables de données",
    "description": "Représentation tabulaire des données, descripteurs\n(colonnes), enregistrements (lignes), opérations courantes\n(recherche, tri, sélection, fusion, jointure simple), en\nPython avec listes de dictionnaires ou listes de listes.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Qu'est-ce qu'une table ?",
      "statement": "En informatique, qu'appelle-t-on une **table de données** ?",
      "options": [
        {
          "text": "Un meuble pour poser un ordinateur",
          "correct": false,
          "feedback": "Plaisanterie : on parle bien d'une structure\ninformatique, pas de mobilier.\n"
        },
        {
          "text": "Un type primitif de Python",
          "correct": false,
          "feedback": "Erreur : Python n'a pas de type natif \"table\".\nOn peut la simuler avec des listes ou\ndictionnaires.\n"
        },
        {
          "text": "Un tableau composé de lignes (enregistrements) et de colonnes (descripteurs ou attributs)",
          "correct": true,
          "feedback": "Bonne réponse : c'est la définition standard,\nidentique à celle d'un tableur ou d'une table\nde base de données. Chaque ligne décrit une\nentité (élève, livre...), chaque colonne un\nattribut (nom, âge...).\n"
        },
        {
          "text": "Une fonction Python qui renvoie un dictionnaire",
          "correct": false,
          "feedback": "Erreur : c'est éventuellement un moyen de\n**représenter** une table, pas la définition\nelle-même.\n"
        }
      ],
      "explanation": "Les tables sont la structure la plus simple et la\nplus répandue pour organiser des données : tableurs,\nbases relationnelles, fichiers CSV, DataFrames pandas\nreposent tous sur ce modèle."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "vocabulaire"
      ],
      "title": "Vocabulaire",
      "statement": "Comment appelle-t-on une **colonne** dans une table\nde données ?",
      "options": [
        {
          "text": "Une ligne",
          "correct": false,
          "feedback": "Erreur : confusion ligne/colonne. Une ligne\ndécrit une entité.\n"
        },
        {
          "text": "Une clé primaire",
          "correct": false,
          "feedback": "Erreur : une clé primaire est un **descripteur\nparticulier** qui identifie de manière unique\nchaque ligne. Toutes les colonnes ne sont pas\ndes clés primaires.\n"
        },
        {
          "text": "Un enregistrement",
          "correct": false,
          "feedback": "Erreur : un enregistrement est une **ligne**,\npas une colonne.\n"
        },
        {
          "text": "Un descripteur (ou attribut, ou champ)",
          "correct": true,
          "feedback": "Bonne réponse : la colonne décrit une propriété\ncommune à toutes les entités (par exemple\n\"nom\", \"age\", \"ville\"). Les termes\n\"descripteur\", \"attribut\", \"champ\" sont\nsynonymes selon le contexte.\n"
        }
      ],
      "explanation": "Vocabulaire à maîtriser : ligne = enregistrement\n= entité ; colonne = descripteur = attribut =\nchamp. Selon le manuel, le mot peut varier."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "representation-python"
      ],
      "title": "Liste de dictionnaires",
      "statement": "Comment représente-t-on couramment une table en\nPython (avec en-tête) ?",
      "options": [
        {
          "text": "Comme une simple chaîne de caractères",
          "correct": false,
          "feedback": "Erreur : ce ne serait pas pratique pour\nmanipuler les données.\n"
        },
        {
          "text": "Comme une liste de dictionnaires, chaque dictionnaire représentant une ligne",
          "correct": true,
          "feedback": "Bonne réponse : exemple\n`[{\"nom\":\"Alice\",\"age\":17}, {\"nom\":\"Bob\",\"age\":16}]`.\nC'est ce que renvoie `csv.DictReader` quand on\nle convertit en liste. Très lisible\n(`ligne[\"nom\"]`).\n"
        },
        {
          "text": "Comme une fonction",
          "correct": false,
          "feedback": "Erreur : une fonction décrit un calcul, pas\ndes données.\n"
        },
        {
          "text": "Comme un entier",
          "correct": false,
          "feedback": "Erreur : un entier ne stocke aucune\nstructure.\n"
        }
      ],
      "explanation": "Autres représentations possibles : liste de\ntuples, liste de listes, dict de listes (« colonne\npar colonne »). Le choix dépend de l'usage. Pour\nle NSI Première, on utilise principalement la liste\nde dictionnaires."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "acces-cellule"
      ],
      "title": "Accéder à une cellule",
      "statement": "Soit `table = [{\"nom\":\"Alice\",\"age\":17}, {\"nom\":\"Bob\",\"age\":16}]`.\nComment accéder à l'âge de Bob ?",
      "options": [
        {
          "text": "`table[1]['age']`",
          "correct": true,
          "feedback": "Bonne réponse : Bob est la deuxième ligne\n(indice 1), et `'age'` est le descripteur. On\nobtient `16`.\n"
        },
        {
          "text": "`table['Bob']['age']`",
          "correct": false,
          "feedback": "Erreur : `table` est une **liste**, pas un\ndictionnaire indexé par nom. Cela lève\n`TypeError`.\n"
        },
        {
          "text": "`table[2]['age']`",
          "correct": false,
          "feedback": "Erreur : indice **hors-borne**. Avec deux\nlignes, les indices valides sont 0 et 1, pas\n2.\n"
        },
        {
          "text": "`table.age[1]`",
          "correct": false,
          "feedback": "Erreur : aucune méthode ni attribut `.age` sur\nune liste.\n"
        }
      ],
      "explanation": "Schéma général : `table[indice_ligne][descripteur]`.\nL'ordre des accès reflète la structure : d'abord\nla liste, puis le dictionnaire."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "parcours"
      ],
      "title": "Parcourir une table",
      "statement": "Pour afficher tous les noms d'élèves d'une table\n(liste de dictionnaires avec une clé `\"nom\"`), quel\ncode utilise-t-on ?",
      "options": [
        {
          "text": "```python\nprint(table.nom)\n```\n",
          "correct": false,
          "feedback": "Erreur : pas d'attribut `.nom` sur une liste.\nCela lève `AttributeError`.\n"
        },
        {
          "text": "```python\nfor ligne in table:\n    print(ligne[\"nom\"])\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : la boucle parcourt chaque\ndictionnaire, et on accède à la valeur de la\nclé `\"nom\"`.\n"
        },
        {
          "text": "```python\nfor nom in table:\n    print(nom)\n```\n",
          "correct": false,
          "feedback": "Erreur : la variable `nom` recevrait chaque\n**dictionnaire** entier, pas le nom seulement.\n"
        },
        {
          "text": "```python\nprint(table[\"nom\"])\n```\n",
          "correct": false,
          "feedback": "Erreur : `table` est une liste, on ne peut\npas indexer par chaîne.\n"
        }
      ],
      "explanation": "Schéma classique de parcours d'une liste de\ndictionnaires. À retenir."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "origine-donnees"
      ],
      "title": "Origine des tables",
      "statement": "D'où peut provenir une table de données dans un\nprojet réel ?",
      "options": [
        {
          "text": "D'un fichier (CSV, JSON), d'un tableur, d'une base de données, d'une API web...",
          "correct": true,
          "feedback": "Bonne réponse : les tables sont partout en\ninformatique. Open Data (data.gouv.fr),\nextracts SQL, exports Excel, scraping web...\nautant de sources possibles.\n"
        },
        {
          "text": "Aucune des réponses précédentes",
          "correct": false,
          "feedback": "Erreur : la deuxième proposition est\ncorrecte.\n"
        },
        {
          "text": "Uniquement d'un fichier CSV créé à la main",
          "correct": false,
          "feedback": "Trop restrictif : il existe de nombreuses\nautres sources.\n"
        },
        {
          "text": "Uniquement d'une base de données SQL",
          "correct": false,
          "feedback": "Trop restrictif : SQL est une source\nfréquente mais pas la seule.\n"
        }
      ],
      "explanation": "Une compétence clé du NSI : savoir lire des\ndonnées réelles (souvent en CSV ou JSON), les\ntransformer en table Python, puis les manipuler."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "recherche"
      ],
      "title": "Recherche par critère",
      "statement": "Pour trouver toutes les lignes où l'âge est\nstrictement supérieur à 16, quel code écrit-on ?",
      "options": [
        {
          "text": "```python\nfor ligne in table:\n    if ligne[\"age\"] > 16:\n        print(ligne)\n```\n",
          "correct": false,
          "feedback": "Cela **affiche** les lignes mais ne renvoie\npas une liste. Si la question est \"trouver\",\non attend une valeur de retour, pas un\naffichage.\n"
        },
        {
          "text": "```python\n[ligne for ligne in table if ligne[\"age\"] > 16]\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : compréhension de liste avec\ncondition. Forme idiomatique en Python pour\nfiltrer.\n"
        },
        {
          "text": "```python\ntable.find(age > 16)\n```\n",
          "correct": false,
          "feedback": "Erreur : la méthode `.find()` n'existe pas\nsur les listes.\n"
        },
        {
          "text": "```python\ntable[ligne[\"age\"] > 16]\n```\n",
          "correct": false,
          "feedback": "Erreur : syntaxe invalide. `ligne` n'est pas\ndéfini hors d'une boucle.\n"
        }
      ],
      "explanation": "Compréhension de liste : schéma Python élégant\npour filtrer, transformer et combiner. Très\nutilisé sur les tables."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "csv-vers-table"
      ],
      "title": "Construire depuis CSV",
      "statement": "Quel est le moyen le plus simple de construire une\ntable (liste de dictionnaires) à partir d'un fichier\nCSV avec en-tête ?",
      "options": [
        {
          "text": "Lire le fichier ligne par ligne et coder à la main le découpage",
          "correct": false,
          "feedback": "Possible mais fastidieux et fragile. Il existe\nun outil dédié.\n"
        },
        {
          "text": "`csv.load('data.csv')` (cette fonction existe-t-elle ?)",
          "correct": false,
          "feedback": "Non, cette fonction n'existe pas dans le\nmodule `csv`.\n"
        },
        {
          "text": "`list(csv.DictReader(open('data.csv', encoding='utf-8')))`",
          "correct": true,
          "feedback": "Bonne réponse : `csv.DictReader` produit un\nitérateur de dictionnaires ; `list()` le\nmatérialise en table prête à l'emploi.\n"
        },
        {
          "text": "`json.load(open('data.csv'))`",
          "correct": false,
          "feedback": "Erreur : `json.load` lit du JSON, pas du CSV.\n"
        }
      ],
      "explanation": "Idiome standard. Penser à fermer le fichier\nproprement avec un `with`, ou utiliser cette\nécriture compacte si le fichier est petit."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "taille"
      ],
      "title": "Nombre de lignes",
      "statement": "Comment obtenir le **nombre d'enregistrements**\n(lignes) d'une table représentée comme liste de\ndictionnaires ?",
      "options": [
        {
          "text": "`size(table)`",
          "correct": false,
          "feedback": "Erreur : pas de fonction `size` en Python\nstandard.\n"
        },
        {
          "text": "`len(table.keys())`",
          "correct": false,
          "feedback": "Erreur : `.keys()` n'existe pas sur une\nliste. Cela lève `AttributeError`.\n"
        },
        {
          "text": "`len(table)`",
          "correct": true,
          "feedback": "Bonne réponse : `len()` sur la liste donne le\nnombre de dictionnaires, donc de lignes.\n"
        },
        {
          "text": "`table.count`",
          "correct": false,
          "feedback": "Erreur : `.count` est une **méthode** (qui\ncompte les occurrences d'un élément), il faut\nl'appeler avec des parenthèses et un\nargument.\n"
        }
      ],
      "explanation": "Pour le nombre de **colonnes**, on regarde les\nclés du premier dictionnaire :\n`len(table[0])` (en supposant la table non vide)."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "vide"
      ],
      "title": "Table vide",
      "statement": "Que vaut `len(table)` pour une table vide ?",
      "options": [
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "Erreur : `len([])` renvoie `0` sans erreur.\n"
        },
        {
          "text": "`0`",
          "correct": true,
          "feedback": "Bonne réponse : `[]` (liste vide) contient\nzéro élément.\n"
        },
        {
          "text": "`-1`",
          "correct": false,
          "feedback": "Erreur : `len` n'est jamais négatif.\n"
        },
        {
          "text": "`None`",
          "correct": false,
          "feedback": "Erreur : `len` renvoie toujours un entier.\n"
        }
      ],
      "explanation": "Toujours penser au cas de la table vide dans les\ncalculs : `moyenne = total / len(table)` lèverait\n`ZeroDivisionError`."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "tri"
      ],
      "title": "Trier par descripteur",
      "statement": "Comment trier la table par âge **croissant** ?",
      "options": [
        {
          "text": "`table.sort(key=lambda ligne: ligne['age'])`",
          "correct": true,
          "feedback": "Bonne réponse : `key` indique sur quoi\ncomparer. La lambda renvoie l'âge de chaque\nligne. Tri en place de la liste.\n"
        },
        {
          "text": "`table.sort()`",
          "correct": false,
          "feedback": "Erreur : `sort` ne sait pas comparer deux\ndictionnaires sans préciser sur quel\ndescripteur.\n"
        },
        {
          "text": "`sorted(table, by='age')`",
          "correct": false,
          "feedback": "Erreur : `sorted` n'a pas d'argument `by`.\nC'est `key=` qu'il faut.\n"
        },
        {
          "text": "`table.sort('age')`",
          "correct": false,
          "feedback": "Erreur : `sort` n'accepte pas une chaîne\ncomme premier argument positionnel. Il faut\nle mot-clé `key=`.\n"
        }
      ],
      "explanation": "Pour trier sans modifier la table d'origine :\n`sorted(table, key=lambda r: r['age'])`. Pour un\ntri **décroissant**, ajouter `reverse=True`."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "selection-colonnes"
      ],
      "title": "Sélectionner des colonnes",
      "statement": "Comment construire une nouvelle table ne contenant\nque les colonnes `nom` et `age` (sans les autres\ndescripteurs) ?",
      "options": [
        {
          "text": "`table[['nom', 'age']]`",
          "correct": false,
          "feedback": "Erreur : c'est une syntaxe **pandas**, pas\nPython standard.\n"
        },
        {
          "text": "`table.select('nom', 'age')`",
          "correct": false,
          "feedback": "Erreur : la méthode `.select` n'existe pas\nsur les listes (cela ressemble à du SQL).\n"
        },
        {
          "text": "`[{k: l[k] for k in ['nom', 'age']} for l in table]`",
          "correct": true,
          "feedback": "Bonne réponse : compréhension de liste\nimbriquée avec une compréhension de\ndictionnaire. Pour chaque ligne, on\nreconstruit un dictionnaire ne gardant que les\nclés voulues.\n"
        },
        {
          "text": "`table.keep('nom', 'age')`",
          "correct": false,
          "feedback": "Erreur : pas de méthode `.keep` en Python\nstandard.\n"
        }
      ],
      "explanation": "Cette opération consiste à **extraire un\nsous-ensemble de colonnes** d'une table. C'est\nl'une des manipulations classiques de tables,\nau même titre que la sélection de lignes, le\ntri ou la fusion."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "fusion"
      ],
      "title": "Fusion de tables",
      "statement": "Soient deux tables `t1` et `t2` ayant les **mêmes\ndescripteurs**. Comment obtenir une nouvelle table\ncontenant toutes les lignes de `t1` puis celles de\n`t2` ?",
      "options": [
        {
          "text": "`t1.append(t2)`",
          "correct": false,
          "feedback": "Attention : `append(t2)` ajouterait `t2`\ncomme **un seul élément** (une liste de\nlistes), pas en fusionnant. Il faudrait\nplutôt `extend`.\n"
        },
        {
          "text": "`fusion(t1, t2)`",
          "correct": false,
          "feedback": "Erreur : pas de fonction `fusion` en Python\nstandard.\n"
        },
        {
          "text": "`t1.merge(t2)`",
          "correct": false,
          "feedback": "Erreur : pas de méthode `.merge` sur les\nlistes (cela ressemble à du pandas).\n"
        },
        {
          "text": "`t1 + t2`",
          "correct": true,
          "feedback": "Bonne réponse : la concaténation de listes\n(`+`) crée une nouvelle liste contenant tous\nles éléments. Idiome Python standard.\n"
        }
      ],
      "explanation": "Pour modifier `t1` **en place** au lieu de créer\nune nouvelle table : `t1.extend(t2)` ou\n`t1 += t2`."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "moyenne"
      ],
      "title": "Calculer une moyenne",
      "statement": "Comment calculer la **moyenne** des âges d'une\ntable avec un descripteur `age` (entier) ?",
      "options": [
        {
          "text": "`mean(table.age)`",
          "correct": false,
          "feedback": "Erreur : pas de fonction `mean` en standard,\net pas d'attribut `.age` sur une liste.\n"
        },
        {
          "text": "`table.average('age')`",
          "correct": false,
          "feedback": "Erreur : pas de méthode `.average`.\n"
        },
        {
          "text": "`sum(table['age']) / len(table)`",
          "correct": false,
          "feedback": "Erreur : `table` est une **liste**, on ne\npeut pas l'indexer par chaîne. Lève\n`TypeError`.\n"
        },
        {
          "text": "`sum(ligne['age'] for ligne in table) / len(table)`",
          "correct": true,
          "feedback": "Bonne réponse : on extrait la valeur `age`\npour chaque ligne via une expression\ngénératrice, puis on divise par le nombre de\nlignes.\n"
        }
      ],
      "explanation": "Penser à gérer le cas de la table **vide**\n(division par zéro) : tester `if table:` avant\nle calcul, ou utiliser\n`sum(...) / len(table) if table else 0`."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "jointure-simple"
      ],
      "title": "Jointure simple",
      "statement": "Soient `eleves = [{\"id\":1,\"nom\":\"Alice\"},...]` et\n`notes = [{\"id_eleve\":1,\"matiere\":\"Maths\",\"note\":15},...]`.\nPour associer chaque note au nom de l'élève, on\neffectue une **jointure**. Quel pseudo-code\nl'implémente correctement ?",
      "options": [
        {
          "text": "```python\nnotes + eleves\n```\n",
          "correct": false,
          "feedback": "Erreur : la concaténation ne fait aucune\nmise en correspondance.\n"
        },
        {
          "text": "```python\neleves.merge(notes)\n```\n",
          "correct": false,
          "feedback": "Erreur : pas de méthode `.merge` en Python\nstandard.\n"
        },
        {
          "text": "```python\nfor n in notes:\n    for e in eleves:\n        if n[\"id_eleve\"] == e[\"id\"]:\n            print(e[\"nom\"], n[\"matiere\"], n[\"note\"])\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on parcourt les notes et,\npour chacune, on cherche l'élève\ncorrespondant. Complexité O(n × m).\n"
        },
        {
          "text": "```python\nfor n in notes:\n    print(eleves[n[\"id_eleve\"]][\"nom\"])\n```\n",
          "correct": false,
          "feedback": "Erreur : `eleves[1]` accède à l'**indice 1**\nde la liste, pas à l'élève d'`id` 1.\nCoïncidence possible mais code incorrect en\ngénéral.\n"
        }
      ],
      "explanation": "C'est l'opération de **fusion** de deux tables sur\nune clé commune. Pour gagner en performance, on\npeut indexer `eleves` par `id` dans un dictionnaire\nau préalable."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "conversion-csv"
      ],
      "title": "Pièges de conversion",
      "statement": "Après lecture d'un CSV avec `csv.DictReader`, on\nveut calculer la moyenne des âges. Quel piège\nfaut-il éviter ?",
      "options": [
        {
          "text": "Le module `csv` ne gère pas les fichiers de plus de 100 lignes",
          "correct": false,
          "feedback": "Erreur : il gère sans problème des fichiers de\nplusieurs millions de lignes.\n"
        },
        {
          "text": "Toujours initialiser une variable globale",
          "correct": false,
          "feedback": "Pas un piège spécifique au CSV.\n"
        },
        {
          "text": "Il faut payer une licence pour utiliser `csv.DictReader`",
          "correct": false,
          "feedback": "Erreur : le module `csv` est gratuit et inclus\ndans Python standard.\n"
        },
        {
          "text": "Oublier de convertir les valeurs en `int` (les valeurs lues sont des chaînes)",
          "correct": true,
          "feedback": "Bonne réponse : `csv.DictReader` ne fait\naucune conversion de type. `\"17\" + \"16\"`\ndonne `\"1716\"`, pas `33`. Toujours convertir\navant de calculer.\n"
        }
      ],
      "explanation": "Piège classique chez les débutants : oublier la\nconversion. Symptôme : addition qui concatène\ndes chaînes ou comparaison qui plante."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "comptage-categorie"
      ],
      "title": "Compter par catégorie",
      "statement": "Comment compter le nombre d'élèves dans chaque\nville à partir de `table = [{\"nom\":..., \"ville\":...}, ...]` ?",
      "options": [
        {
          "text": "`set(table['ville'])`",
          "correct": false,
          "feedback": "Erreur : `table` est une liste, on ne peut\npas l'indexer par chaîne.\n"
        },
        {
          "text": "```python\nc = {}\nfor ligne in table:\n    v = ligne[\"ville\"]\n    c[v] = c.get(v, 0) + 1\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on construit progressivement\nun dictionnaire `ville → effectif`. La\nméthode `.get(v, 0)` renvoie `0` si la ville\nn'a pas encore été rencontrée.\n"
        },
        {
          "text": "`len(table)`",
          "correct": false,
          "feedback": "Erreur : cela donne le total, pas la\nrépartition par ville.\n"
        },
        {
          "text": "`table.count('ville')`",
          "correct": false,
          "feedback": "Erreur : `count` cherche un élément exact\ndans la liste, pas un comptage par\ncatégorie.\n"
        }
      ],
      "explanation": "Alternative plus concise avec\n`collections.Counter` :\n`Counter(ligne[\"ville\"] for ligne in table)`.\nMais le schéma manuel est universel."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "tri-multicritere"
      ],
      "title": "Tri sur deux critères",
      "statement": "Comment trier la table par **âge croissant**, puis\npar **nom alphabétique** en cas d'égalité ?",
      "options": [
        {
          "text": "`table.sort(key=lambda l: (l['age'], l['nom']))`",
          "correct": true,
          "feedback": "Bonne réponse : la clé est un **tuple**.\nPython compare lexicographiquement les\ntuples : d'abord par âge, puis par nom en\ncas d'égalité.\n"
        },
        {
          "text": "`table.sort('age', 'nom')`",
          "correct": false,
          "feedback": "Erreur : `sort` n'accepte pas plusieurs\narguments positionnels.\n"
        },
        {
          "text": "`table.sort(key=lambda l: l['age'] + l['nom'])`",
          "correct": false,
          "feedback": "Erreur : on ne peut pas additionner un entier\net une chaîne. Et ce ne serait pas\nsémantiquement correct.\n"
        },
        {
          "text": "`table.sort(by=['age', 'nom'])`",
          "correct": false,
          "feedback": "Erreur : c'est la syntaxe pandas, pas Python\nstandard.\n"
        }
      ],
      "explanation": "Astuce élégante : la comparaison des tuples\nPython est lexicographique, ce qui simplifie\nbeaucoup les tris multi-critères."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "exemple-reel"
      ],
      "title": "Exemple concret",
      "statement": "Lequel des fichiers Open Data suivants se prête\nnaturellement à une représentation en table ?",
      "options": [
        {
          "text": "La liste des stations Vélib' avec leurs coordonnées et capacités, au format CSV",
          "correct": true,
          "feedback": "Bonne réponse : un cas typique. Chaque ligne\n= une station, descripteurs = nom,\nlongitude, latitude, capacité, etc. Idéal\npour traitement en table.\n"
        },
        {
          "text": "Une carte des arrondissements de Paris au format SVG",
          "correct": false,
          "feedback": "Erreur : SVG est un format vectoriel, pas\ntabulaire.\n"
        },
        {
          "text": "Un PDF illustré",
          "correct": false,
          "feedback": "Erreur : un PDF est un format de\nprésentation, mal adapté à la manipulation\ntabulaire (sauf après extraction).\n"
        },
        {
          "text": "Une vidéo de présentation des transports",
          "correct": false,
          "feedback": "Erreur : un fichier multimédia ne se\nstructure pas en table.\n"
        }
      ],
      "explanation": "Open Data : data.gouv.fr, opendata.paris.fr,\nINSEE... regorgent de jeux de données en CSV\ndirectement utilisables."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "structure-imbriquee"
      ],
      "title": "Limite des tables",
      "statement": "Pour quel type de données les tables sont-elles\n**mal adaptées** ?",
      "options": [
        {
          "text": "Une liste de produits avec prix",
          "correct": false,
          "feedback": "Données plates : table parfaitement adaptée.\n"
        },
        {
          "text": "Un classement sportif",
          "correct": false,
          "feedback": "Données plates : table adaptée.\n"
        },
        {
          "text": "Un arbre généalogique sur plusieurs générations avec des relations multiples",
          "correct": true,
          "feedback": "Bonne réponse : les relations\nparent/enfant/conjoint sont **hiérarchiques**.\nLes tables peuvent les stocker, mais ce n'est\npas la représentation la plus naturelle pour\nce type de données.\n"
        },
        {
          "text": "Une liste d'élèves avec nom, âge, ville",
          "correct": false,
          "feedback": "Données plates : parfaitement adaptées à\nune table.\n"
        }
      ],
      "explanation": "Règle générale : pour des données **plates**, la\ntable est adaptée ; pour des données **hiérarchiques**\n(comme un arbre généalogique), un format imbriqué\n(JSON par exemple) est plus naturel."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "trace-acces"
      ],
      "title": "Trace d'accès",
      "statement": "Soit\n```python\nt = [{\"a\":1,\"b\":2}, {\"a\":3,\"b\":4}, {\"a\":5,\"b\":6}]\n```\nQue vaut `sum(l[\"a\"] for l in t if l[\"b\"] >= 4)` ?",
      "options": [
        {
          "text": "12",
          "correct": false,
          "feedback": "Erreur : tu as additionné les `a` de toutes\nles lignes (1 + 3 + 5) sans filtrer.\n"
        },
        {
          "text": "8",
          "correct": true,
          "feedback": "Bonne réponse : les lignes filtrées sont\n`{\"a\":3,\"b\":4}` (b=4 ≥ 4) et `{\"a\":5,\"b\":6}`\n(b=6 ≥ 4). Somme des `a` : 3 + 5 = 8.\n"
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : il y a bien des lignes qui\nsatisfont `l[\"b\"] >= 4`.\n"
        },
        {
          "text": "9",
          "correct": false,
          "feedback": "Erreur : tu as inclus la première ligne\n(a=1) à tort. Or `b=2`, donc le filtre rejette.\n"
        }
      ],
      "explanation": "Méthode : (1) appliquer le filtre `l[\"b\"] >= 4`\nà chaque ligne ; (2) extraire `l[\"a\"]` pour les\nlignes restantes ; (3) sommer."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "transformation"
      ],
      "title": "Transformer la structure",
      "statement": "On veut convertir une **liste de dictionnaires**\n`[{\"x\":1,\"y\":2}, {\"x\":3,\"y\":4}]` en **dictionnaire\nde listes** `{\"x\":[1,3], \"y\":[2,4]}` (représentation\n\"colonne par colonne\"). Quel code est correct ?",
      "options": [
        {
          "text": "```python\n{k: [l[k] for l in table] for k in table[0]}\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : compréhension de\ndictionnaire. Pour chaque clé du premier\ndictionnaire, on construit la liste des\nvaleurs de cette clé sur toutes les lignes.\n"
        },
        {
          "text": "`table.pivot()`",
          "correct": false,
          "feedback": "Erreur : pas de méthode `.pivot` en Python\nstandard (cela existe en pandas).\n"
        },
        {
          "text": "```python\n{k: l[k] for l in table for k in l}\n```\n",
          "correct": false,
          "feedback": "Erreur : on écrase les valeurs à chaque\nitération (chaque clé n'aura que la dernière\nvaleur).\n"
        },
        {
          "text": "`dict(table)`",
          "correct": false,
          "feedback": "Erreur : `dict` ne sait pas faire cette\ntransformation. Lèverait `TypeError` ou\nproduirait un résultat inattendu.\n"
        }
      ],
      "explanation": "Cette transformation est très utile pour passer\nà `numpy`/`matplotlib`, qui préfèrent souvent les\ncolonnes individuelles aux listes\nd'enregistrements."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "optimisation"
      ],
      "title": "Performance d'une jointure",
      "statement": "Pour une jointure entre deux tables de tailles\n`n` et `m`, la version naïve à deux boucles\nimbriquées a une complexité de O(n × m). Comment\nl'améliorer ?",
      "options": [
        {
          "text": "En supprimant la moitié des colonnes",
          "correct": false,
          "feedback": "Erreur : la complexité dépend du **nombre de\nlignes**, pas du nombre de colonnes.\n"
        },
        {
          "text": "En triant les deux tables au préalable",
          "correct": false,
          "feedback": "Possible (jointure par fusion) mais coûteux\nen pré-traitement. Il existe plus simple\nen Python.\n"
        },
        {
          "text": "En indexant une table par sa clé dans un dictionnaire (accès O(1)), ce qui ramène la jointure à O(n + m)",
          "correct": true,
          "feedback": "Bonne réponse : par exemple,\n`idx = {e[\"id\"]: e for e in eleves}`, puis\n`for n in notes: e = idx.get(n[\"id_eleve\"])`.\nBeaucoup plus rapide pour de gros volumes.\n"
        },
        {
          "text": "En utilisant des tuples au lieu de dictionnaires",
          "correct": false,
          "feedback": "Pas une vraie optimisation algorithmique.\n"
        }
      ],
      "explanation": "C'est exactement ce que font les SGBD avec leurs\n**index** : transformer une recherche linéaire\nen accès direct quasi instantané (table de\nhachage ou arbre B)."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "debug"
      ],
      "title": "Bug subtil",
      "statement": "Un élève écrit\n```python\nfor ligne in table:\n    if ligne[\"age\"] > 16:\n        table.remove(ligne)\n```\nQuel est le problème ?",
      "options": [
        {
          "text": "La boucle ne compile pas",
          "correct": false,
          "feedback": "Erreur : la syntaxe est valide, le code\ns'exécute (mais avec un bug logique).\n"
        },
        {
          "text": "Cela lève toujours une exception",
          "correct": false,
          "feedback": "Pas toujours : ça produit un résultat\nincorrect, sans erreur visible.\n"
        },
        {
          "text": "Modifier une liste pendant qu'on la parcourt provoque des lignes ignorées : certains éléments ne seront jamais testés",
          "correct": true,
          "feedback": "Bonne réponse : c'est un piège classique.\nQuand on supprime l'élément d'indice `i`,\nles indices suivants se décalent et le\nparcours saute le suivant. Solution :\nconstruire une nouvelle liste filtrée.\n"
        },
        {
          "text": "La méthode `.remove` n'existe pas",
          "correct": false,
          "feedback": "Erreur : elle existe parfaitement.\n"
        }
      ],
      "explanation": "Bonne pratique : **ne jamais modifier** une\nstructure pendant qu'on la parcourt. Construire\nune nouvelle liste filtrée :\n`table = [l for l in table if l[\"age\"] <= 16]`."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes sur les tables de\ndonnées, laquelle est **fausse** ?",
      "options": [
        {
          "text": "Les opérations classiques sont la recherche, le tri, la sélection de lignes (filtrage), la sélection de colonnes et la fusion",
          "correct": false,
          "feedback": "Vrai : ce sont les opérations canoniques sur\nles tables, prévues par le programme.\n"
        },
        {
          "text": "Une fusion permet de combiner deux tables sur la base d'un descripteur commun",
          "correct": false,
          "feedback": "Vrai : c'est exactement le rôle de la fusion,\nqui rapproche les lignes de deux tables\npartageant une clé.\n"
        },
        {
          "text": "En Python, on représente couramment une table par une liste de dictionnaires",
          "correct": false,
          "feedback": "Vrai : c'est la représentation la plus\nfréquente en NSI Première.\n"
        },
        {
          "text": "Le module `csv` produit toujours des données typées (entiers, flottants, dates)",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : `csv` produit\nuniquement des **chaînes**. C'est au\nprogramme de convertir les types.\n"
        }
      ],
      "explanation": "Les tables de données et leurs opérations\nassociées (recherche, tri, fusion) sont au cœur\ndu traitement de données. Les maîtriser en\nPython est un atout majeur pour\nmanipuler de gros volumes."
    }
  ]
}