{
  "chapter": {
    "id": "tuples-listes",
    "level": "premiere",
    "theme": "Programmation",
    "title": "Tuples et listes",
    "description": "Types construits : tuples (immuables) et listes\n(mutables), création, accès par indice, parcours,\najout avec `append`, longueur avec `len`, tranches,\ndifférences entre tuples et listes, cas d'usage,\ndécoupage (*slicing*), affectation multiple.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-liste"
      ],
      "title": "Création d'une liste",
      "statement": "Comment crée-t-on une **liste** vide en\nPython ?",
      "options": [
        {
          "text": "Avec la syntaxe `liste = []`",
          "correct": true,
          "feedback": "On utilise les crochets pour\ndélimiter une liste. Une forme\néquivalente consiste à écrire\n`liste = list()`.\n"
        },
        {
          "text": "Avec la syntaxe `liste = ()`",
          "correct": false,
          "feedback": "Cette syntaxe crée un **tuple**\nvide, et non une liste.\n"
        },
        {
          "text": "Avec la syntaxe `liste = {}`",
          "correct": false,
          "feedback": "Les accolades sont utilisées\npour les **dictionnaires** ou\nles ensembles, et non pour les\nlistes.\n"
        },
        {
          "text": "Avec la syntaxe `liste = null`",
          "correct": false,
          "feedback": "Le mot-clé `null` n'existe pas\nen Python ; l'équivalent\ns'appelle `None`, qui ne\nreprésente d'ailleurs pas une\nliste.\n"
        }
      ],
      "explanation": "Trois conteneurs natifs principaux\nsont à distinguer : les listes\n(avec `[]`), les tuples (avec\n`()`) et les dictionnaires (avec\n`{}`). Ils diffèrent par leur\nmutabilité, leur ordre et leur\nmode d'accès."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "tuple"
      ],
      "title": "Création d'un tuple",
      "statement": "Comment crée-t-on un **tuple** en\nPython ?",
      "options": [
        {
          "text": "Avec `t = tuple[1, 2, 3]`, crochets après le mot `tuple`",
          "correct": false,
          "feedback": "Erreur : `tuple[...]` avec des crochets\nest une annotation de type, pas une\ncréation de tuple. Pour convertir une\nliste, on écrit `tuple([1, 2, 3])`.\n"
        },
        {
          "text": "Avec `t = (1, 2, 3)`, ou même `t = 1, 2, 3`",
          "correct": true,
          "feedback": "Bonne réponse : les parenthèses sont\nsouvent facultatives ; ce sont les\nvirgules qui définissent un tuple. Le\ntuple vide s'écrit `()` et, pour un\nseul élément, la virgule est\nobligatoire : `t = (1,)`.\n"
        },
        {
          "text": "Avec `t = {1, 2, 3}`, les accolades regroupant les éléments",
          "correct": false,
          "feedback": "Erreur : les accolades autour de\nplusieurs éléments séparés par des\nvirgules créent un **ensemble**, un\nautre type de collection qui n'est\npas au programme de première, et\nnon un tuple.\n"
        },
        {
          "text": "Avec `t = [1, 2, 3]`, puis `t.freeze()` pour le figer",
          "correct": false,
          "feedback": "Erreur : les crochets créent une liste,\net aucune méthode `freeze` n'existe. La\nconversion se fait avec `tuple(liste)`.\n"
        }
      ],
      "explanation": "Une distinction subtile : `(1)` est\nsimplement l'entier $1$ entouré\nde parenthèses de groupement,\ntandis que `(1,)` est un tuple à\nun seul élément. C'est la\nvirgule, et non les parenthèses,\nqui crée le tuple."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "mutabilite"
      ],
      "title": "Mutabilité",
      "statement": "Quelle est la **différence fondamentale** entre un tuple et\nune liste ?",
      "options": [
        {
          "text": "Un tuple contient forcément exactement deux éléments",
          "correct": false,
          "feedback": "Aucune contrainte sur le\nnombre d'éléments : un tuple\npeut en contenir zéro, un, ou\nbeaucoup.\n"
        },
        {
          "text": "Aucune différence, seule la notation change",
          "correct": false,
          "feedback": "Erreur : la notation diffère, mais\nsurtout le comportement : une liste se\nmodifie sur place, un tuple jamais.\n"
        },
        {
          "text": "La liste est modifiable, le tuple ne l'est pas",
          "correct": true,
          "feedback": "Bonne réponse : une liste est\n**mutable**, un tuple **immuable**\n(figé). En conséquence, les tuples\npeuvent être utilisés comme\nclés de dictionnaire, ce que\nles listes ne peuvent pas\nfaire. Les tuples sont\négalement un peu plus rapides\nà manipuler.\n"
        },
        {
          "text": "Un tuple ne contient que des nombres, une liste tout type",
          "correct": false,
          "feedback": "Erreur : un tuple accepte n'importe quels\nobjets, comme une liste (`(\"a\", 1, [2])`\nest valide). La différence concerne la\nmutabilité.\n"
        }
      ],
      "explanation": "La mutabilité désigne la\npossibilité d'être modifié.\nL'instruction `liste.append(x)`\nmodifie la liste sur place. Sur\nun tuple, l'appel\n`tuple.append(x)` provoque une\nexception `AttributeError`."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "acces-indice"
      ],
      "title": "Accès par indice",
      "statement": "Que renvoie l'expression\n`[\"a\", \"b\", \"c\"][1]` ?",
      "options": [
        {
          "text": "La chaîne `\"c\"`",
          "correct": false,
          "feedback": "La chaîne `\"c\"` se trouve à\nl'indice $2$, et non à\nl'indice $1$.\n"
        },
        {
          "text": "La chaîne `\"b\"`",
          "correct": true,
          "feedback": "L'indice $1$ correspond au\ndeuxième élément de la liste.\nC'est la convention standard\ndes langages indexés à partir\nde $0$.\n"
        },
        {
          "text": "La chaîne `\"a\"`",
          "correct": false,
          "feedback": "En Python, l'indexation\ncommence à $0$. L'élément\nd'indice $0$ est donc `\"a\"`,\net l'élément d'indice $1$ est\n`\"b\"`.\n"
        },
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "L'indice $1$ est valide pour\nune liste de trois éléments :\naucune exception n'est levée.\n"
        }
      ],
      "explanation": "Une indexation négative est aussi\npossible : `liste[-1]` désigne le\ndernier élément, `liste[-2]`\nl'avant-dernier, et ainsi de\nsuite. C'est très utile pour\naccéder à la fin d'une liste sans\navoir à connaître sa longueur."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "longueur"
      ],
      "title": "Longueur d'une séquence",
      "statement": "Comment obtient-on la **longueur**\nd'une liste ou d'un tuple ?",
      "options": [
        {
          "text": "Avec `liste.size()`",
          "correct": false,
          "feedback": "La méthode `size` n'existe\npas en Python. Elle vient du\nmonde C++.\n"
        },
        {
          "text": "Avec `liste.length`",
          "correct": false,
          "feedback": "L'attribut `length` n'existe\npas en Python. Cette syntaxe\nprovient de JavaScript ou de\nJava.\n"
        },
        {
          "text": "Avec `len(liste)`",
          "correct": true,
          "feedback": "La fonction native `len`\nfonctionne sur tous les\nconteneurs standard : listes,\ntuples, chaînes,\ndictionnaires, ensembles.\n"
        },
        {
          "text": "Avec `size(liste)`",
          "correct": false,
          "feedback": "Aucune fonction `size`\nn'existe dans la\nbibliothèque standard de\nPython.\n"
        }
      ],
      "explanation": "La fonction `len` s'exécute en\n$O(1)$, car la longueur est\nstockée à part. L'expression\n`len([])` vaut $0$, ce qui\npermet de tester rapidement si\nune liste est vide."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "append"
      ],
      "title": "Ajouter à une liste",
      "statement": "Comment **ajoute-t-on** l'élément\n$42$ à la fin d'une liste `liste` ?",
      "options": [
        {
          "text": "Avec `liste += 42`",
          "correct": false,
          "feedback": "L'opérateur `+=` attend un\nitérable, pas un entier.\nL'écriture `liste += [42]`\nfonctionnerait, mais reste\nmoins idiomatique\nqu'`append`.\n"
        },
        {
          "text": "Avec `liste = liste + 42`",
          "correct": false,
          "feedback": "L'opérateur `+` ne sait pas\ncombiner une liste avec un\nentier ; il déclenche une\nexception `TypeError`.\n"
        },
        {
          "text": "Avec `liste.append(42)`",
          "correct": true,
          "feedback": "Cette méthode modifie la\nliste sur place. Par\nexemple, après\n`liste = [1, 2]; liste.append(3)`,\nla liste vaut `[1, 2, 3]`.\n"
        },
        {
          "text": "Avec `liste.add(42)`",
          "correct": false,
          "feedback": "La méthode `add` est définie\npour les ensembles, mais pas\npour les listes.\n"
        }
      ],
      "explanation": "Différence importante : la\nméthode `append` modifie la\nliste sur place, alors que\nl'opérateur `+` crée une\nnouvelle liste. Pour ajouter\nplusieurs éléments d'un coup, on\nécrit `liste = liste + [4, 5, 6]`."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "parcours"
      ],
      "title": "Parcours d'une liste",
      "statement": "Comment **parcourt-on** tous les\néléments d'une liste pour les\nafficher ?",
      "options": [
        {
          "text": "```python\nfor i in liste.length:\n    print(liste[i])\n```\n",
          "correct": false,
          "feedback": "L'attribut `length` n'existe\npas en Python, et\n`for i in nombre:` n'est pas\nune syntaxe valide.\n"
        },
        {
          "text": "```python\nprint liste\n```\n",
          "correct": false,
          "feedback": "Cette syntaxe correspond à\nPython 2. En Python 3,\n`print` est une fonction et\nrequiert des parenthèses.\n"
        },
        {
          "text": "```python\nfor x in liste:\n    print(x)\n```\n",
          "correct": true,
          "feedback": "C'est la forme idiomatique\nen Python. La variable `x`\nreçoit successivement chacun\ndes éléments de la liste.\n"
        },
        {
          "text": "```python\nwhile liste:\n    print(liste)\n```\n",
          "correct": false,
          "feedback": "Cette boucle est infinie tant\nqu'on ne retire pas des\néléments à la liste, et elle\nn'affiche pas les éléments\nun par un.\n"
        }
      ],
      "explanation": "Pour parcourir une liste avec un\nindice, on écrit\n`for i in range(len(liste)):`.\nOn dispose alors à la fois de\nl'indice `i` et de la valeur\n`liste[i]` à chaque tour de boucle."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "in-operator"
      ],
      "title": "Test d'appartenance",
      "statement": "Comment teste-t-on si l'élément\n$42$ est présent dans une liste\n`liste` ?",
      "options": [
        {
          "text": "Avec `liste.has(42)`",
          "correct": false,
          "feedback": "Aucune méthode de ce nom\nn'existe pour les listes en\nPython.\n"
        },
        {
          "text": "Avec `42 in liste`",
          "correct": true,
          "feedback": "L'opérateur `in` renvoie un\nbooléen indiquant la\nprésence ou l'absence de\nl'élément. Sur une liste,\ncette opération s'exécute en\n$O(n)$ par parcours.\n"
        },
        {
          "text": "Avec `liste.find(42)`",
          "correct": false,
          "feedback": "La méthode `find` est définie\npour les chaînes de\ncaractères, mais pas pour les\nlistes.\n"
        },
        {
          "text": "Avec `liste.contains(42)`",
          "correct": false,
          "feedback": "La méthode `contains`\nn'existe pas en Python.\n"
        }
      ],
      "explanation": "Deux méthodes utiles existent\npar ailleurs : `liste.index(42)`\nrenvoie l'indice de la première\noccurrence, ou lève une\nexception `ValueError` si\nl'élément n'est pas présent ;\n`liste.count(42)` compte le\nnombre d'occurrences."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "comparaison-tuple-liste"
      ],
      "title": "Quand utiliser un tuple ?",
      "statement": "Dans quel cas préfère-t-on un\n**tuple** à une liste ?",
      "options": [
        {
          "text": "Uniquement pour stocker des valeurs numériques entières",
          "correct": false,
          "feedback": "Aucune restriction sur le\ntype des éléments. Un tuple\npeut contenir des chaînes,\ndes objets, ou tout autre\ntype.\n"
        },
        {
          "text": "Pour des données qui ne doivent pas être modifiées (constantes, coordonnées)",
          "correct": true,
          "feedback": "Le tuple offre la sécurité\n(l'immuabilité empêche les\nmodifications\naccidentelles), une légère\nefficacité supplémentaire,\net permet l'usage comme clé\nde dictionnaire.\n"
        },
        {
          "text": "Pour économiser quelques touches au clavier",
          "correct": false,
          "feedback": "Cette motivation est trop\nsuperficielle. Le choix\nentre tuple et liste est\nd'abord d'ordre conceptuel.\n"
        },
        {
          "text": "Pour stocker des données qui changent souvent au cours du programme",
          "correct": false,
          "feedback": "Pour des données dynamiques,\nla liste est plus adaptée\npuisqu'elle est mutable.\n"
        }
      ],
      "explanation": "Cas d'usage typiques d'un tuple :\ncoordonnées comme `(x, y)`,\nretours multiples d'une fonction\navec `return a, b`, ou\nenregistrement comme\n`(\"Alice\", 17, \"Lyon\")`."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "exemple-simple"
      ],
      "title": "Trace simple",
      "statement": "Que produit ce code ?\n```python\nl = [1, 2, 3]\nl.append(4)\nprint(len(l))\n```",
      "options": [
        {
          "text": "Il affiche `5`",
          "correct": false,
          "feedback": "On ajoute un seul élément\nà une liste qui en contenait\ntrois : la longueur passe\nde $3$ à $4$, et non à $5$.\n"
        },
        {
          "text": "Il affiche `3`",
          "correct": false,
          "feedback": "Cette réponse ignore l'ajout\nde l'élément $4$ par la\nméthode `append`.\n"
        },
        {
          "text": "Il affiche `4`",
          "correct": true,
          "feedback": "La liste initiale est\n`[1, 2, 3]`. Après\n`l.append(4)`, elle vaut\n`[1, 2, 3, 4]`, donc sa\nlongueur est $4$.\n"
        },
        {
          "text": "Il déclenche une exception",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide.\n"
        }
      ],
      "explanation": "La méthode `append` modifie la\nliste sur place. Il ne faut pas\nécrire `l = l.append(4)` :\n`append` ne renvoie rien (la\nvaleur renvoyée est `None`),\ndonc `l` se retrouverait à\n`None`."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "slice"
      ],
      "title": "Découpage d'une liste",
      "statement": "Que renvoie l'expression\n`[1, 2, 3, 4, 5][1:4]` ?",
      "options": [
        {
          "text": "La liste `[1, 2, 3]`",
          "correct": false,
          "feedback": "L'indice $1$ commence au\ndeuxième élément. La borne\nbasse est inclusive, donc\non commence par l'élément à\nl'indice $1$.\n"
        },
        {
          "text": "La liste `[2, 3, 4]`",
          "correct": true,
          "feedback": "La syntaxe `liste[a:b]`\nextrait les éléments de\nl'indice `a` (inclus) à\nl'indice `b` (exclu). Les\nindices $1$, $2$ et $3$\ncorrespondent ici aux\néléments $2$, $3$ et $4$.\n"
        },
        {
          "text": "La liste `[1, 4]`",
          "correct": false,
          "feedback": "Le découpage extrait une\ntranche continue, et non\ndeux éléments isolés.\n"
        },
        {
          "text": "La liste `[2, 3, 4, 5]`",
          "correct": false,
          "feedback": "La borne haute est\n**exclue** par convention.\nOn ne prend donc pas\nl'élément d'indice $4$\n(qui est $5$).\n"
        }
      ],
      "explanation": "Convention Python : la borne\nbasse est incluse, la borne\nhaute est exclue. C'est cohérent\navec `range(a, b)`. Une borne\nomise vaut le début ou la fin :\n`[1, 2, 3, 4, 5][:2]` donne\n`[1, 2]` et `[1, 2, 3, 4, 5][2:]`\ndonne `[3, 4, 5]`."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "unpacking"
      ],
      "title": "Affectation multiple",
      "statement": "Quelles sont les valeurs de `a`,\n`b` et `c` après l'instruction\n`(a, b, c) = (1, 2, 3)` ?",
      "options": [
        {
          "text": "Le tuple `((1, 2, 3),)`",
          "correct": false,
          "feedback": "Aucune imbrication ne se\nproduit ici. Chaque variable\nreçoit une seule valeur.\n"
        },
        {
          "text": "$a = 1$, $b = 1$, $c = 1$",
          "correct": false,
          "feedback": "L'affectation multiple\nattribue chaque valeur du\ntuple de droite à la variable\ncorrespondante de gauche, et\nnon la même valeur à toutes.\n"
        },
        {
          "text": "$a = 1$, $b = 2$, $c = 3$",
          "correct": true,
          "feedback": "C'est ce qu'on appelle le\n**dépaquetage** d'un tuple.\nLes parenthèses peuvent\nêtre omises :\n`a, b, c = 1, 2, 3`. Cette\nécriture est très utile,\nnotamment pour échanger\ndeux variables :\n`a, b = b, a`.\n"
        },
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide.\n"
        }
      ],
      "explanation": "Une variante plus avancée\nutilise l'astérisque pour\ncapturer une partie variable :\n`a, *reste = [1, 2, 3, 4]`\naffecte $a = 1$ et\n`reste = [2, 3, 4]`."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "slice",
        "dernier-element"
      ],
      "title": "Retirer le dernier élément",
      "statement": "On dispose de `l = [10, 20, 30]`. Quelle expression\nfournit une **nouvelle** liste contenant tous les\néléments de `l` sauf le dernier, quelle que soit la\nlongueur de `l` ?",
      "options": [
        {
          "text": "`l[1:]`",
          "correct": false,
          "feedback": "Erreur : cette tranche part de l'indice $1$ et\nretire donc le **premier** élément. Sur\nl'exemple, elle vaut `[20, 30]`.\n"
        },
        {
          "text": "`l[:-1]`",
          "correct": true,
          "feedback": "Bonne réponse : la borne haute `-1` désigne le\ndernier élément, exclu de la tranche. On obtient\n`[10, 20]` dans une nouvelle liste ; `l` n'est\npas modifiée.\n"
        },
        {
          "text": "`l[:-2]`",
          "correct": false,
          "feedback": "Erreur : la borne haute étant exclue, `-2`\nretire les **deux** derniers éléments. Sur\nl'exemple, la tranche vaut `[10]`.\n"
        },
        {
          "text": "`l[-1]`",
          "correct": false,
          "feedback": "Erreur : sans deux-points, les crochets font un\naccès par indice, pas une tranche. `l[-1]` est\nle dernier élément lui-même, ici $30$.\n"
        }
      ],
      "explanation": "Dans une tranche `l[debut:fin]`, la borne de fin est\nexclue et peut être négative : `l[:-1]` s'arrête\njuste avant le dernier élément. Le résultat est une\nnouvelle liste, la liste d'origine reste intacte."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "bug-mutabilite"
      ],
      "title": "Piège de la valeur par défaut mutable",
      "statement": "```python\ndef f(l=[]):\n    l.append(1)\n    return l\n\nprint(f())\nprint(f())\n```\nQuel est le résultat de l'exécution\nde ce code ?",
      "options": [
        {
          "text": "```\n[1]\n[1]\n```\n",
          "correct": false,
          "feedback": "Cette réponse ignore le fait\nque la valeur par défaut est\npartagée entre les appels\nsuccessifs.\n"
        },
        {
          "text": "```\n[]\n[]\n```\n",
          "correct": false,
          "feedback": "Cette réponse ignore les\nappels à `append`, qui\najoutent bien un élément\nà chaque appel de `f`.\n"
        },
        {
          "text": "```\n[1]\n[1, 1]\n```\n",
          "correct": true,
          "feedback": "La liste par défaut est créée\n**une seule fois**, à la\ndéfinition de la fonction.\nLes appels successifs\npartagent donc la même\nliste. Pour éviter ce\npiège, on écrit `l=None`,\npuis dans la fonction\n`if l is None: l = []`.\n"
        },
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide ; aucune exception\nn'est levée.\n"
        }
      ],
      "explanation": "C'est l'un des plus célèbres\npièges de Python. Il découle\ndirectement de la mutabilité\ndes listes et de la manière\ndont Python évalue les valeurs\npar défaut."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "tri"
      ],
      "title": "Méthodes sort et sorted",
      "statement": "Quelle est la différence entre\n`liste.sort()` et `sorted(liste)` ?",
      "options": [
        {
          "text": "La méthode `sort` est nettement plus lente à l'exécution",
          "correct": false,
          "feedback": "Les deux ont une\nperformance équivalente.\nLa différence porte sur\nla modification, ou non,\nde la liste d'origine.\n"
        },
        {
          "text": "Aucune différence, `sorted(liste)` appelle `liste.sort()`",
          "correct": false,
          "feedback": "Erreur : `sorted` construit une\n**nouvelle** liste triée sans toucher à\nl'original ; `sort` modifie la liste sur\nplace et renvoie `None`.\n"
        },
        {
          "text": "La fonction `sorted` ne fonctionne que sur des nombres",
          "correct": false,
          "feedback": "La fonction `sorted`\nfonctionne sur tout type\ncomparable (entiers,\nchaînes, tuples, etc.).\n"
        },
        {
          "text": "`sort` modifie la liste, `sorted` renvoie une copie triée",
          "correct": true,
          "feedback": "Bonne réponse : `sort` trie sur place\net renvoie `None`, `sorted` renvoie une\nnouvelle liste. Le choix dépend du besoin :\nsi l'on veut conserver\nl'original, on utilise\n`sorted` ; sinon, `sort`\nest préférable. La\nfonction `sorted`\nfonctionne aussi sur les\ntuples, contrairement à\n`sort`.\n"
        }
      ],
      "explanation": "Moyen mnémotechnique : `sorted`\n(« trié ») désigne un résultat,\nune nouvelle liste triée\nrenvoyée sans modifier\nl'original ; `sort` (« trier »)\ndésigne une action, qui modifie\nla liste sur place et renvoie\n`None`."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "comprehension-liste"
      ],
      "title": "Compréhension de liste",
      "statement": "Quelle expression construit la liste\n`[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]`\ndes carrés des entiers de $1$ à $10$ ?",
      "options": [
        {
          "text": "`[i ** 2 for i in range(1, 11)]`",
          "correct": true,
          "feedback": "Bonne réponse : `range(1, 11)` parcourt les\nentiers de $1$ à $10$ inclus, et `i ** 2`\ncalcule le carré de chacun. La compréhension\nrassemble ces dix carrés dans une liste.\n"
        },
        {
          "text": "`[i * 2 for i in range(1, 11)]`",
          "correct": false,
          "feedback": "Erreur : `i * 2` est le double de `i`, pas\nson carré. Cette expression construit\n`[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]`.\nLe carré s'écrit `i ** 2` ou `i * i`.\n"
        },
        {
          "text": "`[i ** 2 for i in range(10)]`",
          "correct": false,
          "feedback": "Erreur : `range(10)` commence à $0$ et\ns'arrête à $9$. Cette expression construit\n`[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]` :\nle carré de $0$ est en trop, celui de $10$\nmanque.\n"
        },
        {
          "text": "`[i ** 2 for i in range(1, 10)]`",
          "correct": false,
          "feedback": "Erreur : la borne de fin de `range` est\nexclue, donc `range(1, 10)` s'arrête à\n$9$. Cette expression construit\n`[1, 4, 9, 16, 25, 36, 49, 64, 81]`, sans\nle carré de $10$.\n"
        }
      ],
      "explanation": "Une compréhension de liste\n`[expression for i in range(a, b)]` construit\nla liste des valeurs de l'expression pour `i`\nallant de `a` à `b - 1`. Elle équivaut à une\nboucle qui part d'une liste vide et ajoute\nchaque valeur avec `append`. Les deux erreurs\nclassiques sont de confondre `*` et `**`, et\nde se tromper sur les bornes de `range`."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "reference-vs-copie"
      ],
      "title": "Référence et copie",
      "statement": "```python\na = [1, 2, 3]\nb = a\nb.append(4)\nprint(a)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "La liste `[4]`",
          "correct": false,
          "feedback": "La méthode `append`\najoute à la liste\nexistante, sans la\nremplacer.\n"
        },
        {
          "text": "La liste `[1, 2, 3]`",
          "correct": false,
          "feedback": "L'affectation `b = a` ne\ncrée pas une copie, mais\nattribue une nouvelle\nréférence vers la même\nliste.\n"
        },
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide.\n"
        },
        {
          "text": "La liste `[1, 2, 3, 4]`",
          "correct": true,
          "feedback": "Les variables `a` et `b`\ndésignent le **même** objet\nen mémoire. Modifier l'un\nrevient à modifier\nl'autre. Pour faire une\ncopie, on utilise\n`b = a.copy()` ou\n`b = a[:]`.\n"
        }
      ],
      "explanation": "C'est la notion essentielle\nd'**alias** : deux variables\npeuvent désigner le même\nobjet. Ce comportement diffère\ndes types immuables comme les\nentiers, où `b = a` n'a pas\ncet effet."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "parcours",
        "variable-de-boucle",
        "mutabilite"
      ],
      "title": "Réaffecter la variable de boucle",
      "statement": "```python\nl = [1, 2, 3]\nfor x in l:\n    x = x * 2\nprint(l)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "Il affiche `[2, 4, 6]`",
          "correct": false,
          "feedback": "Erreur : `x = x * 2` crée un nouvel entier et le\nrange dans `x`, sans rien écrire dans la liste.\nPour doubler les éléments, il faut écrire\n`l[i] = l[i] * 2` en parcourant les indices.\n"
        },
        {
          "text": "Il affiche `[1, 2, 3]`",
          "correct": true,
          "feedback": "Bonne réponse : réaffecter la variable de boucle\nne modifie jamais la liste. À chaque tour, `x`\ndésigne un élément, puis reçoit un nouvel\nentier ; la liste n'est pas touchée.\n"
        },
        {
          "text": "Il affiche `[1, 2, 3, 2, 4, 6]`",
          "correct": false,
          "feedback": "Erreur : aucune instruction n'ajoute d'élément\nà `l`. Seule la méthode `append` allongerait la\nliste ; ici on ne fait que réaffecter `x`.\n"
        },
        {
          "text": "Il déclenche une erreur, car la liste est modifiée pendant le parcours",
          "correct": false,
          "feedback": "Erreur : la liste n'est justement pas modifiée,\net réaffecter la variable de boucle est de toute\nfaçon autorisé. Le code s'exécute sans erreur.\n"
        }
      ],
      "explanation": "Pour transformer les éléments d'une liste, on\nparcourt les indices et on écrit dans la liste :\n`for i in range(len(l)): l[i] = l[i] * 2`. La\nvariable `x` d'un `for x in l` n'est qu'un nom\ntemporaire posé sur chaque élément."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "hashable"
      ],
      "title": "Tuple comme clé de dictionnaire",
      "statement": "Pourquoi un tuple peut-il être\nutilisé comme **clé** de\ndictionnaire, alors qu'une liste\nne le peut pas ?",
      "options": [
        {
          "text": "Aucune des deux structures ne peut servir de clé",
          "correct": false,
          "feedback": "Un tuple peut bien servir\nde clé, à condition que\ntous ses éléments soient\neux-mêmes immuables.\n"
        },
        {
          "text": "Aucune raison particulière",
          "correct": false,
          "feedback": "La distinction repose sur\ndes considérations\ntechniques précises, comme\nexpliqué dans la bonne\nréponse.\n"
        },
        {
          "text": "Parce qu'un tuple est immuable : il a une empreinte de hachage stable",
          "correct": true,
          "feedback": "Les clés de dictionnaire\ndoivent être stables dans\nle temps. Une liste,\nmutable, ne pourrait pas\nservir de clé : on\npourrait modifier la clé\naprès l'insertion, ce qui\ncasserait la table de\nhachage.\n"
        },
        {
          "text": "Uniquement pour des raisons purement historiques et anciennes",
          "correct": false,
          "feedback": "La raison est strictement\ntechnique, et non\nhistorique.\n"
        }
      ],
      "explanation": "Un objet est dit *hashable*\nlorsqu'il possède une\nempreinte stable et peut\ndonc être placé dans un\nensemble ou utilisé comme\nclé. Les types immuables\nclassiques sont hashables :\nentiers, flottants, chaînes,\ntuples (s'ils ne contiennent\nque des éléments hashables)\net ensembles figés."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "longueur-tuple"
      ],
      "title": "Tuple à un élément",
      "statement": "Que vaut l'expression `len((42))` ?",
      "options": [
        {
          "text": "La valeur $0$, car le tuple `(42)` est considéré comme vide",
          "correct": false,
          "feedback": "Erreur : `(42)` n'est pas un tuple du\ntout, et `len` lève une exception sur un\nentier.\n"
        },
        {
          "text": "Une exception `TypeError`",
          "correct": true,
          "feedback": "Bonne réponse : `(42)` n'est pas un tuple\nmais l'entier 42 entre parenthèses, et\n`len` n'agit pas sur un entier. Pour créer\nun tuple à un seul élément, il faut une\nvirgule : `(42,)`.\n"
        },
        {
          "text": "La valeur $42$, l'unique élément du tuple",
          "correct": false,
          "feedback": "Erreur : `len` ne renvoie pas la valeur\nde son argument, et `(42)` n'est pas un\ntuple.\n"
        },
        {
          "text": "La valeur entière $1$, comme on pourrait l'attendre",
          "correct": false,
          "feedback": "L'expression `(42)` n'est\npas un tuple, mais\nsimplement l'entier $42$\nentouré de parenthèses de\ngroupement.\n"
        }
      ],
      "explanation": "C'est un piège classique : il\nfaut toujours penser à la\nvirgule pour les tuples à un\nseul élément. Pour vérifier,\n`type((42))` renvoie `int`,\ntandis que `type((42,))`\nrenvoie `tuple`."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "alias",
        "concatenation",
        "reaffectation"
      ],
      "title": "Alias et concaténation",
      "statement": "```python\nl = [1, 2, 3]\nm = l\nl = l + [4]\nprint(m)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "Il affiche `[1, 2, 3, 4]`",
          "correct": false,
          "feedback": "Erreur : l'alias `m` suivrait `l` si la liste\nétait modifiée sur place, par `l.append(4)` ou\n`l += [4]`. Mais `l + [4]` construit une\n**nouvelle** liste, que `l` reçoit ; `m` désigne\ntoujours l'ancienne.\n"
        },
        {
          "text": "Il affiche `[1, 2, 3, [4]]`",
          "correct": false,
          "feedback": "Erreur : l'opérateur `+` concatène les éléments\nsans imbriquer la liste `[4]`, et surtout il\nproduit une nouvelle liste : `m` n'en voit rien.\n"
        },
        {
          "text": "Il affiche `[1, 2, 3]`",
          "correct": true,
          "feedback": "Bonne réponse : après `m = l`, les deux noms\ndésignent la même liste. Puis `l + [4]` crée\nune nouvelle liste `[1, 2, 3, 4]`, affectée à\n`l` seul. `m` garde l'ancienne liste, intacte.\n"
        },
        {
          "text": "Il déclenche une erreur, car on ne peut pas additionner deux listes",
          "correct": false,
          "feedback": "Erreur : `+` entre deux listes est défini et\nproduit leur concaténation. Seule l'addition\nd'une liste et d'un entier, comme `l + 4`,\nlève `TypeError`.\n"
        }
      ],
      "explanation": "Deux façons d'allonger une liste n'ont pas le même\neffet sur les alias : `l.append(4)` et `l += [4]`\nmodifient la liste partagée, visible depuis `m` ;\n`l = l + [4]` réaffecte `l` à une nouvelle liste\net rompt le lien avec `m`."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "aliasing-piege"
      ],
      "title": "Liste de listes",
      "statement": "```python\ngrille = [[0] * 3] * 3\ngrille[0][0] = 1\nprint(grille)\n```\nQue produit ce code ?",
      "options": [
        {
          "text": "`[[1, 0, 0], [1, 0, 0], [1, 0, 0]]`",
          "correct": true,
          "feedback": "L'expression `[[0]*3]*3`\ncrée trois références\nvers la même sous-liste.\nModifier une sous-liste\nrevient donc à modifier\nles trois. La solution\nconsiste à utiliser une\ncompréhension de liste :\n`[[0] * 3 for _ in range(3)]`.\n"
        },
        {
          "text": "Une exception est levée",
          "correct": false,
          "feedback": "Le code est parfaitement\nvalide.\n"
        },
        {
          "text": "`[[1, 0, 0], [0, 0, 0], [0, 0, 0]]`",
          "correct": false,
          "feedback": "Cette réponse ignore que\nles trois sous-listes\nréférencent le même objet.\n"
        },
        {
          "text": "`[[0, 0, 0], [0, 0, 0], [0, 0, 0]]`",
          "correct": false,
          "feedback": "On a affecté\n`grille[0][0] = 1`, donc\nla valeur $1$ apparaît\nquelque part dans le\nrésultat.\n"
        }
      ],
      "explanation": "C'est un piège classique. La\nbonne pratique consiste à\nutiliser une compréhension de\nliste pour créer chaque\nsous-liste indépendamment."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "tuple",
        "mutabilite",
        "liste-de-tuples"
      ],
      "title": "Corriger une note dans une liste de p-uplets",
      "statement": "Les notes d'une classe sont rangées dans\n`eleves = [(\"Ana\", 12), (\"Bilal\", 9), (\"Chloé\", 15)]`,\nun p-uplet par élève. La note de Bilal est\nfausse, il a eu $11$. Quelle instruction\ncorrige la liste sans erreur ?",
      "options": [
        {
          "text": "`eleves[1][1] = 11`",
          "correct": false,
          "feedback": "Erreur : `eleves[1]` est le p-uplet\n`(\"Bilal\", 9)`, et un p-uplet est immuable.\nPython lève\n`TypeError: 'tuple' object does not support item assignment`.\n"
        },
        {
          "text": "`eleves[1] = (\"Bilal\", 11)`",
          "correct": true,
          "feedback": "Bonne réponse : on ne modifie pas le p-uplet,\non le remplace. La liste, elle, est mutable :\nl'affectation range un nouveau p-uplet à\nl'indice $1$, et la liste vaut\n`[(\"Ana\", 12), (\"Bilal\", 11), (\"Chloé\", 15)]`.\n"
        },
        {
          "text": "`eleves[\"Bilal\"] = 11`",
          "correct": false,
          "feedback": "Erreur : une liste s'indexe par un entier,\npas par une chaîne ; cette écriture est celle\nd'un dictionnaire. Python lève\n`TypeError: list indices must be integers or slices, not str`.\n"
        },
        {
          "text": "`eleves[1].append(11)`",
          "correct": false,
          "feedback": "Erreur : `eleves[1]` est un p-uplet, qui n'a\npas de méthode `append`. Python lève\n`AttributeError: 'tuple' object has no attribute 'append'`.\nEt même sur une liste, on aurait ajouté $11$\nau lieu de remplacer $9$.\n"
        }
      ],
      "explanation": "Dans une liste de p-uplets, chaque enregistrement\nest immuable, mais la liste qui les contient est\nmutable. Pour corriger un champ, on construit un\nnouveau p-uplet et on l'affecte à l'indice voulu :\n`eleves[1] = (\"Bilal\", 11)`. Toute tentative de\nmodifier le p-uplet sur place lève une exception."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "liste-vs-tuple-perf"
      ],
      "title": "Tuples pour les constantes",
      "statement": "Pour stocker des **constantes**\nque l'on ne modifiera jamais,\nquelle structure est la plus\nindiquée ?",
      "options": [
        {
          "text": "Un dictionnaire associant des clés à des valeurs",
          "correct": false,
          "feedback": "Le dictionnaire associe\ndes clés à des valeurs.\nPour de simples\nconstantes, c'est plus\nlourd qu'un tuple.\n"
        },
        {
          "text": "Un tuple, immuable",
          "correct": true,
          "feedback": "Bonne réponse : le tuple signale\nl'immuabilité et reste un peu plus\nléger qu'une liste. Par exemple,\n`JOURS = (\"lun\", \"mar\",\n\"mer\", ...)`. Le lecteur\ndu code comprend\nimmédiatement que ces\ndonnées ne changeront\npas.\n"
        },
        {
          "text": "Une liste, que l'on pourra compléter si les constantes évoluent",
          "correct": false,
          "feedback": "Erreur : une liste fonctionnerait, mais\nson caractère modifiable contredit\nl'intention (des données figées) et\nn'avertit pas le lecteur du code.\n"
        },
        {
          "text": "Une chaîne de caractères, les valeurs séparées par des virgules",
          "correct": false,
          "feedback": "Une chaîne n'est pas\nadaptée pour stocker des\néléments séparés, sauf\ndans des cas\nparticuliers.\n"
        }
      ],
      "explanation": "Convention Python : on\nutilise les tuples pour les\ndonnées figées, et les\nlistes pour les données qui\névoluent au cours du\nprogramme. Cette distinction\naméliore la lisibilité du\ncode."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes\nsur les tuples et les listes,\nlaquelle est **fausse** ?",
      "options": [
        {
          "text": "Les deux structures supportent l'indexation et le découpage",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte : on peut écrire\n`l[0]`, `l[1:3]` aussi\nbien sur une liste que\nsur un tuple.\n"
        },
        {
          "text": "Les listes peuvent être utilisées comme clés de dictionnaire",
          "correct": true,
          "feedback": "Cette affirmation est\nfausse (donc c'est la\nbonne réponse). Les\nlistes sont mutables,\ndonc non hashables, et\nne peuvent pas servir\nde clé. Seuls les\ntuples (à condition que\ntous leurs éléments\nsoient hashables)\npeuvent l'être.\n"
        },
        {
          "text": "`liste = []` crée une liste vide ; `t = ()` crée un tuple vide",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte.\n"
        },
        {
          "text": "Une liste est mutable, un tuple est immuable",
          "correct": false,
          "feedback": "Cette affirmation est\ncorrecte. C'est même la\ndifférence la plus\nfondamentale entre les\ndeux structures.\n"
        }
      ],
      "explanation": "Mémo utile : les clés d'un\ndictionnaire doivent être\nstables dans le temps. Tout\nce qui est mutable n'offre\npas cette stabilité, et ne\npeut donc pas servir de\nclé."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "parcours",
        "inversion",
        "construction-liste"
      ],
      "title": "Renverser une liste",
      "statement": "On dispose d'une liste `t = [10, 20, 30, 40, 50]` et\nl'on veut construire une nouvelle liste `r` contenant\nles mêmes éléments dans l'ordre inverse, soit\n`[50, 40, 30, 20, 10]`. Quel code convient ?",
      "options": [
        {
          "text": "```python\nr = []\nfor x in t:\n    r.append(x)\n```\n",
          "correct": false,
          "feedback": "Erreur : chaque élément est ajouté à la fin de\n`r` dans l'ordre de parcours. On obtient une\nsimple copie `[10, 20, 30, 40, 50]`.\n"
        },
        {
          "text": "```python\nr = []\nfor x in t:\n    r = [x] + r\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : à chaque tour, `[x] + r` place\nle nouvel élément **devant** ceux déjà rangés.\n`r` vaut successivement `[10]`, `[20, 10]`,\n`[30, 20, 10]`, jusqu'à `[50, 40, 30, 20, 10]`.\n"
        },
        {
          "text": "```python\nr = []\nfor i in range(len(t)):\n    r.append(t[-i])\n```\n",
          "correct": false,
          "feedback": "Erreur : au premier tour, `i` vaut $0$ et `t[-0]`\nest `t[0]`, le premier élément. Le résultat est\n`[10, 50, 40, 30, 20]`, décalé d'un cran.\n"
        },
        {
          "text": "```python\nr = []\nfor i in range(len(t)):\n    r.append(t[len(t) - i])\n```\n",
          "correct": false,
          "feedback": "Erreur : au premier tour, `t[len(t) - 0]` vaut\n`t[5]`, hors de la liste, d'où une `IndexError`.\nIl faudrait écrire `t[len(t) - 1 - i]`.\n"
        }
      ],
      "explanation": "Construire la liste renversée revient à insérer\nchaque élément en tête, ou à lire la liste de la fin\nvers le début avec les indices `len(t) - 1 - i`.\nDans les deux cas, `t` n'est pas modifiée."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "methodes",
        "count",
        "index"
      ],
      "title": "Compter et trouver dans une liste",
      "statement": "Soit `t = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]`. Que\nrenvoient successivement les expressions\n`t.count(5)` et `t.index(5)` ?",
      "options": [
        {
          "text": "`5` puis `3`",
          "correct": false,
          "feedback": "Erreur : confusion entre les rôles. La méthode\n`count` répond à « combien ? » (ici trois fois)\net `index` à « où ? » (ici à l'indice $4$).\n"
        },
        {
          "text": "`3` puis `4`",
          "correct": true,
          "feedback": "Bonne réponse : `t.count(5)` compte le nombre\nd'occurrences de $5$ dans la liste, il y en a\ntrois (indices $4$, $8$ et $10$).\n`t.index(5)` renvoie l'indice de la **première**\noccurrence, ici $4$ (l'indexation commence à\nzéro).\n"
        },
        {
          "text": "`5` puis `5`",
          "correct": false,
          "feedback": "Erreur : la valeur $5$ est mentionnée trois\nfois et son premier indice n'est pas $5$. Bien\ndistinguer `count` (combien d'occurrences) de\n`index` (à quelle position).\n"
        },
        {
          "text": "`3` puis `5`",
          "correct": false,
          "feedback": "Erreur : `count` est correct ($3$ occurrences),\nmais `index` renvoie l'indice du **premier**\n$5$, pas la valeur $5$. La première occurrence\nse trouve en position $4$.\n"
        }
      ],
      "explanation": "Méthodes utiles sur les listes : `count(x)` (nombre\nd'occurrences), `index(x)` (premier indice de `x`,\nlève `ValueError` si absent), `remove(x)` (supprime\nla première occurrence). Pour vérifier la présence\nsans déclencher d'erreur, préférer `if x in t:`."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "append",
        "extend",
        "concatenation"
      ],
      "title": "append, extend et concaténation",
      "statement": "On exécute :\n\n```python\na = [1, 2, 3]\nb = [4, 5]\na.append(b)\n```\n\nQue vaut la liste `a` après cette exécution ?",
      "options": [
        {
          "text": "Le code lève une erreur",
          "correct": false,
          "feedback": "Erreur : la méthode `append` accepte n'importe\nquel objet, y compris une liste. Le code\ns'exécute sans erreur, même si le résultat\npeut surprendre.\n"
        },
        {
          "text": "`[1, 2, 3, [4, 5]]`",
          "correct": true,
          "feedback": "Bonne réponse : `append` ajoute son paramètre\ntel quel à la fin de la liste, sans déballage.\nComme `b` est lui-même une liste, on obtient\nune liste imbriquée.\n"
        },
        {
          "text": "`[1, 2, 3, 4, 5]`",
          "correct": false,
          "feedback": "Erreur : ce serait le résultat de\n`a = a + b`, qui enchaîne les éléments de `b`\nun par un après ceux de `a`. La méthode\n`append`, elle, ajoute son paramètre comme un\n**unique élément**.\n"
        },
        {
          "text": "`[[1, 2, 3], 4, 5]`",
          "correct": false,
          "feedback": "Erreur : `append` ajoute en **fin** de liste,\npas au début. Et c'est `b` qui est ajouté, pas\n`a`.\n"
        }
      ],
      "explanation": "Deux opérations à distinguer : `a.append(x)`\najoute `x` à la fin de `a` comme un seul élément,\nen modifiant `a` sur place ; `a + b` produit une\n**nouvelle** liste formée des éléments de `a`\npuis de ceux de `b`, sans modifier ni `a` ni `b`.\nPour obtenir `[1, 2, 3, 4, 5]` dans `a`, on écrit\ndonc `a = a + b`. Confondre les deux est l'une des\nerreurs les plus fréquentes en Python."
    }
  ]
}