{
  "chapter": {
    "id": "recherche-lineaire",
    "level": "premiere",
    "theme": "Algorithmique",
    "title": "Recherche linéaire",
    "description": "Recherche séquentielle d'un élément dans une liste,\nparcours élément par élément, cas favorables et\ndéfavorables, complexité, comparaison avec la recherche\ndichotomique, variantes (indice, booléen, toutes les\noccurrences).",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Recherche linéaire ou séquentielle",
      "statement": "En quoi consiste la **recherche linéaire** (ou séquentielle) ?",
      "options": [
        {
          "text": "Sauter directement à la position centrale puis comparer",
          "correct": false,
          "feedback": "Erreur : c'est l'idée de la recherche\n**dichotomique**, qui n'est applicable qu'à\nune liste triée.\n"
        },
        {
          "text": "Parcourir la liste élément par élément et comparer chaque valeur à celle recherchée",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'algorithme le plus\nsimple. Il fonctionne sur n'importe quelle\nliste (triée ou non).\n"
        },
        {
          "text": "Utiliser un dictionnaire pour aller directement à la valeur",
          "correct": false,
          "feedback": "Erreur : c'est une autre stratégie (table de\nhachage), différente de la recherche\nlinéaire.\n"
        },
        {
          "text": "Trier la liste puis chercher le premier élément",
          "correct": false,
          "feedback": "Erreur : la recherche linéaire ne nécessite\npas de tri préalable.\n"
        }
      ],
      "explanation": "La recherche linéaire est universelle : elle\nfonctionne sur tout itérable (liste, chaîne,\ntuple) sans hypothèse sur l'ordre des éléments.\nSa simplicité a un prix : elle peut nécessiter\njusqu'à n comparaisons."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "hypothese"
      ],
      "title": "Liste triée ou non ?",
      "statement": "La recherche linéaire suppose-t-elle que la liste\nsoit **triée** ?",
      "options": [
        {
          "text": "Uniquement si la liste contient des nombres",
          "correct": false,
          "feedback": "Erreur : la recherche linéaire fonctionne\nquel que soit le type des éléments\n(nombres, chaînes, objets...).\n"
        },
        {
          "text": "Non, elle fonctionne sur n'importe quelle liste",
          "correct": true,
          "feedback": "Bonne réponse : c'est précisément l'avantage\nde la recherche linéaire. On peut l'appliquer\nà des données non triées (par exemple, une\nliste de noms saisis dans l'ordre d'arrivée).\n"
        },
        {
          "text": "Uniquement si la liste contient au moins 10 éléments",
          "correct": false,
          "feedback": "Erreur : la recherche linéaire fonctionne\naussi sur des listes courtes ou vides.\n"
        },
        {
          "text": "Oui, sinon elle ne fonctionne pas",
          "correct": false,
          "feedback": "Erreur : c'est la **dichotomique** qui exige\nune liste triée. La linéaire fonctionne sans\ncondition.\n"
        }
      ],
      "explanation": "Quand la liste est triée, on peut faire mieux\navec la dichotomique (O(log n)). Mais la linéaire\nreste un choix correct, simple et universel."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "complexite-pire"
      ],
      "title": "Pire cas",
      "statement": "Quel est le **nombre maximal de comparaisons** dans\nune recherche linéaire dans une liste de longueur n ?",
      "options": [
        {
          "text": "n²",
          "correct": false,
          "feedback": "Erreur : on parcourt la liste **une seule\nfois**, sans imbrication de boucle.\n"
        },
        {
          "text": "log(n)",
          "correct": false,
          "feedback": "Erreur : c'est la complexité de la recherche\ndichotomique, applicable seulement aux listes\ntriées.\n"
        },
        {
          "text": "n",
          "correct": true,
          "feedback": "Bonne réponse : dans le pire cas (élément\nabsent ou en dernière position), il faut\ncomparer chacun des n éléments. La complexité\nest en O(n).\n"
        },
        {
          "text": "n / 2",
          "correct": false,
          "feedback": "Erreur : n / 2 est le nombre **moyen** de\ncomparaisons (en supposant l'élément présent\nen position aléatoire), pas le maximum.\n"
        }
      ],
      "explanation": "Pire cas : l'élément n'est pas présent. On a fait\nn comparaisons négatives. Meilleur cas : l'élément\nest en première position, une seule comparaison."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "arret"
      ],
      "title": "Quand arrête-t-on la boucle ?",
      "statement": "Dans une recherche linéaire bien programmée, quand\narrête-t-on de parcourir la liste ?",
      "options": [
        {
          "text": "Au bout de log(n) comparaisons",
          "correct": false,
          "feedback": "Erreur : c'est la dichotomique. Sans\nhypothèse de tri, on ne peut pas s'arrêter\navant.\n"
        },
        {
          "text": "Dès que l'élément n'est pas trouvé",
          "correct": false,
          "feedback": "Erreur : si le premier élément n'est pas le\nbon, l'élément peut quand même être plus loin.\n"
        },
        {
          "text": "Toujours après avoir parcouru toute la liste",
          "correct": false,
          "feedback": "Pas optimal : si on a déjà trouvé l'élément,\ninutile de continuer. Mais cela donne malgré\ntout un résultat correct (juste plus lent).\n"
        },
        {
          "text": "Dès que l'élément cherché est trouvé, ou en arrivant à la fin de la liste",
          "correct": true,
          "feedback": "Bonne réponse : on s'arrête au plus tôt avec\nun `break` ou un `return`. Cela améliore le\ntemps moyen mais ne change pas le pire cas\n(élément absent, parcours complet).\n"
        }
      ],
      "explanation": "Schéma Python typique :\n`for i, x in enumerate(liste):`\n`    if x == cible: return i`\n`return -1`."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "retour"
      ],
      "title": "Que renvoyer si absent ?",
      "statement": "Si l'élément cherché n'est **pas dans la liste**,\nque peut-on raisonnablement renvoyer ?",
      "options": [
        {
          "text": "La dernière position de la liste (n - 1)",
          "correct": false,
          "feedback": "Erreur : on confondrait avec « élément en\ndernière position ».\n"
        },
        {
          "text": "La position 0 (par défaut)",
          "correct": false,
          "feedback": "Erreur : on confondrait avec « élément en\npremière position ».\n"
        },
        {
          "text": "La longueur de la liste",
          "correct": false,
          "feedback": "Erreur : valeur arbitraire, pas une\nconvention reconnue.\n"
        },
        {
          "text": "`None` ou `-1`, pour signaler l'absence",
          "correct": true,
          "feedback": "Bonne réponse : ce sont les deux conventions\ncourantes. `str.find` renvoie `-1`,\n`dict.get` renvoie `None`. À la maison de\nchoisir, mais il faut **être cohérent** dans\ntout le code.\n"
        }
      ],
      "explanation": "Lever une exception (`ValueError`, comme\n`list.index`) est aussi une option. La méthode\nPython `str.find` renvoie `-1`, mais `str.index`\nlève une exception : il existe les deux."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "code-python"
      ],
      "title": "Code Python typique",
      "statement": "Quel code implémente correctement la recherche\nlinéaire et renvoie l'**indice** de la cible (ou\n-1 si absente) ?",
      "options": [
        {
          "text": "```python\ndef chercher(liste, cible):\n    for i in range(len(liste)):\n        if liste[i] == cible:\n            return i\n        return -1\n```\n",
          "correct": false,
          "feedback": "Erreur d'**indentation** : `return -1` est\ndans la boucle, ce qui termine après la\npremière comparaison. La fonction renverrait\ntoujours 0 ou -1 selon le premier élément.\n"
        },
        {
          "text": "```python\ndef chercher(liste, cible):\n    for x in liste:\n        if x == cible:\n            return x\n    return -1\n```\n",
          "correct": false,
          "feedback": "Erreur : on renvoie `x` (la **valeur**), pas\nson indice. La spécification demandait\nl'indice.\n"
        },
        {
          "text": "```python\ndef chercher(liste, cible):\n    for i in range(len(liste)):\n        if liste[i] == cible:\n            return i\n    return -1\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : parcours par indice, retour\nprécoce dès la cible trouvée, -1 si la\nboucle se termine sans trouver.\n"
        },
        {
          "text": "```python\ndef chercher(liste, cible):\n    while True:\n        if liste[0] == cible:\n            return 0\n```\n",
          "correct": false,
          "feedback": "Erreur : boucle infinie si l'élément n'est\npas en première position.\n"
        }
      ],
      "explanation": "Vérifier deux choses dans toute fonction de\nrecherche : le **type** de retour (indice,\nbooléen, ou objet), et le **comportement** en\ncas d'absence."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "operateur-in"
      ],
      "title": "Opérateur `in`",
      "statement": "L'expression Python `42 in liste` réalise\nimplicitement quelle opération ?",
      "options": [
        {
          "text": "Un tri",
          "correct": false,
          "feedback": "Erreur : `in` ne modifie pas la liste.\n"
        },
        {
          "text": "Une recherche dichotomique (rapide)",
          "correct": false,
          "feedback": "Erreur : Python ne suppose pas que la liste\nest triée. Il fait du linéaire.\n"
        },
        {
          "text": "Une recherche linéaire de gauche à droite, qui s'arrête dès qu'une correspondance est trouvée",
          "correct": true,
          "feedback": "Bonne réponse : sur une `list`, `in` est en\nO(n). Sur un `set` ou `dict`, il est en O(1)\n(table de hachage).\n"
        },
        {
          "text": "Une vérification de type",
          "correct": false,
          "feedback": "Erreur : `in` teste l'appartenance, pas le\ntype.\n"
        }
      ],
      "explanation": "L'opérateur `in` cache la complexité. Sur des\ngros volumes, préférer `set` (O(1)) à `list`\n(O(n)) pour les tests d'appartenance répétés."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "liste-vide"
      ],
      "title": "Liste vide",
      "statement": "Que doit renvoyer une recherche linéaire (qui\nrenvoie l'indice ou -1) sur une **liste vide** ?",
      "options": [
        {
          "text": "Une exception",
          "correct": false,
          "feedback": "Pas nécessairement. Avec le schéma\nstandard, la boucle ne s'exécute pas et la\nfonction renvoie `-1` proprement.\n"
        },
        {
          "text": "`None`",
          "correct": false,
          "feedback": "Possible (autre convention), mais selon\nnotre énoncé, la fonction renvoie `-1`.\n"
        },
        {
          "text": "-1 (élément absent)",
          "correct": true,
          "feedback": "Bonne réponse : aucune valeur n'est dans la\nliste vide, donc tout élément y est absent.\nLa boucle ne s'exécute pas, on tombe\ndirectement sur `return -1`.\n"
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : 0 signifierait « trouvé en première\nposition » alors qu'il n'y a aucune position.\n"
        }
      ],
      "explanation": "Cas limite à toujours tester. Bonne pratique :\nécrire un test unitaire `assert chercher([], x) == -1`."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "min-max"
      ],
      "title": "Recherche de minimum",
      "statement": "Trouver le **minimum** d'une liste est aussi un\ncas de recherche linéaire. Quelle est sa\ncomplexité ?",
      "options": [
        {
          "text": "O(n²) : double boucle nécessaire",
          "correct": false,
          "feedback": "Erreur : un seul parcours suffit (en gardant\nle minimum courant dans une variable).\n"
        },
        {
          "text": "O(1) : on prend juste le premier élément",
          "correct": false,
          "feedback": "Erreur : le premier élément n'est pas\nforcément le minimum (sauf liste triée).\n"
        },
        {
          "text": "O(log n) : recherche dichotomique",
          "correct": false,
          "feedback": "Erreur : ne fonctionne pas, même sur liste\ntriée la dichotomique cherche une cible\nprécise, pas un extremum.\n"
        },
        {
          "text": "O(n) : on doit comparer tous les éléments",
          "correct": true,
          "feedback": "Bonne réponse : pour garantir le minimum, il\nfaut **examiner chaque** élément. On ne peut\npas s'arrêter avant la fin (contrairement à\nla recherche d'une cible précise).\n"
        }
      ],
      "explanation": "Note importante : ici on **ne peut pas s'arrêter\ntôt** car on ne sait pas si un élément plus\npetit existe plus loin. C'est différent de la\nrecherche d'une valeur précise."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "trace"
      ],
      "title": "Trace simple",
      "statement": "On cherche `cible = 7` dans `liste = [3, 5, 7, 2, 7]`.\nCombien de comparaisons effectue une recherche\nlinéaire **avec arrêt précoce** ?",
      "options": [
        {
          "text": "5",
          "correct": false,
          "feedback": "Erreur : on ne parcourt pas toute la liste si\non s'arrête au premier `7` rencontré.\n"
        },
        {
          "text": "1",
          "correct": false,
          "feedback": "Erreur : il faut comparer 3, puis 5 avant\nd'arriver à 7.\n"
        },
        {
          "text": "2",
          "correct": false,
          "feedback": "Erreur : il faut bien tester `liste[2]` (et\nla trouver égale à 7) pour conclure.\n"
        },
        {
          "text": "3",
          "correct": true,
          "feedback": "Bonne réponse : `liste[0]=3` (≠), `liste[1]=5`\n(≠), `liste[2]=7` (=) → on s'arrête. Trois\ncomparaisons.\n"
        }
      ],
      "explanation": "Sans arrêt précoce, on aurait fait 5\ncomparaisons et compté 2 occurrences. Avec\narrêt précoce, 3 comparaisons et indice retourné\ndirectement (= 2)."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "meilleur-cas"
      ],
      "title": "Meilleur cas",
      "statement": "Quelle est la complexité dans le **meilleur cas**\nde la recherche linéaire ?",
      "options": [
        {
          "text": "O(1)",
          "correct": true,
          "feedback": "Bonne réponse : si l'élément cherché est en\n**première position**, on s'arrête après une\nseule comparaison.\n"
        },
        {
          "text": "O(n²)",
          "correct": false,
          "feedback": "Erreur : pas de double boucle.\n"
        },
        {
          "text": "O(log n)",
          "correct": false,
          "feedback": "Erreur : aucun mécanisme dichotomique ici.\n"
        },
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : O(n) est le pire cas.\n"
        }
      ],
      "explanation": "Pour une recherche d'un élément aléatoirement\nplacé dans la liste, la complexité **moyenne**\nest O(n / 2), souvent simplifiée en O(n) car les\nconstantes sont ignorées dans la notation O."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "variante-booleen"
      ],
      "title": "Variante booléenne",
      "statement": "On veut juste savoir si l'élément est présent ou\nnon (sans avoir besoin de l'indice). Quelle\nversion est la plus pythonique ?",
      "options": [
        {
          "text": "```python\ndef present(liste, cible):\n    return liste.index(cible)\n```\n",
          "correct": false,
          "feedback": "Erreur : `index` lève `ValueError` si\nl'élément est absent (pas un booléen).\n"
        },
        {
          "text": "```python\ndef present(liste, cible):\n    return cible in liste\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : Python a une syntaxe native\npour cela. Sous le capot, c'est exactement\nla même boucle linéaire, mais le code est\nplus lisible.\n"
        },
        {
          "text": "```python\ndef present(liste, cible):\n    for x in liste:\n        if x == cible:\n            return True\n    return False\n```\n",
          "correct": false,
          "feedback": "Code correct, mais plus long que nécessaire.\nPython offre une syntaxe plus directe.\n"
        },
        {
          "text": "```python\ndef present(liste, cible):\n    return cible == liste\n```\n",
          "correct": false,
          "feedback": "Erreur : on compare l'élément à la liste\nentière, ce qui renvoie toujours `False`\n(sauf cas dégénéré).\n"
        }
      ],
      "explanation": "Maxime : « N'écris pas en Python ce que Python\nsait déjà faire. » L'opérateur `in` est conçu\npour exactement ce cas."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "toutes-occurrences"
      ],
      "title": "Toutes les occurrences",
      "statement": "Pour trouver **toutes** les positions où la cible\napparaît dans la liste, quelle adaptation de la\nrecherche linéaire utilise-t-on ?",
      "options": [
        {
          "text": "On trie la liste avant de chercher",
          "correct": false,
          "feedback": "Erreur : trier modifie l'ordre et fait perdre\nles positions originales.\n"
        },
        {
          "text": "On parcourt toute la liste et on enregistre tous les indices où l'élément correspond",
          "correct": true,
          "feedback": "Bonne réponse : pas d'arrêt précoce ici, on\naccumule dans une liste de résultats.\nComplexité : O(n) (parcours unique).\n"
        },
        {
          "text": "On utilise une recherche dichotomique",
          "correct": false,
          "feedback": "Erreur : la dichotomique trouve **une**\noccurrence dans une liste triée, mais ne\nliste pas toutes les occurrences naturellement.\n"
        },
        {
          "text": "On s'arrête à la première occurrence et on l'enregistre",
          "correct": false,
          "feedback": "Erreur : on raterait les suivantes.\n"
        }
      ],
      "explanation": "Code typique :\n`[i for i, x in enumerate(liste) if x == cible]`.\nÉlégant, en une ligne, en O(n)."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "comparaison-dichotomique"
      ],
      "title": "Linéaire vs dichotomique",
      "statement": "Pour rechercher un élément dans une **liste triée**\nde 1 000 000 d'éléments, combien de comparaisons\nau pire avec chacune des deux méthodes ?",
      "options": [
        {
          "text": "Linéaire : 1 000 000 ; dichotomique : ~ 20",
          "correct": true,
          "feedback": "Bonne réponse : log₂(10⁶) ≈ 20. La\ndichotomique est ~ 50 000 fois plus rapide\ndans ce cas. C'est l'intérêt majeur du tri.\n"
        },
        {
          "text": "Linéaire : 1 000 000 ; dichotomique : 1 000",
          "correct": false,
          "feedback": "Erreur : la dichotomique est en O(log n),\nbeaucoup plus efficace.\n"
        },
        {
          "text": "Linéaire : 1 000 ; dichotomique : 1 000 000",
          "correct": false,
          "feedback": "Erreur : valeurs inversées.\n"
        },
        {
          "text": "Les deux sont identiques",
          "correct": false,
          "feedback": "Erreur : la dichotomique est exponentiellement\nplus rapide sur les grandes tailles.\n"
        }
      ],
      "explanation": "log₂(2¹⁰) = 10, log₂(2²⁰) = 20. Doubler la\ntaille de la liste ajoute juste **une**\ncomparaison à la dichotomique, mais double les\ncomparaisons de la linéaire."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "bug-init"
      ],
      "title": "Bug d'initialisation",
      "statement": "Un élève écrit\n```python\ndef chercher(liste, cible):\n    for i in range(len(liste)):\n        if liste[i] == cible:\n            trouve = True\n        else:\n            trouve = False\n    return trouve\n```\nQuel est le bug ?",
      "options": [
        {
          "text": "`return` doit être à l'intérieur de la boucle",
          "correct": false,
          "feedback": "Erreur : ce serait un autre code, mais ce\nn'est pas la cause du bug actuel.\n"
        },
        {
          "text": "À chaque itération, `trouve` est réinitialisé. Seul le résultat de la dernière comparaison est renvoyé.",
          "correct": true,
          "feedback": "Bonne réponse : si la cible est trouvée puis\nque la dernière itération échoue, `trouve`\nfinit à `False`. Solution : initialiser\n`trouve = False` avant la boucle, et **ne\nfaire** que `trouve = True` à l'intérieur\n(sans `else`).\n"
        },
        {
          "text": "La fonction est trop lente",
          "correct": false,
          "feedback": "Erreur : la performance n'est pas le\nproblème principal.\n"
        },
        {
          "text": "La variable `trouve` n'est pas définie hors de la boucle",
          "correct": false,
          "feedback": "Erreur : Python autorise (à risque) la sortie\nd'une variable de boucle, tant que la\nboucle s'exécute au moins une fois.\n"
        }
      ],
      "explanation": "Schéma correct : initialiser un drapeau\n**avant** la boucle, ne le mettre à `True`\nqu'**en cas de succès**. Mieux : utiliser\n`return True` direct et `return False` après la\nboucle."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "while-vs-for"
      ],
      "title": "While ou for ?",
      "statement": "Quelle est la différence pratique entre une\nrecherche linéaire écrite avec `for` et avec\n`while` ?",
      "options": [
        {
          "text": "`while` ne fonctionne pas sur les listes",
          "correct": false,
          "feedback": "Erreur : `while` fonctionne avec n'importe\nquelle condition booléenne.\n"
        },
        {
          "text": "`for` est plus lisible quand on parcourt tous les indices ; `while` est plus naturel pour combiner deux conditions d'arrêt (par exemple `i < n and not trouve`)",
          "correct": true,
          "feedback": "Bonne réponse : `while` permet une condition\ncomposée. `for` est plus concis quand on\nitère simplement sur la longueur.\n"
        },
        {
          "text": "`for` est plus rapide",
          "correct": false,
          "feedback": "Performance équivalente. La différence est\nstylistique.\n"
        },
        {
          "text": "Les deux sont strictement équivalents en Python",
          "correct": false,
          "feedback": "Sur le plan du résultat oui, sur le plan de\nla lisibilité non.\n"
        }
      ],
      "explanation": "Bonne pratique : `for` pour parcourir une\nstructure connue ; `while` quand on ne sait pas\nà l'avance combien d'itérations seront\nnécessaires (lecture utilisateur, condition\ncomplexe)."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "dictionnaire-recherche"
      ],
      "title": "Recherche dans un dictionnaire",
      "statement": "Pourquoi la recherche d'une **clé** dans un\ndictionnaire Python est-elle en O(1) (alors que\n`in liste` est en O(n)) ?",
      "options": [
        {
          "text": "Parce que les dictionnaires utilisent une table de hachage : la position d'une clé est calculée directement par une fonction de hachage",
          "correct": true,
          "feedback": "Bonne réponse : pas besoin de parcourir, on\ncalcule directement l'emplacement à partir\nde la clé. C'est l'avantage majeur de cette\nstructure.\n"
        },
        {
          "text": "Parce que les dictionnaires sont triés",
          "correct": false,
          "feedback": "Erreur : depuis Python 3.7, l'ordre\nd'insertion est conservé, mais cela n'a rien\nà voir avec la complexité de recherche.\n"
        },
        {
          "text": "Parce qu'ils contiennent moins de données",
          "correct": false,
          "feedback": "Erreur : un dict peut être gigantesque, sa\nrecherche reste en O(1).\n"
        },
        {
          "text": "Parce que Python est un langage rapide",
          "correct": false,
          "feedback": "Réponse vague et incorrecte : la rapidité\nvient de la **structure de données**, pas\ndu langage.\n"
        }
      ],
      "explanation": "À retenir : `liste` → `in` en O(n) ; `set` ou\n`dict` → `in` en O(1) en moyenne. Pour des tests\nd'appartenance répétés, transformer une liste en\n`set` est souvent payant."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "optimisation-position"
      ],
      "title": "Optimisation par fréquence",
      "statement": "Si l'on sait qu'un élément est **plus souvent\ncherché** que les autres, comment optimiser une\nsérie de recherches linéaires successives ?",
      "options": [
        {
          "text": "Trier la liste par ordre croissant",
          "correct": false,
          "feedback": "Le tri n'aide pas la linéaire (et coûte O(n\nlog n)). De plus, les éléments fréquents ne\nsont pas forcément les plus petits.\n"
        },
        {
          "text": "Doubler la taille de la liste",
          "correct": false,
          "feedback": "Erreur : cela aggrave le problème.\n"
        },
        {
          "text": "Placer les éléments les plus fréquents au début de la liste",
          "correct": true,
          "feedback": "Bonne réponse : la recherche linéaire avec\narrêt précoce trouvera ces éléments plus\nvite. Heuristique « move-to-front » :\ndéplacer chaque élément trouvé en tête.\n"
        },
        {
          "text": "Convertir la liste en chaîne",
          "correct": false,
          "feedback": "Erreur : aucun gain, et `in` sur une chaîne\nrecherche les sous-chaînes, pas les\néléments.\n"
        }
      ],
      "explanation": "Cette idée (« self-organizing list ») est\nutilisée en pratique dans certains caches.\nC'est un bon exemple où l'**ordre** des données\ninfluence la performance."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "recherche-tuple"
      ],
      "title": "Recherche dans liste de tuples",
      "statement": "Soit\n`eleves = [(\"Alice\", 17), (\"Bob\", 16), (\"Eve\", 18)]`.\nComment trouver l'âge d'Alice ?",
      "options": [
        {
          "text": "```python\neleves[\"Alice\"]\n```\n",
          "correct": false,
          "feedback": "Erreur : `eleves` est une liste, pas un\ndictionnaire. On ne peut pas l'indexer par\nchaîne.\n"
        },
        {
          "text": "```python\nfor nom, age in eleves:\n    if nom == \"Alice\":\n        return age\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on déballe chaque tuple en\ndeux variables, on teste le nom, on renvoie\nl'âge. Recherche linéaire classique.\n"
        },
        {
          "text": "```python\neleves.index((\"Alice\"))\n```\n",
          "correct": false,
          "feedback": "Erreur : `(\"Alice\")` est une chaîne (pas un\ntuple ; il manque la virgule). Et même\n`(\"Alice\",)` ne correspond pas car le tuple\nest `(\"Alice\", 17)`.\n"
        },
        {
          "text": "```python\neleves.find(\"Alice\")\n```\n",
          "correct": false,
          "feedback": "Erreur : la méthode `.find` n'existe pas sur\nles listes.\n"
        }
      ],
      "explanation": "Si l'on fait souvent ce genre de recherche,\nmieux vaut convertir en `dict` :\n`{nom: age for nom, age in eleves}` puis\n`eleves[\"Alice\"]` en O(1)."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "chaine"
      ],
      "title": "Sur une chaîne",
      "statement": "L'expression `\"a\" in \"abracadabra\"` :",
      "options": [
        {
          "text": "Lève une exception",
          "correct": false,
          "feedback": "Erreur : la syntaxe est valide.\n"
        },
        {
          "text": "Renvoie `False`",
          "correct": false,
          "feedback": "Erreur : `\"a\"` apparaît bien dans\n`\"abracadabra\"`.\n"
        },
        {
          "text": "Renvoie le nombre d'occurrences",
          "correct": false,
          "feedback": "Erreur : `in` renvoie un booléen, pas un\nentier. Pour le comptage, utiliser `.count()`.\n"
        },
        {
          "text": "Renvoie `True`",
          "correct": true,
          "feedback": "Bonne réponse : `in` sur une chaîne fait une\nrecherche de **sous-chaîne** (pas de\ncaractère unique seulement). `\"a\"` est\nprésent comme sous-chaîne.\n"
        }
      ],
      "explanation": "Subtilité : sur une chaîne, `\"abc\" in \"ababcabc\"`\nrenvoie aussi `True` (sous-chaîne, pas\ncaractère). Sur une liste, `\"abc\" in liste`\nteste l'égalité avec chaque élément."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "bug-break"
      ],
      "title": "Bug : oubli du `break`",
      "statement": "```python\ndef trouver(liste, cible):\n    indice = -1\n    for i in range(len(liste)):\n        if liste[i] == cible:\n            indice = i\n    return indice\n```\nQuel est le comportement de cette fonction si la\ncible apparaît plusieurs fois ?",
      "options": [
        {
          "text": "Elle renvoie -1",
          "correct": false,
          "feedback": "Erreur : -1 est l'initialisation, mais elle\nest écrasée si une correspondance existe.\n"
        },
        {
          "text": "Elle renvoie l'indice de la dernière occurrence",
          "correct": true,
          "feedback": "Bonne réponse : la boucle continue jusqu'au\nbout, et `indice = i` écrase la valeur à\nchaque correspondance. À la fin, c'est la\ndernière occurrence qui reste.\n"
        },
        {
          "text": "Elle entre dans une boucle infinie",
          "correct": false,
          "feedback": "Erreur : `for` sur `range` est borné.\n"
        },
        {
          "text": "Elle renvoie l'indice de la première occurrence",
          "correct": false,
          "feedback": "Erreur : il manque un `break` ou un `return`\npour s'arrêter à la première occurrence.\n"
        }
      ],
      "explanation": "Cas piège mais utile : ce code peut être correct\nsi l'on cherche **la dernière occurrence**. La\nversion classique « première occurrence »\nrequiert un `return i` ou un `break` après\nl'affectation."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "choix-algorithme"
      ],
      "title": "Choisir le bon algorithme",
      "statement": "Pour rechercher un élément dans une liste de\n100 éléments, **non triée**, qu'on ne consultera\nqu'une seule fois, quelle stratégie est la plus\nefficace **globalement** ?",
      "options": [
        {
          "text": "Recherche linéaire directement",
          "correct": true,
          "feedback": "Bonne réponse : pour **une seule**\nrecherche, la linéaire (au plus 100\ncomparaisons) est plus rapide que tri +\ndichotomie. L'investissement du tri n'est\nrentable que pour de **multiples**\nrecherches.\n"
        },
        {
          "text": "Convertir en dictionnaire",
          "correct": false,
          "feedback": "Possible mais O(n) pour construire le dict.\nPour une seule recherche, gain nul.\n"
        },
        {
          "text": "Faire la recherche en parallèle sur 4 cœurs",
          "correct": false,
          "feedback": "Sur 100 éléments, le coût de\nparallélisation dépasse largement le coût\nde la recherche.\n"
        },
        {
          "text": "Trier puis dichotomie",
          "correct": false,
          "feedback": "Erreur : trier coûte O(n log n) ≈ 700\nopérations, plus 7 pour la dichotomie. Soit\n~ 707 opérations contre 100 max pour la\nlinéaire. Pas rentable pour une seule\nrecherche.\n"
        }
      ],
      "explanation": "Règle générale : prétraiter (trier, indexer)\nn'est rentable qu'au-delà d'un certain nombre\nde recherches. Pour une recherche unique, la\nlinéaire est imbattable en simplicité."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "boucle-correcte"
      ],
      "title": "Boucle while à bug",
      "statement": "```python\ndef chercher(liste, cible):\n    i = 0\n    while liste[i] != cible:\n        i += 1\n    return i\n```\nQuel problème pose ce code ?",
      "options": [
        {
          "text": "Aucun, le code est correct",
          "correct": false,
          "feedback": "Erreur : il y a un bug majeur lié au cas\nd'absence.\n"
        },
        {
          "text": "La fonction ne renvoie jamais rien",
          "correct": false,
          "feedback": "Erreur : elle renvoie `i` quand elle trouve\nla cible.\n"
        },
        {
          "text": "Si l'élément est absent, la boucle dépasse la fin de la liste et lève `IndexError`",
          "correct": true,
          "feedback": "Bonne réponse : il manque la condition\n`i < len(liste)` dans le `while`. Sans cela,\non accède à `liste[len(liste)]` qui n'existe\npas. Solution :\n`while i < len(liste) and liste[i] != cible:`.\n"
        },
        {
          "text": "Le code modifie la liste",
          "correct": false,
          "feedback": "Erreur : aucune modification de la liste.\n"
        }
      ],
      "explanation": "Bug très classique. Pour les boucles `while`,\ntoujours vérifier deux choses : la condition\nd'arrêt **et** l'évolution de la variable de\nboucle (pour éviter une boucle infinie ou un\ndépassement)."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "complexite-pratique"
      ],
      "title": "Comparer en pratique",
      "statement": "Sur ma machine, une recherche linéaire dans une\nliste de 1 million d'éléments prend ~ 30 ms. Que\nprendra une recherche dans une liste de 1\nmilliard d'éléments (si la mémoire le permet) ?",
      "options": [
        {
          "text": "Environ 60 ms (juste deux fois plus)",
          "correct": false,
          "feedback": "Erreur : 1 milliard, c'est mille fois plus\nque 1 million, pas deux fois.\n"
        },
        {
          "text": "Environ 30 secondes (1000 fois plus, car n est multiplié par 1000)",
          "correct": true,
          "feedback": "Bonne réponse : O(n) → temps proportionnel.\n1 milliard / 1 million = 1000, donc 30 ms ×\n1000 = 30 s. Cette estimation est précieuse\npour anticiper les performances.\n"
        },
        {
          "text": "Environ 30 secondes au carré",
          "correct": false,
          "feedback": "Erreur : ce serait O(n²), pas O(n).\n"
        },
        {
          "text": "Toujours environ 30 ms (constante)",
          "correct": false,
          "feedback": "Erreur : O(n) signifie que le temps croît\n**proportionnellement** à n.\n"
        }
      ],
      "explanation": "Compétence essentielle : savoir extrapoler le\ntemps d'exécution selon la complexité. La\nrecherche linéaire devient gênante au-delà de\nquelques millions d'éléments si elle est\nrépétée."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes sur la recherche\nlinéaire, laquelle est **fausse** ?",
      "options": [
        {
          "text": "En Python, l'opérateur `in` sur une liste fait une recherche linéaire",
          "correct": false,
          "feedback": "Vrai : `in` sur une `list` est en O(n) ; il\nfaudrait un `set` ou `dict` pour avoir O(1).\n"
        },
        {
          "text": "Elle fonctionne sur des listes triées comme non triées",
          "correct": false,
          "feedback": "Vrai : c'est l'avantage majeur sur la\ndichotomique.\n"
        },
        {
          "text": "Sa complexité au pire est O(n)",
          "correct": false,
          "feedback": "Vrai : parcours complet de la liste.\n"
        },
        {
          "text": "Elle est toujours plus rapide que la dichotomique",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : sur une liste\ntriée et de grande taille, la dichotomique\n(O(log n)) est exponentiellement plus\nrapide. La linéaire reste compétitive sur\ndes petites listes ou pour une recherche\nunique.\n"
        }
      ],
      "explanation": "Maxime : « Pas d'algorithme universellement\nmeilleur. » Le choix dépend des hypothèses\n(liste triée ?), du nombre de recherches, et\nde la taille des données."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "comptage-occurrences"
      ],
      "title": "Compter les occurrences",
      "statement": "On veut **compter** combien de fois la cible\napparaît dans la liste (et non simplement la\ntrouver). Quelle adaptation de la recherche\nlinéaire faut-il faire ?",
      "options": [
        {
          "text": "```python\ndef compter(liste, cible):\n    for x in liste:\n        if x == cible:\n            return 1\n    return 0\n```\n",
          "correct": false,
          "feedback": "Erreur classique : on s'arrête à la première\noccurrence et on renvoie toujours 0 ou 1, ce\nqui revient à un test d'appartenance, pas à\nun comptage.\n"
        },
        {
          "text": "```python\ndef compter(liste, cible):\n    return len(liste) if cible in liste else 0\n```\n",
          "correct": false,
          "feedback": "Erreur grossière : si la cible apparaît au\nmoins une fois, on renvoie la longueur\ntotale de la liste (donc tous les éléments\nsont comptés, même ceux qui ne sont pas la\ncible).\n"
        },
        {
          "text": "```python\ndef compter(liste, cible):\n    n = 0\n    for x in liste:\n        if x == cible:\n            n += 1\n    return n\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on parcourt **toute** la\nliste sans arrêt précoce, on incrémente un\ncompteur à chaque correspondance. Complexité\nO(n). Équivalent à `liste.count(cible)`.\n"
        },
        {
          "text": "```python\ndef compter(liste, cible):\n    return liste.index(cible)\n```\n",
          "correct": false,
          "feedback": "Erreur : `index` renvoie la position de la\npremière occurrence (et lève une exception\nsi elle est absente), pas le nombre total.\n"
        }
      ],
      "explanation": "Différence essentielle avec la recherche d'une\noccurrence : on ne peut pas s'arrêter au premier\nsuccès, donc le pire cas et le meilleur cas sont\nidentiques en O(n). En Python, la méthode\n`liste.count(cible)` fait exactement cela."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "recherche-predicat"
      ],
      "title": "Recherche par condition",
      "statement": "On cherche le **premier** élément d'une liste de\nnombres qui soit **strictement supérieur** à\n`100` (et son indice). Quel code applique\ncorrectement la recherche linéaire à ce cas ?",
      "options": [
        {
          "text": "```python\ndef premier_grand(liste):\n    liste.sort()\n    return liste[-1]\n```\n",
          "correct": false,
          "feedback": "Erreur double : on trie (ce qui modifie la\nliste et coûte O(n log n)), puis on renvoie\nle maximum, qui n'est pas forcément le\n**premier** élément supérieur à 100 dans\nl'ordre original.\n"
        },
        {
          "text": "```python\ndef premier_grand(liste):\n    return max(liste) > 100\n```\n",
          "correct": false,
          "feedback": "Erreur de spécification : on demande le\npremier élément supérieur à 100, pas un\nbooléen indiquant s'il en existe un. Et\ncette version coûte O(n) sans donner\nl'information utile.\n"
        },
        {
          "text": "```python\ndef premier_grand(liste):\n    for x in liste:\n        if x > 100:\n            return x\n        return None\n```\n",
          "correct": false,
          "feedback": "Erreur d'indentation : `return None` est\ndans la boucle, donc la fonction sort dès le\npremier élément (qui est rarement supérieur\nà 100). De plus, on ne renvoie pas l'indice.\n"
        },
        {
          "text": "```python\ndef premier_grand(liste):\n    for i, x in enumerate(liste):\n        if x > 100:\n            return (i, x)\n    return None\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : la recherche linéaire se\ngénéralise à n'importe quelle condition\n(prédicat). On parcourt la liste, on teste\nla condition, on renvoie dès le premier\nsuccès. Si aucun élément ne satisfait la\ncondition, on renvoie `None`.\n"
        }
      ],
      "explanation": "Schéma général de la recherche linéaire par\nprédicat. On peut le rendre plus pythonique\navec `next` : `next((x for x in liste if x > 100), None)`."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "tableau-2d"
      ],
      "title": "Recherche dans un tableau à deux dimensions",
      "statement": "On dispose d'une matrice carrée représentée par\nune liste de listes (par exemple\n`m = [[3, 7, 1], [8, 2, 5], [4, 9, 6]]`). On\ncherche le couple `(ligne, colonne)` de la\npremière occurrence de la valeur `5`. Quel code\nest correct ?",
      "options": [
        {
          "text": "```python\ndef chercher(m, cible):\n    if cible in m:\n        return m.index(cible)\n    return None\n```\n",
          "correct": false,
          "feedback": "Erreur : `in m` teste si la cible est l'une\ndes **lignes** de la matrice, pas l'un de\nses éléments. Ce code ne trouvera donc rien\n(sauf si la cible est elle-même une liste\nidentique à une ligne).\n"
        },
        {
          "text": "```python\ndef chercher(m, cible):\n    for ligne in m:\n        for x in ligne:\n            if x == cible:\n                return x\n    return None\n```\n",
          "correct": false,
          "feedback": "Erreur : on renvoie la **valeur** trouvée\n(ce qui est trivial puisqu'on connaît\ndéjà la cible), pas le couple de\ncoordonnées demandé. Il faut utiliser\n`enumerate` pour récupérer les indices.\n"
        },
        {
          "text": "```python\ndef chercher(m, cible):\n    for i in range(len(m)):\n        if m[i] == cible:\n            return i\n    return None\n```\n",
          "correct": false,
          "feedback": "Erreur : on compare une **ligne entière** à\nla cible (un nombre), ce qui sera toujours\nfaux. On confond élément de la matrice et\nligne.\n"
        },
        {
          "text": "```python\ndef chercher(m, cible):\n    for i in range(len(m)):\n        for j in range(len(m[i])):\n            if m[i][j] == cible:\n                return (i, j)\n    return None\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : double boucle imbriquée pour\nparcourir lignes et colonnes, retour\nprécoce dès la cible trouvée. Pour la\nmatrice donnée, on renvoie `(1, 2)`.\nComplexité O(L × C) où L est le nombre de\nlignes et C le nombre de colonnes.\n"
        }
      ],
      "explanation": "Pour une matrice non carrée, on remplace\n`len(m[i])` par la longueur de la ligne\ncourante. Avec `enumerate`, le code devient\nplus lisible :\n`for i, ligne in enumerate(m): for j, x in enumerate(ligne): ...`."
    }
  ]
}