{
  "chapter": {
    "id": "fichiers-csv",
    "level": "premiere",
    "theme": "Données",
    "title": "Fichiers CSV",
    "description": "Format CSV (Comma Separated Values), séparateurs (virgule,\npoint-virgule, tabulation), lecture et écriture avec le\nmodule `csv`, en-tête, encodage UTF-8, conversion des\ntypes, manipulations courantes.",
    "parties": [
      {
        "id": "format",
        "title": "Le format CSV"
      },
      {
        "id": "lecture",
        "title": "Lire un fichier en Python"
      },
      {
        "id": "traitement",
        "title": "Exploiter les données lues"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Que signifie CSV ?",
      "partie": "format",
      "statement": "Que signifie l'acronyme **CSV** ?",
      "options": [
        {
          "text": "Comma Separated Values",
          "correct": true,
          "feedback": "Bonne réponse : c'est un format texte simple\ndans lequel chaque ligne est un enregistrement\net les champs sont séparés par un délimiteur\n(souvent une virgule, parfois un point-virgule\nou une tabulation).\n"
        },
        {
          "text": "Computer System Variables",
          "correct": false,
          "feedback": "Erreur : aucun rapport. C'est un format de\nfichier de données tabulaires.\n"
        },
        {
          "text": "Compressed Storage Vector",
          "correct": false,
          "feedback": "Erreur : CSV n'est pas un format compressé.\n"
        },
        {
          "text": "Centered Spreadsheet View",
          "correct": false,
          "feedback": "Erreur : ce n'est pas une vue de tableur, c'est\nun format texte.\n"
        }
      ],
      "explanation": "Le CSV est un standard très répandu pour les échanges\nde données entre tableurs, bases de données et\nprogrammes. Sa simplicité (texte brut, ligne par\nligne) en fait un format universel."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "structure"
      ],
      "title": "Structure d'un CSV",
      "partie": "format",
      "statement": "Quelle est la structure de base d'un fichier CSV ?",
      "options": [
        {
          "text": "Un fichier XML structuré par balises",
          "correct": false,
          "feedback": "Erreur : CSV n'a pas de balises, juste des\nséparateurs.\n"
        },
        {
          "text": "Un format de type JSON aux structures imbriquées",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec JSON. CSV est plat.\n"
        },
        {
          "text": "Un fichier texte, un enregistrement par ligne, champs délimités",
          "correct": true,
          "feedback": "Bonne réponse : la première ligne contient\nsouvent les noms des colonnes (en-tête), puis\nchaque ligne suivante représente une entrée du\njeu de données.\n"
        },
        {
          "text": "Un fichier binaire avec en-tête et indexation",
          "correct": false,
          "feedback": "Erreur : CSV est un format **texte**, pas\nbinaire. Il s'ouvre avec n'importe quel éditeur\nde texte.\n"
        }
      ],
      "explanation": "Exemple typique : la première ligne `nom,age,ville`\ndéfinit les colonnes ; les lignes suivantes\ncontiennent les valeurs correspondantes."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "separateurs"
      ],
      "title": "Séparateur en France",
      "partie": "format",
      "statement": "Pourquoi en France utilise-t-on souvent le **point-virgule**\n`;` comme séparateur dans les fichiers CSV ?",
      "options": [
        {
          "text": "Parce qu'une norme ISO officielle l'impose strictement",
          "correct": false,
          "feedback": "Erreur : CSV n'est pas strictement normé. Le\nchoix du séparateur dépend des conventions\nlocales.\n"
        },
        {
          "text": "Parce que les fichiers seraient sinon trop volumineux",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec la taille.\n"
        },
        {
          "text": "Parce que la virgule sert de séparateur décimal en français, créant un conflit",
          "correct": true,
          "feedback": "Bonne réponse : `1,5;Paris` est sans ambiguïté,\nmais `1,5,Paris` ne l'est pas (s'agit-il d'un\nou deux champs ?). Excel et LibreOffice en\nversion française utilisent donc `;` par défaut.\n"
        },
        {
          "text": "Parce que le point-virgule est plus rapide à taper",
          "correct": false,
          "feedback": "Erreur : ce n'est pas une raison de saisie.\n"
        }
      ],
      "explanation": "D'autres séparateurs sont possibles : tabulation\n(TSV), barre verticale `|`, etc. Le module `csv` de\nPython permet de spécifier le délimiteur via\n`delimiter=\";\"`."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "entete"
      ],
      "title": "Ligne d'en-tête",
      "partie": "format",
      "statement": "À quoi sert la **première ligne** d'un fichier CSV ?",
      "options": [
        {
          "text": "Elle indique la taille du fichier",
          "correct": false,
          "feedback": "La taille d'un fichier\nse mesure en octets et\nse lit dans le système\nde fichiers, pas dans\nson contenu. L'en-tête\nd'un CSV donne les noms\ndes colonnes.\n"
        },
        {
          "text": "Elle contient toujours les données du premier enregistrement",
          "correct": false,
          "feedback": "Erreur : par convention, la première ligne\ncontient le plus souvent un **en-tête**, pas\ndes données.\n"
        },
        {
          "text": "Elle est obligatoirement vide",
          "correct": false,
          "feedback": "Erreur : au contraire, elle contient\ngénéralement les noms de colonnes.\n"
        },
        {
          "text": "Elle contient les noms des colonnes (en-tête)",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'usage le plus répandu.\nLes programmes (et `csv.DictReader`) s'en\nservent pour donner un nom à chaque champ.\n"
        }
      ],
      "explanation": "L'en-tête est une **convention**, pas une\nobligation. Certains CSV n'en ont pas (par exemple\ndes exports machine sans description). Il faut alors\nle savoir avant de lire le fichier."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "extension"
      ],
      "title": "Extension de fichier",
      "partie": "format",
      "statement": "Quelle est l'extension habituelle d'un fichier CSV ?",
      "options": [
        {
          "text": ".json",
          "correct": false,
          "feedback": "Erreur : JSON est un autre format de données\n(hiérarchique).\n"
        },
        {
          "text": ".txt",
          "correct": false,
          "feedback": "Erreur : techniquement possible (CSV est du\ntexte), mais l'extension conventionnelle est\ndifférente.\n"
        },
        {
          "text": ".csv",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'extension standard,\nreconnue par les tableurs et les outils de\ntraitement de données.\n"
        },
        {
          "text": ".xlsx",
          "correct": false,
          "feedback": "Erreur : `.xlsx` est le format propriétaire\nd'Excel (binaire compressé), pas CSV.\n"
        }
      ],
      "explanation": "L'extension `.csv` est reconnue par Excel,\nLibreOffice Calc, Google Sheets et la plupart des\nlangages de programmation, qui peuvent\nautomatiquement proposer un import."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "lecture-simple"
      ],
      "title": "Lecture sans le module csv",
      "partie": "lecture",
      "statement": "Sans utiliser le module `csv`, comment peut-on lire\nun fichier CSV simple en Python ?",
      "options": [
        {
          "text": "Avec `open()` puis `.split('\\n')` sur chaque ligne",
          "correct": false,
          "feedback": "Erreur : chaque ligne parcourue ne contient qu'un saut de ligne, à sa fin. Sur `\"Alice,17\\n\"`, `split('\\n')` renvoie `['Alice,17', '']` : les champs ne sont pas séparés.\n"
        },
        {
          "text": "Avec `readlines()` seul, qui sépare déjà les champs",
          "correct": false,
          "feedback": "Erreur : `readlines()` découpe le fichier en\n**lignes**, pas en champs ; il faut encore\nséparer chaque ligne par le délimiteur.\n"
        },
        {
          "text": "Avec `open()` puis `.split(',')` sur chaque ligne",
          "correct": true,
          "feedback": "Bonne réponse : on ouvre le fichier en lecture\net on parcourt ligne par ligne, en séparant\nchaque ligne par le délimiteur. Cela marche\npour les CSV simples mais échoue avec les\nchamps contenant le délimiteur.\n"
        },
        {
          "text": "Avec `open()` puis `.strip(',')` sur chaque ligne",
          "correct": false,
          "feedback": "Erreur : `strip(',')` ne retire que les virgules\nsituées en début et en fin de ligne ; sur\n`\"Alice,17\"`, il renvoie `\"Alice,17\"` inchangé.\nLa ligne n'est pas découpée en champs.\n"
        }
      ],
      "explanation": "Cette approche a des limites : elle ne gère pas les\nchamps entre guillemets, les sauts de ligne dans\nles champs, ou les caractères d'échappement. Pour\ncela, mieux vaut le module `csv`."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "csv-module"
      ],
      "title": "Module standard",
      "partie": "lecture",
      "statement": "Quel module de la bibliothèque standard de Python\nfacilite la manipulation des fichiers CSV ?",
      "options": [
        {
          "text": "`csv`",
          "correct": true,
          "feedback": "Bonne réponse : `import csv` donne accès à `csv.reader` et `csv.DictReader`, qui découpent chaque ligne du fichier en champs.\n"
        },
        {
          "text": "`math`",
          "correct": false,
          "feedback": "Erreur : le module `math` fournit des fonctions mathématiques comme `sqrt` ; il ne lit aucun fichier.\n"
        },
        {
          "text": "`random`",
          "correct": false,
          "feedback": "Erreur : le module `random` produit des nombres au hasard ; il ne sait rien du format CSV.\n"
        },
        {
          "text": "`time`",
          "correct": false,
          "feedback": "Erreur : le module `time` sert à mesurer une durée ou à attendre, pas à lire un fichier.\n"
        }
      ],
      "explanation": "Le module `csv` fait partie de la bibliothèque\nstandard (livré avec Python). Aucune installation\nn'est nécessaire."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "encodage"
      ],
      "title": "Encodage recommandé",
      "partie": "format",
      "statement": "Quel **encodage** est aujourd'hui recommandé pour les\nfichiers CSV ?",
      "options": [
        {
          "text": "ISO-8859-1 uniquement",
          "correct": false,
          "feedback": "Encodage ancien (Latin-1), encore courant en\nEurope mais limité à 256 caractères. À éviter\npour de nouveaux projets.\n"
        },
        {
          "text": "Binaire compressé",
          "correct": false,
          "feedback": "Erreur : un CSV est du texte, pas du binaire.\n"
        },
        {
          "text": "UTF-8",
          "correct": true,
          "feedback": "Bonne réponse : UTF-8 supporte tous les\ncaractères Unicode (français, chinois, emojis...).\nC'est l'encodage standard moderne.\n"
        },
        {
          "text": "ASCII pur",
          "correct": false,
          "feedback": "Erreur : ASCII ne couvre pas les caractères\naccentués (é, à, ç...), inutilisables en\nfrançais.\n"
        }
      ],
      "explanation": "En Python, on précise l'encodage à l'ouverture :\n`open(\"data.csv\", encoding=\"utf-8\")`. Si on omet\ncet argument, Python utilise l'encodage par défaut\ndu système, ce qui peut poser problème entre\nWindows et Linux."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "csv-reader"
      ],
      "title": "Itération avec csv.reader",
      "partie": "lecture",
      "statement": "Que renvoie `csv.reader(f)` lorsqu'on l'utilise dans\nune boucle `for ligne in csv.reader(f):` ?",
      "options": [
        {
          "text": "Un dictionnaire `{nom_colonne: valeur}`",
          "correct": false,
          "feedback": "Erreur : c'est ce que renvoie `csv.DictReader`,\npas `csv.reader`.\n"
        },
        {
          "text": "Une chaîne de caractères pour chaque ligne",
          "correct": false,
          "feedback": "Erreur : pour avoir une chaîne, il suffirait\nde boucler directement sur le fichier `f`.\n"
        },
        {
          "text": "Un tuple immuable",
          "correct": false,
          "feedback": "Erreur : c'est une liste mutable.\n"
        },
        {
          "text": "Une liste des champs de chaque ligne",
          "correct": true,
          "feedback": "Bonne réponse : chaque itération produit une\nliste comme `[\"Alice\", \"17\", \"Lyon\"]`. Tous les\néléments sont des chaînes (il faut convertir\nsi l'on veut des entiers).\n"
        }
      ],
      "explanation": "Penser à convertir les valeurs numériques avec\n`int()` ou `float()` avant tout calcul. CSV est\ntexte : `\"17\" + 1` ne fonctionnera pas."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "csv-reader",
        "indices"
      ],
      "title": "Une cellule de la table chargée",
      "partie": "lecture",
      "statement": "Le fichier `notes.csv` contient :\n```text\nnom,note\nAli,12\nZoe,15\n```\nOn le charge dans une table :\n```python\nimport csv\ntable = []\nwith open(\"notes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        table.append(ligne)\n```\nQue vaut `table[2][1]` ?",
      "options": [
        {
          "text": "`15`",
          "correct": false,
          "feedback": "Erreur : le module `csv` ne convertit rien, toutes les valeurs lues sont des chaînes. Il faudrait écrire `int(table[2][1])` pour obtenir l'entier $15$.\n"
        },
        {
          "text": "`'12'`",
          "correct": false,
          "feedback": "Erreur : tu oublies que l'en-tête est rangé dans la table, à l'indice $0$. La ligne de Ali est `table[1]`, celle de Zoe `table[2]`.\n"
        },
        {
          "text": "`'15'`",
          "correct": true,
          "feedback": "Bonne réponse : la ligne d'en-tête occupe l'indice $0$, donc `table[2]` vaut `['Zoe', '15']`, et son élément d'indice $1$ est la chaîne `'15'`.\n"
        },
        {
          "text": "`'Zoe'`",
          "correct": false,
          "feedback": "Erreur : dans chaque ligne aussi, les indices commencent à $0$. `table[2][0]` vaut `'Zoe'`, et `table[2][1]` désigne la colonne suivante, celle de la note.\n"
        }
      ],
      "explanation": "Une table importée ainsi est un tableau doublement indexé : `table[i][j]` désigne la valeur de la ligne `i` et de la colonne `j`. L'en-tête compte comme une ligne, et chaque valeur reste une chaîne tant qu'on ne la convertit pas.",
      "variants": [
        {
          "statement": "Le fichier `notes.csv` contient :\n```text\nnom,note\nAli,12\nZoe,15\n```\nOn le charge dans une table :\n```python\nimport csv\ntable = []\nwith open(\"notes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        table.append(ligne)\n```\nQue vaut `table[2][1]` ?",
          "options": [
            {
              "text": "`15`",
              "correct": false,
              "feedback": "Erreur : le module `csv` ne convertit rien, toutes les valeurs lues sont des chaînes. Il faudrait écrire `int(table[2][1])` pour obtenir l'entier $15$.\n"
            },
            {
              "text": "`'12'`",
              "correct": false,
              "feedback": "Erreur : tu oublies que l'en-tête est rangé dans la table, à l'indice $0$. La ligne de Ali est `table[1]`, celle de Zoe `table[2]`.\n"
            },
            {
              "text": "`'15'`",
              "correct": true,
              "feedback": "Bonne réponse : la ligne d'en-tête occupe l'indice $0$, donc `table[2]` vaut `['Zoe', '15']`, et son élément d'indice $1$ est la chaîne `'15'`.\n"
            },
            {
              "text": "`'Zoe'`",
              "correct": false,
              "feedback": "Erreur : dans chaque ligne aussi, les indices commencent à $0$. `table[2][0]` vaut `'Zoe'`, et `table[2][1]` désigne la colonne suivante, celle de la note.\n"
            }
          ],
          "explanation": "Une table importée ainsi est un tableau doublement indexé : `table[i][j]` désigne la valeur de la ligne `i` et de la colonne `j`. L'en-tête compte comme une ligne, et chaque valeur reste une chaîne tant qu'on ne la convertit pas."
        },
        {
          "statement": "Le fichier `notes.csv` contient :\n```text\nnom,note\nMax,9\nLou,18\n```\nOn le charge dans une table :\n```python\nimport csv\ntable = []\nwith open(\"notes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        table.append(ligne)\n```\nQue vaut `table[2][1]` ?",
          "options": [
            {
              "text": "`18`",
              "correct": false,
              "feedback": "Erreur : le module `csv` ne convertit rien, toutes les valeurs lues sont des chaînes. Il faudrait écrire `int(table[2][1])` pour obtenir l'entier $18$.\n"
            },
            {
              "text": "`'9'`",
              "correct": false,
              "feedback": "Erreur : tu oublies que l'en-tête est rangé dans la table, à l'indice $0$. La ligne de Max est `table[1]`, celle de Lou `table[2]`.\n"
            },
            {
              "text": "`'18'`",
              "correct": true,
              "feedback": "Bonne réponse : la ligne d'en-tête occupe l'indice $0$, donc `table[2]` vaut `['Lou', '18']`, et son élément d'indice $1$ est la chaîne `'18'`.\n"
            },
            {
              "text": "`'Lou'`",
              "correct": false,
              "feedback": "Erreur : dans chaque ligne aussi, les indices commencent à $0$. `table[2][0]` vaut `'Lou'`, et `table[2][1]` désigne la colonne suivante, celle de la note.\n"
            }
          ],
          "explanation": "Une table importée ainsi est un tableau doublement indexé : `table[i][j]` désigne la valeur de la ligne `i` et de la colonne `j`. L'en-tête compte comme une ligne, et chaque valeur reste une chaîne tant qu'on ne la convertit pas."
        },
        {
          "statement": "Le fichier `notes.csv` contient :\n```text\nnom,note\nAna,14\nTom,11\n```\nOn le charge dans une table :\n```python\nimport csv\ntable = []\nwith open(\"notes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        table.append(ligne)\n```\nQue vaut `table[2][1]` ?",
          "options": [
            {
              "text": "`11`",
              "correct": false,
              "feedback": "Erreur : le module `csv` ne convertit rien, toutes les valeurs lues sont des chaînes. Il faudrait écrire `int(table[2][1])` pour obtenir l'entier $11$.\n"
            },
            {
              "text": "`'14'`",
              "correct": false,
              "feedback": "Erreur : tu oublies que l'en-tête est rangé dans la table, à l'indice $0$. La ligne de Ana est `table[1]`, celle de Tom `table[2]`.\n"
            },
            {
              "text": "`'11'`",
              "correct": true,
              "feedback": "Bonne réponse : la ligne d'en-tête occupe l'indice $0$, donc `table[2]` vaut `['Tom', '11']`, et son élément d'indice $1$ est la chaîne `'11'`.\n"
            },
            {
              "text": "`'Tom'`",
              "correct": false,
              "feedback": "Erreur : dans chaque ligne aussi, les indices commencent à $0$. `table[2][0]` vaut `'Tom'`, et `table[2][1]` désigne la colonne suivante, celle de la note.\n"
            }
          ],
          "explanation": "Une table importée ainsi est un tableau doublement indexé : `table[i][j]` désigne la valeur de la ligne `i` et de la colonne `j`. L'en-tête compte comme une ligne, et chaque valeur reste une chaîne tant qu'on ne la convertit pas."
        }
      ]
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "dictreader"
      ],
      "title": "Différence reader / DictReader",
      "partie": "lecture",
      "statement": "Quelle est la différence entre `csv.reader` et\n`csv.DictReader` ?",
      "options": [
        {
          "text": "La classe `DictReader` lit nettement plus vite les fichiers",
          "correct": false,
          "feedback": "Erreur : la performance n'est pas la\ndifférence essentielle.\n"
        },
        {
          "text": "La fonction `reader` n'existe plus en Python 3",
          "correct": false,
          "feedback": "Erreur : elle existe parfaitement.\n"
        },
        {
          "text": "`reader` produit des listes, `DictReader` des dictionnaires",
          "correct": true,
          "feedback": "Bonne réponse : `DictReader` consomme la\npremière ligne comme en-tête et permet\nensuite d'écrire `ligne[\"nom\"]` au lieu de\n`ligne[0]`. Plus lisible et robuste si l'ordre\ndes colonnes change.\n"
        },
        {
          "text": "`DictReader` ignore la ligne d'en-tête du fichier",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse, il **utilise**\nl'en-tête pour nommer les colonnes.\n"
        }
      ],
      "explanation": "`DictReader` est généralement préférable quand le\nfichier a un en-tête : le code est plus auto-documenté\n(`ligne[\"age\"]` plutôt que `ligne[2]`)."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "conversion-types"
      ],
      "title": "Conversion de types",
      "partie": "traitement",
      "statement": "Après lecture, on obtient `ligne = [\"Alice\", \"17\"]`.\nComment obtenir un âge utilisable comme nombre ?",
      "options": [
        {
          "text": "`age = int(ligne[1])`",
          "correct": true,
          "feedback": "Bonne réponse : `int()` convertit la chaîne\n`\"17\"` en entier `17`, utilisable dans des\ncalculs.\n"
        },
        {
          "text": "`age = list(ligne[1])`",
          "correct": false,
          "feedback": "Erreur : `list(\"17\")` donne `['1', '7']`, pas\nun entier.\n"
        },
        {
          "text": "`age = ligne[1].number()`",
          "correct": false,
          "feedback": "Erreur : la méthode `.number()` n'existe pas\nsur les chaînes.\n"
        },
        {
          "text": "`age = ligne[1]`",
          "correct": false,
          "feedback": "Erreur : `age` reste une **chaîne**. Faire\n`age + 1` lèverait `TypeError`.\n"
        }
      ],
      "explanation": "Toujours convertir explicitement : `int()`,\n`float()`, `bool()` selon le besoin. Penser aussi\nau cas où la cellule est vide (chaîne `\"\"`), qui\nprovoquerait `ValueError` avec `int()`."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "delimiter"
      ],
      "title": "Spécifier le séparateur",
      "partie": "format",
      "statement": "Comment lire un CSV utilisant le **point-virgule**\ncomme séparateur avec le module `csv` ?",
      "options": [
        {
          "text": "`csv.reader(f).set_separator(';')`",
          "correct": false,
          "feedback": "Erreur : cette méthode n'existe pas.\n"
        },
        {
          "text": "`csv.reader(f, delimiter=';')`",
          "correct": true,
          "feedback": "Bonne réponse : on précise le délimiteur via\nl'argument nommé `delimiter`.\n"
        },
        {
          "text": "`csv.reader_semicolon(f)`",
          "correct": false,
          "feedback": "Erreur : cette fonction n'existe pas.\n"
        },
        {
          "text": "`csv.reader(f)` (suffisant, le module détecte tout seul)",
          "correct": false,
          "feedback": "Erreur : par défaut, le séparateur est la virgule. Le module ne devine pas le séparateur, il faut le lui indiquer.\n"
        }
      ],
      "explanation": "Pour les fichiers exportés par Excel français, le\nséparateur est `;`. Pour les CSV anglo-saxons ou\nGoogle Sheets, c'est `,`. Pour TSV (tab-separated),\nc'est `'\\t'`."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "guillemets"
      ],
      "title": "Champs contenant le séparateur",
      "partie": "format",
      "statement": "Comment représente-t-on un champ contenant le\ncaractère séparateur (par exemple une virgule) dans\nun CSV à séparateur virgule ?",
      "options": [
        {
          "text": "On échappe la virgule avec un antislash `\\,`",
          "correct": false,
          "feedback": "Erreur : ce n'est pas la convention CSV\nstandard (qui utilise les guillemets).\n"
        },
        {
          "text": "On supprime la virgule du champ",
          "correct": false,
          "feedback": "Erreur : on perdrait l'information.\n"
        },
        {
          "text": "On change le séparateur pour ce champ uniquement",
          "correct": false,
          "feedback": "Erreur : un fichier CSV utilise un séparateur\nunique pour tout le fichier.\n"
        },
        {
          "text": "On entoure le champ de guillemets doubles : `\"Lyon, France\"`",
          "correct": true,
          "feedback": "Bonne réponse : c'est la convention du format CSV. Le module `csv` gère cette syntaxe automatiquement, en lecture comme en écriture.\n"
        }
      ],
      "explanation": "Et pour un guillemet à l'intérieur d'un champ\n« guillemeté » ? On le double : `\"il dit \"\"bonjour\"\"\"`."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "dictreader",
        "trace"
      ],
      "title": "Trace de lecture",
      "partie": "lecture",
      "statement": "Le fichier `eleves.csv` contient :\n```\nnom;age\nAlice;17\nBob;16\n```\nQue vaut `ligne` au premier passage de la boucle\n`for ligne in csv.DictReader(open(\"eleves.csv\"), delimiter=\";\")` ?",
      "options": [
        {
          "text": "`['Alice', '17']`",
          "correct": false,
          "feedback": "Erreur : ce serait le retour de `csv.reader`,\npas de `csv.DictReader`.\n"
        },
        {
          "text": "`['nom', 'age']`",
          "correct": false,
          "feedback": "Erreur : `DictReader` consomme la première\nligne comme en-tête, elle n'apparaît pas dans\nla boucle.\n"
        },
        {
          "text": "`{'nom': 'Alice', 'age': '17'}`",
          "correct": true,
          "feedback": "Bonne réponse : `DictReader` saute l'en-tête\net associe chaque valeur à son nom de colonne.\nNoter que `'17'` reste une chaîne.\n"
        },
        {
          "text": "`{'Alice': '17', 'Bob': '16'}`",
          "correct": false,
          "feedback": "Erreur : on ne reçoit qu'une ligne à chaque\nitération, pas tout le fichier.\n"
        }
      ],
      "explanation": "Pour récupérer toutes les lignes d'un coup :\n`lignes = list(csv.DictReader(...))`.",
      "variants": [
        {
          "statement": "Le fichier `eleves.csv` contient :\n```\nnom;age\nAlice;17\nBob;16\n```\nQue vaut `ligne` au premier passage de la boucle\n`for ligne in csv.DictReader(open(\"eleves.csv\"), delimiter=\";\")` ?",
          "options": [
            {
              "text": "`['Alice', '17']`",
              "correct": false,
              "feedback": "Erreur : ce serait le retour de `csv.reader`,\npas de `csv.DictReader`.\n"
            },
            {
              "text": "`['nom', 'age']`",
              "correct": false,
              "feedback": "Erreur : `DictReader` consomme la première\nligne comme en-tête, elle n'apparaît pas dans\nla boucle.\n"
            },
            {
              "text": "`{'nom': 'Alice', 'age': '17'}`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` saute l'en-tête\net associe chaque valeur à son nom de colonne.\nNoter que `'17'` reste une chaîne.\n"
            },
            {
              "text": "`{'Alice': '17', 'Bob': '16'}`",
              "correct": false,
              "feedback": "Erreur : on ne reçoit qu'une ligne à chaque\nitération, pas tout le fichier.\n"
            }
          ],
          "explanation": "Pour récupérer toutes les lignes d'un coup :\n`lignes = list(csv.DictReader(...))`."
        },
        {
          "statement": "Le fichier `eleves.csv` contient :\n```\nnom;age\nChloé;15\nDan;18\n```\nQue vaut `ligne` au premier passage de la boucle\n`for ligne in csv.DictReader(open(\"eleves.csv\"), delimiter=\";\")` ?",
          "options": [
            {
              "text": "`['Chloé', '15']`",
              "correct": false,
              "feedback": "Erreur : ce serait le retour de `csv.reader`,\npas de `csv.DictReader`.\n"
            },
            {
              "text": "`['nom', 'age']`",
              "correct": false,
              "feedback": "Erreur : `DictReader` consomme la première\nligne comme en-tête, elle n'apparaît pas dans\nla boucle.\n"
            },
            {
              "text": "`{'nom': 'Chloé', 'age': '15'}`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` saute l'en-tête\net associe chaque valeur à son nom de colonne.\nNoter que `'15'` reste une chaîne.\n"
            },
            {
              "text": "`{'Chloé': '15', 'Dan': '18'}`",
              "correct": false,
              "feedback": "Erreur : on ne reçoit qu'une ligne à chaque\nitération, pas tout le fichier.\n"
            }
          ],
          "explanation": "Pour récupérer toutes les lignes d'un coup :\n`lignes = list(csv.DictReader(...))`."
        },
        {
          "statement": "Le fichier `eleves.csv` contient :\n```\nnom;age\nEmma;16\nFarid;17\n```\nQue vaut `ligne` au premier passage de la boucle\n`for ligne in csv.DictReader(open(\"eleves.csv\"), delimiter=\";\")` ?",
          "options": [
            {
              "text": "`['Emma', '16']`",
              "correct": false,
              "feedback": "Erreur : ce serait le retour de `csv.reader`,\npas de `csv.DictReader`.\n"
            },
            {
              "text": "`['nom', 'age']`",
              "correct": false,
              "feedback": "Erreur : `DictReader` consomme la première\nligne comme en-tête, elle n'apparaît pas dans\nla boucle.\n"
            },
            {
              "text": "`{'nom': 'Emma', 'age': '16'}`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` saute l'en-tête\net associe chaque valeur à son nom de colonne.\nNoter que `'16'` reste une chaîne.\n"
            },
            {
              "text": "`{'Emma': '16', 'Farid': '17'}`",
              "correct": false,
              "feedback": "Erreur : on ne reçoit qu'une ligne à chaque\nitération, pas tout le fichier.\n"
            }
          ],
          "explanation": "Pour récupérer toutes les lignes d'un coup :\n`lignes = list(csv.DictReader(...))`."
        }
      ]
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "filtrage"
      ],
      "title": "Filtrer les données",
      "partie": "traitement",
      "statement": "Comment afficher seulement les élèves de plus de\n16 ans à partir d'un CSV `nom,age` lu via\n`csv.DictReader` ?",
      "options": [
        {
          "text": "```python\nfor ligne in lecteur:\n    if int(ligne[\"age\"] > 16):\n        print(ligne[\"nom\"])\n```\n",
          "correct": false,
          "feedback": "Erreur : la parenthèse est mal placée. Python évalue d'abord `ligne[\"age\"] > 16`, qui compare une chaîne et un entier et lève `TypeError`, avant même d'appeler `int`.\n"
        },
        {
          "text": "```python\nfor ligne in lecteur:\n    if int(ligne[1]) > 16:\n        print(ligne[0])\n```\n",
          "correct": false,
          "feedback": "Erreur : `csv.DictReader` produit des\n**dictionnaires** dont les clés sont les\nen-têtes (`\"nom\"`, `\"age\"`), pas des listes :\n`ligne[1]` lève `KeyError: 1`. L'accès par\nindice vaut pour `csv.reader`.\n"
        },
        {
          "text": "```python\nfor ligne in lecteur:\n    if ligne[\"age\"] > 16:\n        print(ligne[\"nom\"])\n```\n",
          "correct": false,
          "feedback": "Erreur : `ligne[\"age\"]` est une **chaîne**\n(`\"17\"`). En Python 3, comparer une chaîne et\nun entier lève `TypeError`.\n"
        },
        {
          "text": "```python\nfor ligne in lecteur:\n    if int(ligne[\"age\"]) > 16:\n        print(ligne[\"nom\"])\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on convertit l'âge en entier\n(sinon `\"17\" > 16` est invalide en Python 3),\non filtre, on affiche le nom.\n"
        }
      ],
      "explanation": "Toujours convertir avant de comparer numériquement : toutes les valeurs lues par `csv.DictReader` sont des chaînes."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "delimiter",
        "fichier-tabule"
      ],
      "title": "Une virgule dans un fichier tabulé",
      "partie": "format",
      "statement": "Le fichier texte tabulé `contacts.txt` sépare ses champs par une tabulation. Sa première ligne contient `Dupont, Jean`, une tabulation, puis `17`.\n```python\nimport csv\nwith open(\"contacts.txt\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f, delimiter=\"\\t\"):\n        print(ligne)\n```\nQu'affiche ce programme pour la première ligne ?",
      "options": [
        {
          "text": "`['Dupont', ' Jean', '17']`",
          "correct": false,
          "feedback": "Erreur : tu fais couper le lecteur à la fois sur la virgule et sur la tabulation. Avec `delimiter=\"\\t\"`, seule la tabulation sépare les champs.\n"
        },
        {
          "text": "`['Dupont, Jean', '17']`",
          "correct": true,
          "feedback": "Bonne réponse : seul le délimiteur indiqué, la tabulation `\"\\t\"`, sépare les champs. La virgule n'est qu'un caractère ordinaire du premier champ.\n"
        },
        {
          "text": "`['Dupont, Jean\\t17']`",
          "correct": false,
          "feedback": "Erreur : tu supposes que la tabulation n'est pas reconnue, mais `\"\\t\"` désigne justement ce caractère. Passé comme délimiteur, il découpe la ligne en deux champs.\n"
        },
        {
          "text": "Rien, une virgule étant interdite dans un fichier tabulé",
          "correct": false,
          "feedback": "Erreur : un champ de fichier tabulé peut contenir n'importe quel caractère, sauf la tabulation elle-même. C'est même l'intérêt de ce format quand les données contiennent des virgules.\n"
        }
      ],
      "explanation": "Un fichier texte tabulé s'importe comme un fichier CSV, en indiquant `delimiter=\"\\t\"`. Choisir un séparateur absent des données évite de couper un champ par erreur."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "valeurs-manquantes"
      ],
      "title": "Valeurs manquantes",
      "partie": "traitement",
      "statement": "Dans un CSV avec `nom,age,ville` et la ligne\n`Alice,,Lyon`, que vaut `ligne[\"age\"]` après lecture\npar `csv.DictReader` ?",
      "options": [
        {
          "text": "Le programme lève une exception",
          "correct": false,
          "feedback": "Erreur : la lecture se passe sans erreur ;\nc'est seulement la conversion ultérieure\n(`int(\"\")`) qui lèverait `ValueError`.\n"
        },
        {
          "text": "`0`",
          "correct": false,
          "feedback": "Erreur : le module ne fait aucune supposition.\nUne absence n'est pas un zéro.\n"
        },
        {
          "text": "`None`",
          "correct": false,
          "feedback": "Erreur : `csv.DictReader` ne convertit pas\nautomatiquement les vides en `None`.\n"
        },
        {
          "text": "`''` (chaîne vide)",
          "correct": true,
          "feedback": "Bonne réponse : un champ vide entre deux\nséparateurs est lu comme une chaîne vide. Il\nfaut le tester explicitement avant toute\nconversion : `if ligne[\"age\"] != \"\":`.\n"
        }
      ],
      "explanation": "Donnée manquante : il faut tester la valeur avant de la convertir, par exemple avec `if ligne[\"age\"] != \"\":`, puis écarter la ligne ou lui attribuer une valeur choisie."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "export-tableur"
      ],
      "title": "Export depuis tableur",
      "partie": "format",
      "statement": "Quelle situation correspond à un cas d'usage typique\ndu format CSV ?",
      "options": [
        {
          "text": "Échanger un tableau de notes entre Pronote et un tableur",
          "correct": true,
          "feedback": "Bonne réponse : c'est exactement le rôle du\nCSV, un format pivot universel pour les\ndonnées tabulaires, lisible par tous les\noutils.\n"
        },
        {
          "text": "Stocker une vidéo en haute définition",
          "correct": false,
          "feedback": "Erreur : CSV est un format texte, inadapté à\nla vidéo (binaire compressé).\n"
        },
        {
          "text": "Construire une page web interactive",
          "correct": false,
          "feedback": "Erreur : c'est le rôle du HTML/CSS/JS.\n"
        },
        {
          "text": "Programmer le microcontrôleur d'un objet connecté",
          "correct": false,
          "feedback": "Erreur : on programme en C, Python, etc., pas\nen CSV.\n"
        }
      ],
      "explanation": "Force du CSV : universalité. Faiblesses : pas de\ntypes (tout est texte), pas de hiérarchie, pas de\nschéma standard (séparateur, encodage variables)."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "dictreader",
        "p-uplets",
        "conversion"
      ],
      "title": "Des p-uplets construits à la lecture",
      "partie": "lecture",
      "statement": "Le fichier `ventes.csv` contient :\n```text\nproduit,quantite\nstylo,4\ncahier,6\n```\n```python\nimport csv\ntable = []\nwith open(\"ventes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.DictReader(f):\n        table.append((ligne[\"produit\"], int(ligne[\"quantite\"])))\n```\nQue vaut `table[1]` ?",
      "options": [
        {
          "text": "`('stylo', 4)`",
          "correct": false,
          "feedback": "Erreur : les indices commencent à $0$. La ligne du stylo est `table[0]` ; `table[1]` désigne la ligne suivante, celle du cahier.\n"
        },
        {
          "text": "`('cahier', '6')`",
          "correct": false,
          "feedback": "Erreur : tu oublies l'appel à `int`, qui convertit la chaîne `'6'` en entier avant la construction du p-uplet.\n"
        },
        {
          "text": "`{'produit': 'cahier', 'quantite': 6}`",
          "correct": false,
          "feedback": "Erreur : chaque ligne lue est bien un dictionnaire, mais le programme range dans la table un p-uplet construit à partir de deux de ses valeurs, et non le dictionnaire lui-même.\n"
        },
        {
          "text": "`('cahier', 6)`",
          "correct": true,
          "feedback": "Bonne réponse : `DictReader` utilise l'en-tête pour nommer les champs, la table ne reçoit que les deux lignes de données. `table[1]` est la seconde, dont la quantité a été convertie par `int`.\n"
        }
      ],
      "explanation": "Importer une table, c'est souvent la ranger dans un tableau de p-uplets : on choisit les descripteurs utiles et on convertit les valeurs numériques au moment de la lecture, une fois pour toutes.",
      "variants": [
        {
          "statement": "Le fichier `ventes.csv` contient :\n```text\nproduit,quantite\nstylo,4\ncahier,6\n```\n```python\nimport csv\ntable = []\nwith open(\"ventes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.DictReader(f):\n        table.append((ligne[\"produit\"], int(ligne[\"quantite\"])))\n```\nQue vaut `table[1]` ?",
          "options": [
            {
              "text": "`('stylo', 4)`",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$. La ligne du stylo est `table[0]` ; `table[1]` désigne la ligne suivante, celle du cahier.\n"
            },
            {
              "text": "`('cahier', '6')`",
              "correct": false,
              "feedback": "Erreur : tu oublies l'appel à `int`, qui convertit la chaîne `'6'` en entier avant la construction du p-uplet.\n"
            },
            {
              "text": "`{'produit': 'cahier', 'quantite': 6}`",
              "correct": false,
              "feedback": "Erreur : chaque ligne lue est bien un dictionnaire, mais le programme range dans la table un p-uplet construit à partir de deux de ses valeurs, et non le dictionnaire lui-même.\n"
            },
            {
              "text": "`('cahier', 6)`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` utilise l'en-tête pour nommer les champs, la table ne reçoit que les deux lignes de données. `table[1]` est la seconde, dont la quantité a été convertie par `int`.\n"
            }
          ],
          "explanation": "Importer une table, c'est souvent la ranger dans un tableau de p-uplets : on choisit les descripteurs utiles et on convertit les valeurs numériques au moment de la lecture, une fois pour toutes."
        },
        {
          "statement": "Le fichier `ventes.csv` contient :\n```text\nproduit,quantite\ngomme,9\nregle,2\n```\n```python\nimport csv\ntable = []\nwith open(\"ventes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.DictReader(f):\n        table.append((ligne[\"produit\"], int(ligne[\"quantite\"])))\n```\nQue vaut `table[1]` ?",
          "options": [
            {
              "text": "`('gomme', 9)`",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$. La ligne du gomme est `table[0]` ; `table[1]` désigne la ligne suivante, celle du regle.\n"
            },
            {
              "text": "`('regle', '2')`",
              "correct": false,
              "feedback": "Erreur : tu oublies l'appel à `int`, qui convertit la chaîne `'2'` en entier avant la construction du p-uplet.\n"
            },
            {
              "text": "`{'produit': 'regle', 'quantite': 2}`",
              "correct": false,
              "feedback": "Erreur : chaque ligne lue est bien un dictionnaire, mais le programme range dans la table un p-uplet construit à partir de deux de ses valeurs, et non le dictionnaire lui-même.\n"
            },
            {
              "text": "`('regle', 2)`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` utilise l'en-tête pour nommer les champs, la table ne reçoit que les deux lignes de données. `table[1]` est la seconde, dont la quantité a été convertie par `int`.\n"
            }
          ],
          "explanation": "Importer une table, c'est souvent la ranger dans un tableau de p-uplets : on choisit les descripteurs utiles et on convertit les valeurs numériques au moment de la lecture, une fois pour toutes."
        },
        {
          "statement": "Le fichier `ventes.csv` contient :\n```text\nproduit,quantite\ncolle,7\nfeutre,12\n```\n```python\nimport csv\ntable = []\nwith open(\"ventes.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.DictReader(f):\n        table.append((ligne[\"produit\"], int(ligne[\"quantite\"])))\n```\nQue vaut `table[1]` ?",
          "options": [
            {
              "text": "`('colle', 7)`",
              "correct": false,
              "feedback": "Erreur : les indices commencent à $0$. La ligne du colle est `table[0]` ; `table[1]` désigne la ligne suivante, celle du feutre.\n"
            },
            {
              "text": "`('feutre', '12')`",
              "correct": false,
              "feedback": "Erreur : tu oublies l'appel à `int`, qui convertit la chaîne `'12'` en entier avant la construction du p-uplet.\n"
            },
            {
              "text": "`{'produit': 'feutre', 'quantite': 12}`",
              "correct": false,
              "feedback": "Erreur : chaque ligne lue est bien un dictionnaire, mais le programme range dans la table un p-uplet construit à partir de deux de ses valeurs, et non le dictionnaire lui-même.\n"
            },
            {
              "text": "`('feutre', 12)`",
              "correct": true,
              "feedback": "Bonne réponse : `DictReader` utilise l'en-tête pour nommer les champs, la table ne reçoit que les deux lignes de données. `table[1]` est la seconde, dont la quantité a été convertie par `int`.\n"
            }
          ],
          "explanation": "Importer une table, c'est souvent la ranger dans un tableau de p-uplets : on choisit les descripteurs utiles et on convertit les valeurs numériques au moment de la lecture, une fois pour toutes."
        }
      ]
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "debug-encodage"
      ],
      "title": "Bug d'encodage",
      "partie": "format",
      "statement": "Un élève lit un CSV créé sous Windows et obtient\n`CafÃ©` au lieu de `Café`. Quelle est la cause la\nplus probable ?",
      "options": [
        {
          "text": "Python est trop ancien pour gérer l'UTF-8",
          "correct": false,
          "feedback": "Erreur : Python 3 gère parfaitement UTF-8\ndepuis sa sortie.\n"
        },
        {
          "text": "Le fichier de données est partiellement corrompu",
          "correct": false,
          "feedback": "Erreur : le contenu est correct, c'est\nl'**interprétation** de l'encodage qui pose\nproblème.\n"
        },
        {
          "text": "Le fichier UTF-8 est lu comme du Latin-1",
          "correct": true,
          "feedback": "Bonne réponse : `é` en UTF-8 = `0xC3 0xA9`. Lu\ncomme Latin-1, ces deux octets donnent `Ã©`.\nSolution : préciser `encoding=\"utf-8\"` à\nl'ouverture.\n"
        },
        {
          "text": "La connexion internet est instable",
          "correct": false,
          "feedback": "Erreur : aucun rapport, on lit un fichier\nlocal.\n"
        }
      ],
      "explanation": "Indice révélateur : `Ã©` à la place de `é` est la **signature classique** d'une mauvaise interprétation d'UTF-8\nen Latin-1. Il existe d'autres signatures\n(`Ã¨` au lieu de `è`) qui pointent vers le même\nproblème."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "parsing-piege"
      ],
      "title": "Parsing à la main",
      "partie": "lecture",
      "statement": "Pourquoi `ligne.split(',')` est-il **insuffisant**\npour parser correctement la ligne CSV\n`\"Doe, John\",42,\"Lyon, France\"` ?",
      "options": [
        {
          "text": "`split` ne fonctionne que sur des fichiers UTF-8",
          "correct": false,
          "feedback": "Erreur : `split` est indépendant de l'encodage.\n"
        },
        {
          "text": "`split` modifie le fichier d'origine sur le disque",
          "correct": false,
          "feedback": "Erreur : `split` ne modifie rien, il renvoie\nune nouvelle liste.\n"
        },
        {
          "text": "`split` ignore les guillemets et coupe à chaque virgule",
          "correct": true,
          "feedback": "Bonne réponse : on obtient cinq champs au lieu\nde trois, `split(',')` produisant `['\\\"Doe',\n' John\\\"', '42', '\\\"Lyon', ' France\\\"']`. Le\nmodule `csv` gère correctement ce cas en\nregroupant les champs entourés de guillemets.\n"
        },
        {
          "text": "La méthode `split` est beaucoup trop lente ici",
          "correct": false,
          "feedback": "Erreur : la performance n'est pas le\nproblème, la **correction** l'est.\n"
        }
      ],
      "explanation": "Leçon : pour tout CSV non trivial (guillemets,\nsauts de ligne dans les champs, échappements),\nutiliser le module `csv`. Le « parsing manuel »\nn'est correct que pour des cas très simples et\nmaîtrisés."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "statistiques"
      ],
      "title": "Calcul d'une moyenne",
      "partie": "traitement",
      "statement": "Le fichier `notes.csv` contient une colonne `note`\n(entête en première ligne) avec des nombres entiers.\nQuel code calcule correctement la **moyenne** ?",
      "options": [
        {
          "text": "```python\nmoyenne = csv.mean(\"notes.csv\", \"note\")\n```\n",
          "correct": false,
          "feedback": "Erreur : la fonction `csv.mean` n'existe pas.\nLe module `csv` se limite à la lecture/écriture.\n"
        },
        {
          "text": "```python\nwith open(\"notes.csv\") as f:\n    moyenne = sum(csv.reader(f)) / 2\n```\n",
          "correct": false,
          "feedback": "Erreur : `sum` ne sait pas additionner des\nlistes de chaînes ; et la division par 2 est\narbitraire.\n"
        },
        {
          "text": "```python\nwith open(\"notes.csv\") as f:\n    total = 0\n    for ligne in csv.DictReader(f):\n        total += ligne[\"note\"]\n    moyenne = total / len(lignes)\n```\n",
          "correct": false,
          "feedback": "Erreur : `ligne[\"note\"]` est une **chaîne**.\n`total += \"12\"` lève `TypeError`. De plus\n`lignes` n'est pas défini.\n"
        },
        {
          "text": "```python\nwith open(\"notes.csv\") as f:\n    notes = [int(ligne[\"note\"]) for ligne in csv.DictReader(f)]\nmoyenne = sum(notes) / len(notes)\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on construit une liste\nd'entiers (conversion explicite avec `int`),\npuis `sum / len`. Code concis et correct.\n"
        }
      ],
      "explanation": "Trois étapes systématiques : ouvrir, convertir\nles types, calculer. Toujours penser à la\nconversion en numérique avant tout calcul."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "csv-reader",
        "dictionnaires",
        "construction"
      ],
      "title": "Une table de dictionnaires construite à la main",
      "partie": "format",
      "statement": "Sans utiliser `DictReader`, Hugo associe lui-même l'en-tête du fichier `eleves.csv` aux valeurs :\n```text\nnom,classe\nAli,1A\nZoe,1B\n```\n```python\nimport csv\nlignes = []\nwith open(\"eleves.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        lignes.append(ligne)\nentete = lignes[0]\ntable = []\nfor i in range(1, len(lignes)):\n    d = {}\n    for j in range(len(entete)):\n        d[entete[j]] = lignes[i][j]\n    table.append(d)\nprint(table[1])\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`{'nom': 'Ali', 'classe': '1A'}`",
          "correct": false,
          "feedback": "Erreur : tu fais correspondre `table[1]` à `lignes[1]`. Comme l'en-tête n'est pas recopié dans `table`, les indices sont décalés d'un cran : la ligne de Ali devient `table[0]`.\n"
        },
        {
          "text": "`{'Ali': '1A', 'Zoe': '1B'}`",
          "correct": false,
          "feedback": "Erreur : tu prends les valeurs de la première colonne pour des clés. Les clés viennent de `entete[j]`, c'est-à-dire `'nom'` et `'classe'`, et chaque ligne donne son propre dictionnaire.\n"
        },
        {
          "text": "`{'nom': 'Zoe', 'classe': '1B'}`",
          "correct": true,
          "feedback": "Bonne réponse : la boucle sur `i` commence à $1$ et saute l'en-tête. `table[0]` est construit à partir de la ligne de Ali, `table[1]` à partir de celle de Zoe, avec pour clés les noms de colonnes.\n"
        },
        {
          "text": "`['Zoe', '1B']`",
          "correct": false,
          "feedback": "Erreur : `table` ne reçoit pas les listes lues, mais le dictionnaire `d` construit à chaque tour de la boucle sur `i`.\n"
        }
      ],
      "explanation": "Associer l'en-tête aux valeurs transforme un tableau doublement indexé en table de dictionnaires : pour chaque ligne de données, la valeur de la colonne `j` est rangée sous la clé `entete[j]`. C'est le travail que fait `csv.DictReader`.",
      "variants": [
        {
          "statement": "Sans utiliser `DictReader`, Hugo associe lui-même l'en-tête du fichier `eleves.csv` aux valeurs :\n```text\nnom,classe\nAli,1A\nZoe,1B\n```\n```python\nimport csv\nlignes = []\nwith open(\"eleves.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        lignes.append(ligne)\nentete = lignes[0]\ntable = []\nfor i in range(1, len(lignes)):\n    d = {}\n    for j in range(len(entete)):\n        d[entete[j]] = lignes[i][j]\n    table.append(d)\nprint(table[1])\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`{'nom': 'Ali', 'classe': '1A'}`",
              "correct": false,
              "feedback": "Erreur : tu fais correspondre `table[1]` à `lignes[1]`. Comme l'en-tête n'est pas recopié dans `table`, les indices sont décalés d'un cran : la ligne de Ali devient `table[0]`.\n"
            },
            {
              "text": "`{'Ali': '1A', 'Zoe': '1B'}`",
              "correct": false,
              "feedback": "Erreur : tu prends les valeurs de la première colonne pour des clés. Les clés viennent de `entete[j]`, c'est-à-dire `'nom'` et `'classe'`, et chaque ligne donne son propre dictionnaire.\n"
            },
            {
              "text": "`{'nom': 'Zoe', 'classe': '1B'}`",
              "correct": true,
              "feedback": "Bonne réponse : la boucle sur `i` commence à $1$ et saute l'en-tête. `table[0]` est construit à partir de la ligne de Ali, `table[1]` à partir de celle de Zoe, avec pour clés les noms de colonnes.\n"
            },
            {
              "text": "`['Zoe', '1B']`",
              "correct": false,
              "feedback": "Erreur : `table` ne reçoit pas les listes lues, mais le dictionnaire `d` construit à chaque tour de la boucle sur `i`.\n"
            }
          ],
          "explanation": "Associer l'en-tête aux valeurs transforme un tableau doublement indexé en table de dictionnaires : pour chaque ligne de données, la valeur de la colonne `j` est rangée sous la clé `entete[j]`. C'est le travail que fait `csv.DictReader`."
        },
        {
          "statement": "Sans utiliser `DictReader`, Hugo associe lui-même l'en-tête du fichier `eleves.csv` aux valeurs :\n```text\nnom,classe\nMax,2B\nLou,2A\n```\n```python\nimport csv\nlignes = []\nwith open(\"eleves.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        lignes.append(ligne)\nentete = lignes[0]\ntable = []\nfor i in range(1, len(lignes)):\n    d = {}\n    for j in range(len(entete)):\n        d[entete[j]] = lignes[i][j]\n    table.append(d)\nprint(table[1])\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`{'nom': 'Max', 'classe': '2B'}`",
              "correct": false,
              "feedback": "Erreur : tu fais correspondre `table[1]` à `lignes[1]`. Comme l'en-tête n'est pas recopié dans `table`, les indices sont décalés d'un cran : la ligne de Max devient `table[0]`.\n"
            },
            {
              "text": "`{'Max': '2B', 'Lou': '2A'}`",
              "correct": false,
              "feedback": "Erreur : tu prends les valeurs de la première colonne pour des clés. Les clés viennent de `entete[j]`, c'est-à-dire `'nom'` et `'classe'`, et chaque ligne donne son propre dictionnaire.\n"
            },
            {
              "text": "`{'nom': 'Lou', 'classe': '2A'}`",
              "correct": true,
              "feedback": "Bonne réponse : la boucle sur `i` commence à $1$ et saute l'en-tête. `table[0]` est construit à partir de la ligne de Max, `table[1]` à partir de celle de Lou, avec pour clés les noms de colonnes.\n"
            },
            {
              "text": "`['Lou', '2A']`",
              "correct": false,
              "feedback": "Erreur : `table` ne reçoit pas les listes lues, mais le dictionnaire `d` construit à chaque tour de la boucle sur `i`.\n"
            }
          ],
          "explanation": "Associer l'en-tête aux valeurs transforme un tableau doublement indexé en table de dictionnaires : pour chaque ligne de données, la valeur de la colonne `j` est rangée sous la clé `entete[j]`. C'est le travail que fait `csv.DictReader`."
        },
        {
          "statement": "Sans utiliser `DictReader`, Hugo associe lui-même l'en-tête du fichier `eleves.csv` aux valeurs :\n```text\nnom,classe\nAna,TG1\nTom,TG3\n```\n```python\nimport csv\nlignes = []\nwith open(\"eleves.csv\", encoding=\"utf-8\") as f:\n    for ligne in csv.reader(f):\n        lignes.append(ligne)\nentete = lignes[0]\ntable = []\nfor i in range(1, len(lignes)):\n    d = {}\n    for j in range(len(entete)):\n        d[entete[j]] = lignes[i][j]\n    table.append(d)\nprint(table[1])\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`{'nom': 'Ana', 'classe': 'TG1'}`",
              "correct": false,
              "feedback": "Erreur : tu fais correspondre `table[1]` à `lignes[1]`. Comme l'en-tête n'est pas recopié dans `table`, les indices sont décalés d'un cran : la ligne de Ana devient `table[0]`.\n"
            },
            {
              "text": "`{'Ana': 'TG1', 'Tom': 'TG3'}`",
              "correct": false,
              "feedback": "Erreur : tu prends les valeurs de la première colonne pour des clés. Les clés viennent de `entete[j]`, c'est-à-dire `'nom'` et `'classe'`, et chaque ligne donne son propre dictionnaire.\n"
            },
            {
              "text": "`{'nom': 'Tom', 'classe': 'TG3'}`",
              "correct": true,
              "feedback": "Bonne réponse : la boucle sur `i` commence à $1$ et saute l'en-tête. `table[0]` est construit à partir de la ligne de Ana, `table[1]` à partir de celle de Tom, avec pour clés les noms de colonnes.\n"
            },
            {
              "text": "`['Tom', 'TG3']`",
              "correct": false,
              "feedback": "Erreur : `table` ne reçoit pas les listes lues, mais le dictionnaire `d` construit à chaque tour de la boucle sur `i`.\n"
            }
          ],
          "explanation": "Associer l'en-tête aux valeurs transforme un tableau doublement indexé en table de dictionnaires : pour chaque ligne de données, la valeur de la colonne `j` est rangée sous la clé `entete[j]`. C'est le travail que fait `csv.DictReader`."
        }
      ]
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Limite du CSV",
      "partie": "traitement",
      "statement": "Parmi les affirmations suivantes sur le format CSV,\nlaquelle est **fausse** ?",
      "options": [
        {
          "text": "CSV impose un typage explicite des colonnes (entier, flottant, date)",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : tout est traité\ncomme du **texte** dans un CSV. Aucun type\nn'est conservé. C'est au programme de lecture\nde convertir explicitement.\n"
        },
        {
          "text": "CSV ne gère pas nativement les structures hiérarchiques (listes imbriquées, dictionnaires)",
          "correct": false,
          "feedback": "Vrai : c'est sa principale limite, un fichier CSV ne représente qu'une table à deux dimensions, lignes et colonnes.\n"
        },
        {
          "text": "CSV est un format texte, lisible avec n'importe quel éditeur",
          "correct": false,
          "feedback": "Vrai : c'est l'un de ses grands avantages.\n"
        },
        {
          "text": "Le séparateur n'est pas standardisé universellement (virgule, point-virgule, tabulation...)",
          "correct": false,
          "feedback": "Vrai : c'est une faiblesse historique du format. Selon le logiciel, on trouve une virgule, un point-virgule ou une tabulation.\n"
        }
      ],
      "explanation": "Limites du CSV : pas de typage, pas de hiérarchie, séparateur variable, encodage variable. C'est au programme qui lit le fichier de connaître ces conventions et de convertir les valeurs."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "conversion",
        "somme",
        "dictionnaire"
      ],
      "title": "Additionner une colonne lue en texte",
      "partie": "traitement",
      "statement": "Une table lue depuis un fichier CSV contient trois lignes :\n```python\ntable = [{\"nom\": \"Lou\", \"points\": \"12\"},\n         {\"nom\": \"Sami\", \"points\": \"30\"},\n         {\"nom\": \"Inès\", \"points\": \"8\"}]\ntotal = 0\nfor ligne in table:\n    total = total + int(ligne[\"points\"])\nprint(total)\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`50`",
          "correct": true,
          "feedback": "Bonne réponse : chaque valeur, lue comme une chaîne, est\nconvertie en entier par `int` avant l'addition.\n"
        },
        {
          "text": "`12308`",
          "correct": false,
          "feedback": "Erreur : sans `int`, `+` collerait les chaînes, mais ici la\nconversion est bien faite.\n"
        },
        {
          "text": "Une erreur, les points étant des chaînes",
          "correct": false,
          "feedback": "Erreur : c'est ce qui arriverait sans `int`. La conversion rend\nl'addition possible.\n"
        },
        {
          "text": "`0`",
          "correct": false,
          "feedback": "Erreur : `total` est augmenté à chaque ligne.\n"
        }
      ],
      "explanation": "Tout ce qui sort d'un fichier CSV est du texte. Convertir les\nnombres avec `int` ou `float` est la première étape de tout calcul.",
      "variants": [
        {
          "statement": "Une table lue depuis un fichier CSV contient trois lignes :\n```python\ntable = [{\"nom\": \"Lou\", \"points\": \"12\"},\n         {\"nom\": \"Sami\", \"points\": \"30\"},\n         {\"nom\": \"Inès\", \"points\": \"8\"}]\ntotal = 0\nfor ligne in table:\n    total = total + int(ligne[\"points\"])\nprint(total)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`50`",
              "correct": true,
              "feedback": "Bonne réponse : chaque valeur, lue comme une chaîne, est\nconvertie en entier par `int` avant l'addition.\n"
            },
            {
              "text": "`12308`",
              "correct": false,
              "feedback": "Erreur : sans `int`, `+` collerait les chaînes, mais ici la\nconversion est bien faite.\n"
            },
            {
              "text": "Une erreur, les points étant des chaînes",
              "correct": false,
              "feedback": "Erreur : c'est ce qui arriverait sans `int`. La conversion rend\nl'addition possible.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : `total` est augmenté à chaque ligne.\n"
            }
          ],
          "explanation": "Tout ce qui sort d'un fichier CSV est du texte. Convertir les\nnombres avec `int` ou `float` est la première étape de tout calcul."
        },
        {
          "statement": "Une table lue depuis un fichier CSV contient trois lignes :\n```python\ntable = [{\"nom\": \"Lou\", \"points\": \"5\"},\n         {\"nom\": \"Sami\", \"points\": \"15\"},\n         {\"nom\": \"Inès\", \"points\": \"25\"}]\ntotal = 0\nfor ligne in table:\n    total = total + int(ligne[\"points\"])\nprint(total)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`45`",
              "correct": true,
              "feedback": "Bonne réponse : chaque valeur, lue comme une chaîne, est\nconvertie en entier par `int` avant l'addition.\n"
            },
            {
              "text": "`51525`",
              "correct": false,
              "feedback": "Erreur : sans `int`, `+` collerait les chaînes, mais ici la\nconversion est bien faite.\n"
            },
            {
              "text": "Une erreur, les points étant des chaînes",
              "correct": false,
              "feedback": "Erreur : c'est ce qui arriverait sans `int`. La conversion rend\nl'addition possible.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : `total` est augmenté à chaque ligne.\n"
            }
          ],
          "explanation": "Tout ce qui sort d'un fichier CSV est du texte. Convertir les\nnombres avec `int` ou `float` est la première étape de tout calcul."
        },
        {
          "statement": "Une table lue depuis un fichier CSV contient trois lignes :\n```python\ntable = [{\"nom\": \"Lou\", \"points\": \"40\"},\n         {\"nom\": \"Sami\", \"points\": \"2\"},\n         {\"nom\": \"Inès\", \"points\": \"11\"}]\ntotal = 0\nfor ligne in table:\n    total = total + int(ligne[\"points\"])\nprint(total)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`53`",
              "correct": true,
              "feedback": "Bonne réponse : chaque valeur, lue comme une chaîne, est\nconvertie en entier par `int` avant l'addition.\n"
            },
            {
              "text": "`40211`",
              "correct": false,
              "feedback": "Erreur : sans `int`, `+` collerait les chaînes, mais ici la\nconversion est bien faite.\n"
            },
            {
              "text": "Une erreur, les points étant des chaînes",
              "correct": false,
              "feedback": "Erreur : c'est ce qui arriverait sans `int`. La conversion rend\nl'addition possible.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : `total` est augmenté à chaque ligne.\n"
            }
          ],
          "explanation": "Tout ce qui sort d'un fichier CSV est du texte. Convertir les\nnombres avec `int` ou `float` est la première étape de tout calcul."
        }
      ]
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "filtre",
        "dictionnaire",
        "comptage"
      ],
      "title": "Compter les lignes d'une ville",
      "partie": "traitement",
      "statement": "La table contient quatre lignes dont les villes sont, dans l'ordre,\n`Lyon`, `Nantes`, `Lyon` et `Paris`. Qu'affiche ce programme ?\n```python\nretenues = []\nfor ligne in table:\n    if ligne[\"ville\"] == \"Lyon\":\n        retenues.append(ligne)\nprint(len(retenues))\n```",
      "options": [
        {
          "text": "`2`",
          "correct": true,
          "feedback": "Bonne réponse : seules les lignes dont la ville est exactement\n`Lyon` sont ajoutées, soit deux.\n"
        },
        {
          "text": "`4`",
          "correct": false,
          "feedback": "Erreur : le `if` filtre les lignes, toutes ne sont pas retenues.\n"
        },
        {
          "text": "`Lyon`",
          "correct": false,
          "feedback": "Erreur : `len(retenues)` est un nombre de lignes, pas une ville.\n"
        },
        {
          "text": "Une erreur, la liste étant vide au départ",
          "correct": false,
          "feedback": "Erreur : une liste vide peut recevoir des éléments avec\n`append`, et `len` d'une liste vide vaut 0.\n"
        }
      ],
      "explanation": "Filtrer une table, c'est construire une nouvelle liste avec les\nlignes qui vérifient une condition.",
      "variants": [
        {
          "statement": "La table contient quatre lignes dont les villes sont, dans l'ordre,\n`Lyon`, `Nantes`, `Lyon` et `Paris`. Qu'affiche ce programme ?\n```python\nretenues = []\nfor ligne in table:\n    if ligne[\"ville\"] == \"Lyon\":\n        retenues.append(ligne)\nprint(len(retenues))\n```",
          "options": [
            {
              "text": "`2`",
              "correct": true,
              "feedback": "Bonne réponse : seules les lignes dont la ville est exactement\n`Lyon` sont ajoutées, soit deux.\n"
            },
            {
              "text": "`4`",
              "correct": false,
              "feedback": "Erreur : le `if` filtre les lignes, toutes ne sont pas retenues.\n"
            },
            {
              "text": "`Lyon`",
              "correct": false,
              "feedback": "Erreur : `len(retenues)` est un nombre de lignes, pas une ville.\n"
            },
            {
              "text": "Une erreur, la liste étant vide au départ",
              "correct": false,
              "feedback": "Erreur : une liste vide peut recevoir des éléments avec\n`append`, et `len` d'une liste vide vaut 0.\n"
            }
          ],
          "explanation": "Filtrer une table, c'est construire une nouvelle liste avec les\nlignes qui vérifient une condition."
        },
        {
          "statement": "La table contient quatre lignes dont les villes sont, dans l'ordre,\n`Lyon`, `Nantes`, `Lyon` et `Paris`. Qu'affiche ce programme ?\n```python\nretenues = []\nfor ligne in table:\n    if ligne[\"ville\"] == \"Nantes\":\n        retenues.append(ligne)\nprint(len(retenues))\n```",
          "options": [
            {
              "text": "`1`",
              "correct": true,
              "feedback": "Bonne réponse : seules les lignes dont la ville est exactement\n`Nantes` sont ajoutées, soit une.\n"
            },
            {
              "text": "`4`",
              "correct": false,
              "feedback": "Erreur : le `if` filtre les lignes, toutes ne sont pas retenues.\n"
            },
            {
              "text": "`Nantes`",
              "correct": false,
              "feedback": "Erreur : `len(retenues)` est un nombre de lignes, pas une ville.\n"
            },
            {
              "text": "Une erreur, la liste étant vide au départ",
              "correct": false,
              "feedback": "Erreur : une liste vide peut recevoir des éléments avec\n`append`, et `len` d'une liste vide vaut 0.\n"
            }
          ],
          "explanation": "Filtrer une table, c'est construire une nouvelle liste avec les\nlignes qui vérifient une condition."
        },
        {
          "statement": "La table contient quatre lignes dont les villes sont, dans l'ordre,\n`Lyon`, `Nantes`, `Lyon` et `Paris`. Qu'affiche ce programme ?\n```python\nretenues = []\nfor ligne in table:\n    if ligne[\"ville\"] == \"Brest\":\n        retenues.append(ligne)\nprint(len(retenues))\n```",
          "options": [
            {
              "text": "`0`",
              "correct": true,
              "feedback": "Bonne réponse : seules les lignes dont la ville est exactement\n`Brest` sont ajoutées, soit aucune.\n"
            },
            {
              "text": "`4`",
              "correct": false,
              "feedback": "Erreur : le `if` filtre les lignes, toutes ne sont pas retenues.\n"
            },
            {
              "text": "`Brest`",
              "correct": false,
              "feedback": "Erreur : `len(retenues)` est un nombre de lignes, pas une ville.\n"
            },
            {
              "text": "Une erreur, la liste étant vide au départ",
              "correct": false,
              "feedback": "Erreur : une liste vide peut recevoir des éléments avec\n`append`, et `len` d'une liste vide vaut 0.\n"
            }
          ],
          "explanation": "Filtrer une table, c'est construire une nouvelle liste avec les\nlignes qui vérifient une condition."
        }
      ]
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "conversion",
        "comparaison",
        "erreur"
      ],
      "title": "Une comparaison qui échoue",
      "partie": "traitement",
      "statement": "Après lecture d'un fichier CSV, Maya écrit `if ligne[\"age\"] > 15:`\net obtient une erreur. Pourquoi ?",
      "options": [
        {
          "text": "`ligne[\"age\"]` est une chaîne, non comparable à un entier",
          "correct": true,
          "feedback": "Bonne réponse : la valeur lue est `\"17\"`, pas `17`. Il faut\nécrire `int(ligne[\"age\"]) > 15`.\n"
        },
        {
          "text": "La clé `age` n'existe pas dans une table lue depuis un CSV",
          "correct": false,
          "feedback": "Erreur : la clé existe si la colonne figure dans l'en-tête ; le\nproblème est le type de la valeur.\n"
        },
        {
          "text": "`>` n'est pas défini sur les dictionnaires d'une table",
          "correct": false,
          "feedback": "Erreur : la comparaison porte sur une valeur du dictionnaire,\npas sur le dictionnaire lui-même.\n"
        },
        {
          "text": "Les nombres d'un CSV sont toujours lus comme des flottants",
          "correct": false,
          "feedback": "Erreur : ce sont des chaînes, à convertir selon le besoin en\n`int` ou en `float`.\n"
        }
      ],
      "explanation": "Python refuse de comparer une chaîne et un nombre : l'erreur\nrappelle qu'une conversion manque."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "valeurs-manquantes",
        "moyenne",
        "conversion"
      ],
      "title": "Une moyenne malgré des cases vides",
      "partie": "traitement",
      "statement": "Les notes d'une colonne ont été lues dans la liste `notes`,\nune case vide donnant la chaîne vide :\n```python\nnotes = [\"12\", \"\", \"15\", \"9\"]\ntotal = 0\nn = 0\nfor note in notes:\n    if note != \"\":\n        total = total + float(note)\n        n = n + 1\nprint(total / n)\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`12.0`",
          "correct": true,
          "feedback": "Bonne réponse : les cases vides sont ignorées à la fois dans la\nsomme et dans le compte, la moyenne porte sur les notes\nprésentes.\n"
        },
        {
          "text": "`9.0`",
          "correct": false,
          "feedback": "Erreur : ce serait la somme divisée par le nombre total de cases,\nvides comprises ; or `n` ne compte que les notes présentes.\n"
        },
        {
          "text": "Une erreur, `float(\"\")` échouant",
          "correct": false,
          "feedback": "Erreur : c'est précisément ce que le test `note != \"\"` évite.\n"
        },
        {
          "text": "`36.0`",
          "correct": false,
          "feedback": "Erreur : 36.0 est la somme ; le programme affiche la\nmoyenne.\n"
        }
      ],
      "explanation": "Une valeur manquante dans un CSV arrive comme chaîne vide. La\ntester avant conversion évite l'erreur et fausse moins la moyenne.",
      "variants": [
        {
          "statement": "Les notes d'une colonne ont été lues dans la liste `notes`,\nune case vide donnant la chaîne vide :\n```python\nnotes = [\"12\", \"\", \"15\", \"9\"]\ntotal = 0\nn = 0\nfor note in notes:\n    if note != \"\":\n        total = total + float(note)\n        n = n + 1\nprint(total / n)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`12.0`",
              "correct": true,
              "feedback": "Bonne réponse : les cases vides sont ignorées à la fois dans la\nsomme et dans le compte, la moyenne porte sur les notes\nprésentes.\n"
            },
            {
              "text": "`9.0`",
              "correct": false,
              "feedback": "Erreur : ce serait la somme divisée par le nombre total de cases,\nvides comprises ; or `n` ne compte que les notes présentes.\n"
            },
            {
              "text": "Une erreur, `float(\"\")` échouant",
              "correct": false,
              "feedback": "Erreur : c'est précisément ce que le test `note != \"\"` évite.\n"
            },
            {
              "text": "`36.0`",
              "correct": false,
              "feedback": "Erreur : 36.0 est la somme ; le programme affiche la\nmoyenne.\n"
            }
          ],
          "explanation": "Une valeur manquante dans un CSV arrive comme chaîne vide. La\ntester avant conversion évite l'erreur et fausse moins la moyenne."
        },
        {
          "statement": "Les notes d'une colonne ont été lues dans la liste `notes`,\nune case vide donnant la chaîne vide :\n```python\nnotes = [\"8\", \"10\", \"\"]\ntotal = 0\nn = 0\nfor note in notes:\n    if note != \"\":\n        total = total + float(note)\n        n = n + 1\nprint(total / n)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`9.0`",
              "correct": true,
              "feedback": "Bonne réponse : les cases vides sont ignorées à la fois dans la\nsomme et dans le compte, la moyenne porte sur les notes\nprésentes.\n"
            },
            {
              "text": "`6.0`",
              "correct": false,
              "feedback": "Erreur : ce serait la somme divisée par le nombre total de cases,\nvides comprises ; or `n` ne compte que les notes présentes.\n"
            },
            {
              "text": "Une erreur, `float(\"\")` échouant",
              "correct": false,
              "feedback": "Erreur : c'est précisément ce que le test `note != \"\"` évite.\n"
            },
            {
              "text": "`18.0`",
              "correct": false,
              "feedback": "Erreur : 18.0 est la somme ; le programme affiche la\nmoyenne.\n"
            }
          ],
          "explanation": "Une valeur manquante dans un CSV arrive comme chaîne vide. La\ntester avant conversion évite l'erreur et fausse moins la moyenne."
        },
        {
          "statement": "Les notes d'une colonne ont été lues dans la liste `notes`,\nune case vide donnant la chaîne vide :\n```python\nnotes = [\"14\", \"\", \"\", \"20\"]\ntotal = 0\nn = 0\nfor note in notes:\n    if note != \"\":\n        total = total + float(note)\n        n = n + 1\nprint(total / n)\n```\nQu'affiche ce programme ?",
          "options": [
            {
              "text": "`17.0`",
              "correct": true,
              "feedback": "Bonne réponse : les cases vides sont ignorées à la fois dans la\nsomme et dans le compte, la moyenne porte sur les notes\nprésentes.\n"
            },
            {
              "text": "`8.5`",
              "correct": false,
              "feedback": "Erreur : ce serait la somme divisée par le nombre total de cases,\nvides comprises ; or `n` ne compte que les notes présentes.\n"
            },
            {
              "text": "Une erreur, `float(\"\")` échouant",
              "correct": false,
              "feedback": "Erreur : c'est précisément ce que le test `note != \"\"` évite.\n"
            },
            {
              "text": "`34.0`",
              "correct": false,
              "feedback": "Erreur : 34.0 est la somme ; le programme affiche la\nmoyenne.\n"
            }
          ],
          "explanation": "Une valeur manquante dans un CSV arrive comme chaîne vide. La\ntester avant conversion évite l'erreur et fausse moins la moyenne."
        }
      ]
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "tri",
        "cle",
        "conversion"
      ],
      "title": "Trier des nombres lus en texte",
      "partie": "traitement",
      "statement": "Pour trier une table par âge, Lina écrit :\n```python\ndef cle(ligne):\n    return int(ligne[\"age\"])\n\ntable_triee = sorted(table, key=cle)\n```\nPourquoi la fonction `cle` convertit-elle l'âge avec `int` ?",
      "options": [
        {
          "text": "Sinon le tri placerait `\"9\"` après `\"10\"`, ordre alphabétique",
          "correct": true,
          "feedback": "Bonne réponse : des chaînes se comparent caractère par\ncaractère, et `\"1\"` précède `\"9\"`. Convertir en entier rétablit\nl'ordre numérique.\n"
        },
        {
          "text": "`sorted` refuse les chaînes de caractères dans une clé de tri",
          "correct": false,
          "feedback": "Erreur : `sorted` trie très bien des chaînes, mais dans l'ordre\ndes caractères, pas des nombres.\n"
        },
        {
          "text": "`int` accélère le tri en allégeant les comparaisons",
          "correct": false,
          "feedback": "Erreur : la conversion ne change pas la vitesse, seulement\nl'ordre obtenu.\n"
        },
        {
          "text": "Les dictionnaires ne peuvent pas contenir de chaînes",
          "correct": false,
          "feedback": "Erreur : un dictionnaire accepte n'importe quelle valeur, et les\nvaleurs lues sont justement des chaînes.\n"
        }
      ],
      "explanation": "La fonction passée en `key` dit comment comparer les lignes. Pour\nun tri numérique sur des données textuelles, elle convertit."
    },
    {
      "id": "q31",
      "difficulty": 3,
      "skills": [
        "doublons",
        "liste",
        "trace"
      ],
      "title": "Compter les valeurs distinctes d'une colonne",
      "partie": "traitement",
      "statement": "Qu'affiche ce programme ?\n```python\nvilles = [\"Lyon\", \"Paris\", \"Lyon\", \"Nice\", \"Paris\"]\nvues = []\nfor v in villes:\n    if v not in vues:\n        vues.append(v)\nprint(len(vues))\n```",
      "options": [
        {
          "text": "`3`",
          "correct": true,
          "feedback": "Bonne réponse : une ville n'est ajoutée à `vues` que si elle n'y\nfigure pas déjà. La liste finale contient chaque ville une\nfois.\n"
        },
        {
          "text": "`5`",
          "correct": false,
          "feedback": "Erreur : 5 est le nombre de lignes ; les répétitions ne sont\npas ajoutées.\n"
        },
        {
          "text": "`2`",
          "correct": false,
          "feedback": "Erreur : 2 est le nombre de répétitions écartées, pas le\nnombre de villes distinctes.\n"
        },
        {
          "text": "`0`",
          "correct": false,
          "feedback": "Erreur : la première ville rencontrée est toujours ajoutée.\n"
        }
      ],
      "explanation": "Ce schéma, tester l'appartenance avant d'ajouter, construit la\nliste des valeurs distinctes d'une colonne.",
      "variants": [
        {
          "statement": "Qu'affiche ce programme ?\n```python\nvilles = [\"Lyon\", \"Paris\", \"Lyon\", \"Nice\", \"Paris\"]\nvues = []\nfor v in villes:\n    if v not in vues:\n        vues.append(v)\nprint(len(vues))\n```",
          "options": [
            {
              "text": "`3`",
              "correct": true,
              "feedback": "Bonne réponse : une ville n'est ajoutée à `vues` que si elle n'y\nfigure pas déjà. La liste finale contient chaque ville une\nfois.\n"
            },
            {
              "text": "`5`",
              "correct": false,
              "feedback": "Erreur : 5 est le nombre de lignes ; les répétitions ne sont\npas ajoutées.\n"
            },
            {
              "text": "`2`",
              "correct": false,
              "feedback": "Erreur : 2 est le nombre de répétitions écartées, pas le\nnombre de villes distinctes.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : la première ville rencontrée est toujours ajoutée.\n"
            }
          ],
          "explanation": "Ce schéma, tester l'appartenance avant d'ajouter, construit la\nliste des valeurs distinctes d'une colonne."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\nvilles = [\"Brest\", \"Brest\", \"Brest\"]\nvues = []\nfor v in villes:\n    if v not in vues:\n        vues.append(v)\nprint(len(vues))\n```",
          "options": [
            {
              "text": "`1`",
              "correct": true,
              "feedback": "Bonne réponse : une ville n'est ajoutée à `vues` que si elle n'y\nfigure pas déjà. La liste finale contient chaque ville une\nfois.\n"
            },
            {
              "text": "`3`",
              "correct": false,
              "feedback": "Erreur : 3 est le nombre de lignes ; les répétitions ne sont\npas ajoutées.\n"
            },
            {
              "text": "`2`",
              "correct": false,
              "feedback": "Erreur : 2 est le nombre de répétitions écartées, pas le\nnombre de villes distinctes.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : la première ville rencontrée est toujours ajoutée.\n"
            }
          ],
          "explanation": "Ce schéma, tester l'appartenance avant d'ajouter, construit la\nliste des valeurs distinctes d'une colonne."
        },
        {
          "statement": "Qu'affiche ce programme ?\n```python\nvilles = [\"Metz\", \"Lille\", \"Caen\", \"Lille\"]\nvues = []\nfor v in villes:\n    if v not in vues:\n        vues.append(v)\nprint(len(vues))\n```",
          "options": [
            {
              "text": "`3`",
              "correct": true,
              "feedback": "Bonne réponse : une ville n'est ajoutée à `vues` que si elle n'y\nfigure pas déjà. La liste finale contient chaque ville une\nfois.\n"
            },
            {
              "text": "`4`",
              "correct": false,
              "feedback": "Erreur : 4 est le nombre de lignes ; les répétitions ne sont\npas ajoutées.\n"
            },
            {
              "text": "`1`",
              "correct": false,
              "feedback": "Erreur : 1 est le nombre de répétitions écartées, pas le\nnombre de villes distinctes.\n"
            },
            {
              "text": "`0`",
              "correct": false,
              "feedback": "Erreur : la première ville rencontrée est toujours ajoutée.\n"
            }
          ],
          "explanation": "Ce schéma, tester l'appartenance avant d'ajouter, construit la\nliste des valeurs distinctes d'une colonne."
        }
      ]
    },
    {
      "id": "q32",
      "difficulty": 1,
      "skills": [
        "open",
        "lecture",
        "mode"
      ],
      "title": "Le mode d'ouverture d'un fichier",
      "partie": "lecture",
      "statement": "Que signifie le `\"r\"` dans `open(\"eleves.csv\", \"r\", encoding=\"utf-8\")` ?",
      "options": [
        {
          "text": "Que le fichier est ouvert en lecture seule",
          "correct": true,
          "feedback": "Bonne réponse : `\"r\"`, pour *read*, ouvre le fichier pour le\nlire. Écrire dedans demanderait `\"w\"` ou `\"a\"`.\n"
        },
        {
          "text": "Que le fichier sera relu plusieurs fois",
          "correct": false,
          "feedback": "Erreur : le nombre de lectures ne se règle pas à l'ouverture.\n"
        },
        {
          "text": "Que les retours à la ligne seront retirés",
          "correct": false,
          "feedback": "Erreur : les retours à la ligne restent dans les lignes lues ;\nc'est `strip` qui les retire.\n"
        },
        {
          "text": "Que le fichier est au format CSV",
          "correct": false,
          "feedback": "Erreur : `open` ne connaît pas les formats ; il lit du texte.\n"
        }
      ],
      "explanation": "`open` prend le nom du fichier, un mode, et l'encodage. Lire du\nCSV commence toujours par une ouverture en mode `\"r\"`."
    },
    {
      "id": "q33",
      "difficulty": 2,
      "skills": [
        "lecture",
        "strip",
        "retour-ligne"
      ],
      "title": "Un retour à la ligne de trop",
      "partie": "lecture",
      "statement": "En lisant un fichier ligne par ligne avec `for ligne in fichier:`,\nThéo découpe chaque ligne avec `ligne.split(\";\")` et constate que\nla dernière valeur de chaque ligne contient un caractère de plus.\nD'où vient-il ?",
      "options": [
        {
          "text": "Chaque ligne lue garde son retour à la ligne final",
          "correct": true,
          "feedback": "Bonne réponse : la lecture conserve le `\\n` final, qui reste\ncollé au dernier champ. `ligne.strip().split(\";\")` règle le\nproblème.\n"
        },
        {
          "text": "Le séparateur `;` est compté dans la dernière valeur",
          "correct": false,
          "feedback": "Erreur : `split` retire les séparateurs ; le caractère en trop\nest le retour à la ligne.\n"
        },
        {
          "text": "Le fichier contient une colonne vide supplémentaire",
          "correct": false,
          "feedback": "Erreur : une colonne en plus donnerait un champ de plus, pas un\ncaractère de plus dans le dernier champ.\n"
        },
        {
          "text": "L'encodage UTF-8 ajoute un caractère invisible à chaque ligne",
          "correct": false,
          "feedback": "Erreur : l'encodage n'ajoute rien ; le caractère est le `\\n`\nqui termine chaque ligne du fichier.\n"
        }
      ],
      "explanation": "Lire un fichier texte à la main demande deux gestes : `strip` pour\nnettoyer la ligne, puis `split` pour la découper."
    },
    {
      "id": "q34",
      "difficulty": 2,
      "skills": [
        "split",
        "chaine",
        "liste"
      ],
      "title": "Découper une ligne de CSV",
      "partie": "lecture",
      "statement": "Que vaut `\"Lou;15;Lyon\".split(\";\")` ?",
      "options": [
        {
          "text": "`['Lou', '15', 'Lyon']`",
          "correct": true,
          "feedback": "Bonne réponse : `split` découpe la chaîne à chaque `;` et\nrenvoie une liste de chaînes, nombres compris.\n"
        },
        {
          "text": "`['Lou', 15, 'Lyon']`",
          "correct": false,
          "feedback": "Erreur : `split` ne convertit rien ; le nombre reste une chaîne\nentre guillemets.\n"
        },
        {
          "text": "`['Lou;15;Lyon']`",
          "correct": false,
          "feedback": "Erreur : le séparateur `;` est présent, la chaîne est bien\ndécoupée.\n"
        },
        {
          "text": "`'Lou;15;Lyon'`",
          "correct": false,
          "feedback": "Erreur : `split` renvoie une liste, pas une chaîne.\n"
        }
      ],
      "explanation": "`split` est l'outil de base pour lire un CSV sans module : chaque\nligne devient une liste de champs, tous textuels.",
      "variants": [
        {
          "statement": "Que vaut `\"Lou;15;Lyon\".split(\";\")` ?",
          "options": [
            {
              "text": "`['Lou', '15', 'Lyon']`",
              "correct": true,
              "feedback": "Bonne réponse : `split` découpe la chaîne à chaque `;` et\nrenvoie une liste de chaînes, nombres compris.\n"
            },
            {
              "text": "`['Lou', 15, 'Lyon']`",
              "correct": false,
              "feedback": "Erreur : `split` ne convertit rien ; le nombre reste une chaîne\nentre guillemets.\n"
            },
            {
              "text": "`['Lou;15;Lyon']`",
              "correct": false,
              "feedback": "Erreur : le séparateur `;` est présent, la chaîne est bien\ndécoupée.\n"
            },
            {
              "text": "`'Lou;15;Lyon'`",
              "correct": false,
              "feedback": "Erreur : `split` renvoie une liste, pas une chaîne.\n"
            }
          ],
          "explanation": "`split` est l'outil de base pour lire un CSV sans module : chaque\nligne devient une liste de champs, tous textuels."
        },
        {
          "statement": "Que vaut `\"Sami;17;Brest\".split(\";\")` ?",
          "options": [
            {
              "text": "`['Sami', '17', 'Brest']`",
              "correct": true,
              "feedback": "Bonne réponse : `split` découpe la chaîne à chaque `;` et\nrenvoie une liste de chaînes, nombres compris.\n"
            },
            {
              "text": "`['Sami', 17, 'Brest']`",
              "correct": false,
              "feedback": "Erreur : `split` ne convertit rien ; le nombre reste une chaîne\nentre guillemets.\n"
            },
            {
              "text": "`['Sami;17;Brest']`",
              "correct": false,
              "feedback": "Erreur : le séparateur `;` est présent, la chaîne est bien\ndécoupée.\n"
            },
            {
              "text": "`'Sami;17;Brest'`",
              "correct": false,
              "feedback": "Erreur : `split` renvoie une liste, pas une chaîne.\n"
            }
          ],
          "explanation": "`split` est l'outil de base pour lire un CSV sans module : chaque\nligne devient une liste de champs, tous textuels."
        },
        {
          "statement": "Que vaut `\"Inès;16;Caen\".split(\";\")` ?",
          "options": [
            {
              "text": "`['Inès', '16', 'Caen']`",
              "correct": true,
              "feedback": "Bonne réponse : `split` découpe la chaîne à chaque `;` et\nrenvoie une liste de chaînes, nombres compris.\n"
            },
            {
              "text": "`['Inès', 16, 'Caen']`",
              "correct": false,
              "feedback": "Erreur : `split` ne convertit rien ; le nombre reste une chaîne\nentre guillemets.\n"
            },
            {
              "text": "`['Inès;16;Caen']`",
              "correct": false,
              "feedback": "Erreur : le séparateur `;` est présent, la chaîne est bien\ndécoupée.\n"
            },
            {
              "text": "`'Inès;16;Caen'`",
              "correct": false,
              "feedback": "Erreur : `split` renvoie une liste, pas une chaîne.\n"
            }
          ],
          "explanation": "`split` est l'outil de base pour lire un CSV sans module : chaque\nligne devient une liste de champs, tous textuels."
        }
      ]
    },
    {
      "id": "q35",
      "difficulty": 3,
      "skills": [
        "en-tete",
        "lecture",
        "conversion"
      ],
      "title": "La première ligne n'est pas une donnée",
      "partie": "lecture",
      "statement": "Un programme lit un fichier CSV ligne par ligne et convertit le\ndeuxième champ de chaque ligne avec `int`. Il échoue dès la\npremière ligne. Quelle est la cause la plus probable ?",
      "options": [
        {
          "text": "La première ligne est l'en-tête, et `int(\"age\")` échoue",
          "correct": true,
          "feedback": "Bonne réponse : l'en-tête contient les noms des descripteurs,\npas des valeurs. Il faut la lire à part et ne convertir que les\nlignes suivantes.\n"
        },
        {
          "text": "Le fichier est vide, et `int` ne reçoit rien",
          "correct": false,
          "feedback": "Erreur : un fichier vide ne provoquerait aucune conversion,\ndonc aucune erreur.\n"
        },
        {
          "text": "`int` ne s'applique pas aux champs d'un CSV",
          "correct": false,
          "feedback": "Erreur : `int` convertit très bien `\"17\"` ; il refuse `\"age\"`.\n"
        },
        {
          "text": "Le deuxième champ n'existe pas dans la première ligne",
          "correct": false,
          "feedback": "Erreur : un champ absent donnerait une erreur d'indice, pas une\nerreur de conversion.\n"
        }
      ],
      "explanation": "L'en-tête décrit les colonnes ; il se traite séparément, souvent\npour construire les clés des dictionnaires de la table."
    },
    {
      "id": "q36",
      "difficulty": 3,
      "skills": [
        "format",
        "fin-de-ligne",
        "portabilite"
      ],
      "title": "Un fichier venu de Windows",
      "partie": "format",
      "statement": "Un fichier CSV créé sous Windows termine ses lignes par deux\ncaractères, `\\r` puis `\\n`, alors qu'un fichier créé sous Linux\nn'utilise que `\\n`. Quelle conséquence pour un programme qui lit\nles lignes et les découpe ?",
      "options": [
        {
          "text": "Un `\\r` reste collé au dernier champ, sauf nettoyage par `strip`",
          "correct": true,
          "feedback": "Bonne réponse : la lecture retire le `\\n` mais garde parfois le\n`\\r`. `strip` élimine les deux, ce qui rend le programme\nindépendant du système d'origine.\n"
        },
        {
          "text": "Le fichier ne peut pas être ouvert sous Linux sans conversion",
          "correct": false,
          "feedback": "Erreur : le fichier s'ouvre ; seule la fin de ligne diffère.\n"
        },
        {
          "text": "Chaque ligne est lue deux fois, une par caractère de fin",
          "correct": false,
          "feedback": "Erreur : les deux caractères marquent une seule fin de ligne.\n"
        },
        {
          "text": "Les séparateurs `;` sont remplacés par des virgules",
          "correct": false,
          "feedback": "Erreur : le séparateur n'a rien à voir avec la fin de ligne.\n"
        }
      ],
      "explanation": "Les conventions de fin de ligne diffèrent selon les systèmes. Un\nprogramme robuste nettoie chaque ligne avant de la découper."
    }
  ]
}