{
  "chapter": {
    "id": "invariants-terminaison",
    "level": "premiere",
    "theme": "Algorithmique",
    "title": "Invariants et terminaison",
    "description": "Preuve de correction et de terminaison d'une boucle :\ninvariant de boucle (propriété conservée à chaque\nitération) et variant de boucle (quantité entière qui\ndécroît strictement). Application aux algorithmes\nclassiques (recherche, tri).",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-invariant"
      ],
      "title": "Invariant de boucle",
      "statement": "Qu'est-ce qu'un **invariant de boucle** ?",
      "options": [
        {
          "text": "Une exception levée pendant l'exécution de la boucle",
          "correct": false,
          "feedback": "Une exception est une\nerreur d'exécution. Un\ninvariant est, lui, une\npropriété logique\nmaintenue tout au long\nde la boucle.\n"
        },
        {
          "text": "Une variable dont la valeur ne change pas dans la boucle",
          "correct": false,
          "feedback": "Trop restrictif : un invariant peut concerner\nplusieurs variables ou des relations entre\nelles.\n"
        },
        {
          "text": "Une propriété conservée à chaque itération",
          "correct": true,
          "feedback": "Bonne réponse : c'est une propriété\nmathématique qu'on prouve vraie avant la\nboucle, puis qu'on prouve conservée à chaque\ntour. Elle reste vraie à la sortie.\n"
        },
        {
          "text": "Une boucle qui ne se termine pas",
          "correct": false,
          "feedback": "Erreur : une boucle infinie est un bug, pas\nun invariant.\n"
        }
      ],
      "explanation": "L'invariant sert à prouver que la boucle calcule\nbien ce qu'elle doit. À la sortie, l'invariant\ncombiné à la condition d'arrêt donne la\ncorrection de l'algorithme."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "terminaison-definition"
      ],
      "title": "Terminaison",
      "statement": "Qu'appelle-t-on **terminaison** d'un algorithme ?",
      "options": [
        {
          "text": "La présence d'une condition d'arrêt dans la boucle",
          "correct": false,
          "feedback": "Erreur : la présence d'une\ncondition d'arrêt ne suffit\npas. Si elle ne devient\njamais fausse, la boucle est\ninfinie : la terminaison est\nla propriété que l'algorithme\ns'arrête réellement.\n"
        },
        {
          "text": "Le fait que l'algorithme renvoie un résultat correct",
          "correct": false,
          "feedback": "Erreur : c'est la **correction**.\nUn algorithme peut être correct\n(bon résultat s'il s'arrête) sans\nterminer ; les deux propriétés se\nprouvent séparément.\n"
        },
        {
          "text": "Le nom donné à la dernière instruction",
          "correct": false,
          "feedback": "Erreur : la terminaison est une propriété\nglobale, pas une instruction particulière.\n"
        },
        {
          "text": "L'algorithme s'arrête en un nombre fini d'étapes",
          "correct": true,
          "feedback": "Bonne réponse : prouver la terminaison, c'est\nmontrer qu'aucune entrée valide ne fera\nboucler indéfiniment l'algorithme.\n"
        }
      ],
      "explanation": "Distinction essentielle : un algorithme peut être\n**correct** (donner le bon résultat **s'il termine**) sans **terminer** (boucle infinie).\nOn veut prouver les deux propriétés."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "variant"
      ],
      "title": "Variant de boucle",
      "statement": "Pour prouver la terminaison d'une boucle `while`,\non cherche un **variant**. Quelle propriété\ndoit-il avoir ?",
      "options": [
        {
          "text": "Croître à chaque itération",
          "correct": false,
          "feedback": "Erreur : un variant croissant ne prouve pas\nla terminaison (la suite peut être\nnon-bornée).\n"
        },
        {
          "text": "Un entier minoré qui décroît strictement",
          "correct": true,
          "feedback": "Bonne réponse : si une quantité entière\ndécroît strictement et est minorée\n(par exemple, par 0), elle ne peut décroître\nqu'un nombre fini de fois.\n"
        },
        {
          "text": "Rester constant",
          "correct": false,
          "feedback": "Erreur : un invariant, oui ; un variant,\nnon.\n"
        },
        {
          "text": "Être une chaîne de caractères qui change",
          "correct": false,
          "feedback": "Erreur : un variant est une **quantité entière** (ou minorée), pas une chaîne.\n"
        }
      ],
      "explanation": "Variant typique : pour la dichotomie, c'est la\n**taille de l'intervalle** `d - g` qui décroît\nstrictement. Pour `while x > 0: x -= 1`, c'est\n`x` lui-même."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "exemple-recherche"
      ],
      "title": "Exemple : recherche linéaire",
      "statement": "Dans une recherche linéaire avec `for i in\nrange(len(liste))`, quel est l'**invariant** à\nl'entrée du tour `i` ?",
      "options": [
        {
          "text": "La cible est plus grande que tous les éléments déjà testés",
          "correct": false,
          "feedback": "Erreur : aucune relation d'ordre n'intervient\ndans une recherche linéaire générale.\n"
        },
        {
          "text": "La cible n'est dans aucune position de la liste",
          "correct": false,
          "feedback": "Trop fort : on ne sait pas encore si la\ncible est plus loin.\n"
        },
        {
          "text": "La liste est triée",
          "correct": false,
          "feedback": "Erreur : la recherche linéaire n'a pas besoin\ndu tri.\n"
        },
        {
          "text": "La cible n'est pas dans `liste[:i]` (déjà parcourus)",
          "correct": true,
          "feedback": "Bonne réponse : à l'entrée de l'itération\n`i`, on a déjà testé les indices 0 à i-1 et\naucun n'a satisfait le critère. Si on en\navait trouvé un, on aurait fait un `return`.\n"
        }
      ],
      "explanation": "Combiner invariant + condition de sortie : à la\nsortie (i = n et pas de return), invariant + (i\n= n) ⇒ « cible absente ». L'invariant est l'âme\nde la preuve."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "variant-while"
      ],
      "title": "Variant for vs while",
      "statement": "Pour une boucle `for i in range(n):`, est-il\nnécessaire de prouver la terminaison ?",
      "options": [
        {
          "text": "Oui, mais seulement lorsque $n$ est un nombre pair",
          "correct": false,
          "feedback": "La parité de $n$ n'a\naucun rapport avec la\nterminaison de la\nboucle.\n"
        },
        {
          "text": "Non, mais il faut prouver l'invariant",
          "correct": false,
          "feedback": "Pertinent pour la **correction** mais hors\nsujet ici (la question portait sur la\nterminaison).\n"
        },
        {
          "text": "Non, un `for` sur un itérateur fini termine seul",
          "correct": true,
          "feedback": "Bonne réponse : Python garantit la\nterminaison d'un `for` sur un `range` fini\nou une liste finie. Pas besoin de variant.\n"
        },
        {
          "text": "Oui, comme pour toute boucle",
          "correct": false,
          "feedback": "Trop strict : la terminaison est\n**automatique** pour une boucle `for` sur un\nitérateur fini.\n"
        }
      ],
      "explanation": "Astuce : si possible, écrire `for` plutôt que\n`while` pour éviter de devoir prouver la\nterminaison. C'est aussi plus lisible."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "terminologie"
      ],
      "title": "Distinction",
      "statement": "Invariant et variant : quelle est la différence\nessentielle ?",
      "options": [
        {
          "text": "Aucune, ce sont synonymes",
          "correct": false,
          "feedback": "Erreur : ce sont deux notions distinctes.\n"
        },
        {
          "text": "Les deux servent à compter les opérations",
          "correct": false,
          "feedback": "Erreur : le compteur d'opérations est lié à la\ncomplexité, pas à ces notions.\n"
        },
        {
          "text": "L'invariant prouve la correction, le variant prouve la terminaison",
          "correct": true,
          "feedback": "Bonne réponse : invariant → résultat juste à\nla sortie ; variant → la sortie est\natteinte.\n"
        },
        {
          "text": "L'invariant est de nature mathématique, le variant est de nature physique",
          "correct": false,
          "feedback": "Cette distinction est\ninexacte : ces deux\nnotions sont, l'une et\nl'autre, des propriétés\nmathématiques de la\nboucle.\n"
        }
      ],
      "explanation": "Mnémonique : « in » comme « **in**variable » →\nreste vrai (correction) ; « variant » comme\n« ça change vers le bas » → décroît strictement\n(terminaison)."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "exemple-somme"
      ],
      "title": "Invariant : somme",
      "statement": "```python\nsomme = 0\nfor x in liste:\n    somme += x\n```\nQuel est l'invariant après k itérations ?",
      "options": [
        {
          "text": "`somme` garde la valeur 0 tant que la boucle tourne",
          "correct": false,
          "feedback": "Erreur : `somme` est modifiée à chaque tour\npar `somme += x` ; elle ne reste nulle que si\ntous les éléments parcourus sont nuls.\n"
        },
        {
          "text": "`somme` est strictement positive à chaque itération",
          "correct": false,
          "feedback": "Erreur : la liste peut contenir des nombres\nnégatifs ou nuls, et `somme` peut donc être\nnégative ou nulle à tout moment.\n"
        },
        {
          "text": "`somme` vaut le premier élément `liste[0]` de la liste",
          "correct": false,
          "feedback": "Erreur : c'est vrai seulement après la\npremière itération. Un invariant doit être\nvrai après **chaque** itération.\n"
        },
        {
          "text": "`somme` vaut la somme des k premiers éléments",
          "correct": true,
          "feedback": "Bonne réponse : à l'entrée de l'itération\nk+1, `somme` contient la somme des k\néléments déjà parcourus. À la sortie\n(k = n), c'est la somme totale.\n"
        }
      ],
      "explanation": "C'est l'invariant le plus simple à formuler :\n« la variable accumule progressivement ce\nqu'elle doit ». Ce schéma apparaît dans tous\nles calculs incrémentaux."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "variant-decroissant"
      ],
      "title": "Variant simple",
      "statement": "```python\nn = 10\nwhile n > 0:\n    n -= 1\n```\nQuel est un variant valide ?",
      "options": [
        {
          "text": "`n` lui-même",
          "correct": true,
          "feedback": "Bonne réponse : `n` est un entier ≥ 0 qui\ndécroît strictement (`n -= 1` à chaque\ntour). Quand n atteint 0, on sort.\n"
        },
        {
          "text": "La constante 10",
          "correct": false,
          "feedback": "Erreur : une constante ne décroît pas.\n"
        },
        {
          "text": "La quantité `10 - n`",
          "correct": false,
          "feedback": "Erreur : `10 - n` **croît** de 0 à 10 au\nfil des tours. Un variant doit être un\nentier positif ou nul qui décroît\nstrictement.\n"
        },
        {
          "text": "Le booléen `n > 0`",
          "correct": false,
          "feedback": "Erreur : un booléen ne décroît pas (au sens\nnumérique).\n"
        }
      ],
      "explanation": "Variant typique : la quantité contrôlée par la\ncondition d'arrêt, qui converge vers la\ncondition finale."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "boucle-infinie"
      ],
      "title": "Boucle infinie",
      "statement": "```python\ni = 0\nwhile i < 10:\n    print(i)\n```\nPourquoi cette boucle ne termine-t-elle pas ?",
      "options": [
        {
          "text": "Parce que la condition devrait être `i <= 10` et non `i < 10`",
          "correct": false,
          "feedback": "Erreur : la borne n'est pas en cause. Avec\n`i <= 10`, la boucle ne terminerait pas\ndavantage, car `i` ne change jamais.\n"
        },
        {
          "text": "Parce que `i` n'est jamais modifié : il n'y a pas de variant qui décroît",
          "correct": true,
          "feedback": "Bonne réponse : la condition `i < 10` reste\ntoujours vraie. Pas de progression vers la\nterminaison. Solution : ajouter `i += 1`\ndans la boucle.\n"
        },
        {
          "text": "Parce que `print(i)` modifie la valeur de `i` à chaque tour",
          "correct": false,
          "feedback": "Erreur : `print` se contente d'afficher son\nargument, il ne le modifie pas. C'est\njustement parce que rien ne modifie `i` que\nla condition reste vraie.\n"
        },
        {
          "text": "Parce que la condition `i < 10` n'est testée qu'une seule fois, au départ",
          "correct": false,
          "feedback": "Erreur : la condition d'un `while` est\nréévaluée avant chaque tour. Ici elle est\nbien retestée, mais reste vraie à chaque\nfois parce que `i` vaut toujours 0.\n"
        }
      ],
      "explanation": "Bug très classique chez les débutants : oublier\nde modifier la variable de boucle. Toujours\nvérifier qu'un variant décroît à chaque tour."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "importance"
      ],
      "title": "Pourquoi prouver ?",
      "statement": "Pourquoi prouver la correction d'un algorithme\nau lieu de simplement le tester ?",
      "options": [
        {
          "text": "Parce que les preuves sont plus rapides à écrire",
          "correct": false,
          "feedback": "En pratique, c'est souvent l'inverse !\n"
        },
        {
          "text": "Aucun test fini ne couvre tous les cas, une preuve oui",
          "correct": true,
          "feedback": "Bonne réponse : les tests valident sur les\ncas testés ; la preuve garantit l'absence\nde bugs sur toutes les entrées. Pour des\nsystèmes critiques (avions, médical),\nc'est essentiel.\n"
        },
        {
          "text": "Parce que c'est obligatoire en cours",
          "correct": false,
          "feedback": "Argument scolaire, pas scientifique.\n"
        },
        {
          "text": "Parce que les tests sont chers",
          "correct": false,
          "feedback": "Erreur : les tests automatisés sont peu\ncoûteux.\n"
        }
      ],
      "explanation": "Citation célèbre de Dijkstra : « Tester ne peut\njamais prouver l'absence de bugs ; au mieux,\ncela peut prouver leur présence. »"
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "trois-etapes"
      ],
      "title": "Trois étapes de preuve",
      "statement": "Une preuve d'invariant comporte combien\nd'étapes ?",
      "options": [
        {
          "text": "Aucune, la preuve d'invariant est totalement implicite",
          "correct": false,
          "feedback": "Erreur : il faut bien la justifier.\n"
        },
        {
          "text": "Trois : initialisation, hérédité, terminaison",
          "correct": true,
          "feedback": "Bonne réponse : (1) **Initialisation** :\nl'invariant est vrai avant la première\nitération ; (2) **Hérédité** : s'il est\nvrai à l'entrée d'un tour, il l'est à la\nsortie ; (3) **Terminaison** : à la sortie\nde la boucle, l'invariant + la condition\nd'arrêt donnent le résultat voulu.\n"
        },
        {
          "text": "Une seule",
          "correct": false,
          "feedback": "Une étape unique ne\npermet pas d'établir\nla correction d'une\nboucle. Il faut au\nminimum vérifier\nl'initialisation, la\nconservation et l'effet\nobtenu en sortie.\n"
        },
        {
          "text": "Cinq",
          "correct": false,
          "feedback": "Erreur : trois suffisent.\n"
        }
      ],
      "explanation": "C'est la même structure qu'une preuve par\n**récurrence** : initialisation, hérédité,\nconclusion. L'invariant de boucle est en fait\nune récurrence sur le numéro de l'itération."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "exemple-min"
      ],
      "title": "Invariant : minimum",
      "statement": "```python\nm = liste[0]\nfor x in liste[1:]:\n    if x < m:\n        m = x\n```\nQuel est l'invariant après l'itération sur les\nk premiers éléments ?",
      "options": [
        {
          "text": "`m` vaut toujours le premier élément `liste[0]`",
          "correct": false,
          "feedback": "Erreur : `m` est mis à jour dès qu'un\nélément plus petit que lui est rencontré.\n"
        },
        {
          "text": "`m` vaut le dernier élément examiné par la boucle",
          "correct": false,
          "feedback": "Erreur : `m` ne prend la valeur de l'élément\ncourant que s'il est plus petit ; sinon il\ngarde sa valeur.\n"
        },
        {
          "text": "`m` est le minimum des k premiers éléments parcourus",
          "correct": true,
          "feedback": "Bonne réponse : à chaque tour, on met à\njour `m` si l'élément courant est plus\npetit. À la fin, c'est le min de toute la\nliste.\n"
        },
        {
          "text": "`m` vaut le minimum de la liste entière dès le premier tour",
          "correct": false,
          "feedback": "Erreur : confusion entre invariant et\n**postcondition**. Le minimum global n'est\ngaranti qu'à la sortie de la boucle, pas\npendant son exécution.\n"
        }
      ],
      "explanation": "Schéma « accumulateur » : la variable `m`\nagrège progressivement la propriété (ici, le\nmin) sur les éléments parcourus."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "variant-dichotomie"
      ],
      "title": "Variant dichotomie",
      "statement": "Pour la **recherche dichotomique** itérative,\nquel est le variant le plus naturel ?",
      "options": [
        {
          "text": "Le compteur d'itérations courant",
          "correct": false,
          "feedback": "Erreur : un compteur **croît** à chaque tour,\nalors qu'un variant doit décroître strictement\nvers 0. On exprime le variant avec les\nvariables de l'algorithme.\n"
        },
        {
          "text": "La longueur totale de la liste parcourue",
          "correct": false,
          "feedback": "Erreur : la longueur de la liste ne change pas\nd'une itération à l'autre ; une quantité\nconstante ne peut pas servir de variant.\n"
        },
        {
          "text": "La taille `d - g + 1` de l'intervalle",
          "correct": true,
          "feedback": "Bonne réponse : à chaque itération,\nl'intervalle est divisé par 2 (donc\ndécroît strictement). Quand il atteint 0\n(ou devient négatif), on sort.\n"
        },
        {
          "text": "La valeur de la cible recherchée",
          "correct": false,
          "feedback": "Erreur : la cible ne change pas pendant la\nrecherche, elle ne peut donc pas décroître.\n"
        }
      ],
      "explanation": "Le variant est ce qui « pousse » l'algorithme\nvers sa fin. Pour la dichotomie, c'est\nl'**intervalle de recherche** qui rétrécit."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "bug-variant"
      ],
      "title": "Bug par variant",
      "statement": "```python\ndef f(n):\n    while n != 1:\n        if n % 2 == 0:\n            n = n // 2\n        else:\n            n = 3 * n + 1\n```\nCette boucle termine-t-elle pour tout n entier ≥ 1 ?",
      "options": [
        {
          "text": "Non : pour n = 5, la suite boucle sans atteindre 1",
          "correct": false,
          "feedback": "Erreur : pour n = 5, la suite atteint 1\nrapidement (5, 16, 8, 4, 2, 1). Aucun\ncontre-exemple n'est connu.\n"
        },
        {
          "text": "Oui : n décroît à chaque tour jusqu'à atteindre 1",
          "correct": false,
          "feedback": "Erreur : `n = 3n + 1` **augmente** n. Il n'y\na pas de variant évident qui décroît à\nchaque tour.\n"
        },
        {
          "text": "Oui : Python détecte et interrompt les boucles infinies",
          "correct": false,
          "feedback": "Erreur : Python n'a aucun mécanisme de\ndétection de boucle infinie (un tel\ndétecteur général est d'ailleurs impossible).\n"
        },
        {
          "text": "On ne sait pas : la terminaison pour tout n est un problème ouvert",
          "correct": true,
          "feedback": "Bonne réponse : c'est la fameuse conjecture\nde Syracuse (ou de Collatz), l'un des plus\ncélèbres problèmes ouverts en mathématiques.\nOn a vérifié pour n très grand, mais aucune\npreuve générale n'existe.\n"
        }
      ],
      "explanation": "Cas instructif : trouver un variant peut être\n**très difficile**. Pour la suite de Syracuse,\npersonne n'a encore réussi."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "pre-postcondition"
      ],
      "title": "Pré et post-conditions",
      "statement": "Que sont les **préconditions** et\n**postconditions** d'une fonction ?",
      "options": [
        {
          "text": "Hypothèse sur les arguments, garantie sur le résultat",
          "correct": true,
          "feedback": "Bonne réponse : « si X (précondition) est\nvraie en entrée, alors Y (postcondition)\nest vraie en sortie ». C'est un **contrat**.\n"
        },
        {
          "text": "Des décorateurs Python",
          "correct": false,
          "feedback": "Possible avec des bibliothèques (par\nexemple `icontract`), mais ce n'est pas la\ndéfinition.\n"
        },
        {
          "text": "Le code situé juste avant et juste après l'appel à la fonction",
          "correct": false,
          "feedback": "Cette interprétation\nlittérale du nom est\ninexacte. Les pré- et\npostconditions sont des\npropriétés logiques sur\nles arguments et le\nrésultat, et non des\nfragments de code.\n"
        },
        {
          "text": "Des commentaires inutiles",
          "correct": false,
          "feedback": "Erreur : ce sont des éléments de\nspécification rigoureuse.\n"
        }
      ],
      "explanation": "L'invariant relie pré et postcondition à\nl'intérieur d'une boucle. Ces notions\nstructurent la programmation **par contrat**."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "decroissance-stricte"
      ],
      "title": "Décroissance stricte",
      "statement": "Pourquoi le variant doit-il décroître\n**strictement** (et pas seulement « ne pas\naugmenter ») ?",
      "options": [
        {
          "text": "Parce que la preuve serait plus longue à rédiger sinon",
          "correct": false,
          "feedback": "La longueur de la preuve\nn'est pas le motif. La\ndécroissance stricte est\nrequise pour des raisons\nmathématiques de fond,\ncomme expliqué dans la\nbonne réponse.\n"
        },
        {
          "text": "Une suite minorée non strictement décroissante peut stagner",
          "correct": true,
          "feedback": "Bonne réponse : la stagnation = boucle\ninfinie. La décroissance stricte garantit\nque l'on s'approche du seuil de sortie à\nchaque tour.\n"
        },
        {
          "text": "Pour rendre la démonstration plus élégante visuellement",
          "correct": false,
          "feedback": "La décroissance stricte\nrépond à un besoin\nmathématique précis :\nassurer que la boucle\nne stagne pas et finit\npar s'arrêter.\n"
        },
        {
          "text": "Parce que Python l'exige",
          "correct": false,
          "feedback": "Erreur : Python n'a pas cette contrainte.\n"
        }
      ],
      "explanation": "Mathématiquement, une suite d'entiers\nstrictement décroissante et minorée par 0 est\nfinie (par bonne fondation de ℕ). Cette\npropriété fonde la preuve de terminaison."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "exemple-compteur"
      ],
      "title": "Invariant : compteur d'occurrences",
      "statement": "```python\nc = 0\nfor i in range(len(liste)):\n    if liste[i] == cible:\n        c = c + 1\n```\nQuel invariant est vérifié à l'entrée du tour\n`i`, avant le test `liste[i] == cible` ?",
      "options": [
        {
          "text": "`c` est le nombre d'occurrences de `cible` dans `liste[:i]`",
          "correct": true,
          "feedback": "Bonne réponse : avant le tour `i`, seuls les\n`i` premiers éléments ont été examinés, et\n`c` a été incrémenté une fois par occurrence\nrencontrée. Sur `liste = [3, 7, 3, 3]` et\n`cible = 3`, `c` vaut 0, 1, 1, 2 à l'entrée\ndes tours 0, 1, 2, 3, puis 3 à la sortie.\n"
        },
        {
          "text": "`c` est le nombre d'occurrences de `cible` dans toute la liste",
          "correct": false,
          "feedback": "Erreur : c'est la postcondition, vraie\nseulement à la sortie de la boucle. À\nl'entrée du tour 0, `c` vaut 0 alors que\n`[3, 7, 3, 3]` contient trois fois la\ncible 3.\n"
        },
        {
          "text": "`c` vaut `i`, le nombre de tours déjà effectués",
          "correct": false,
          "feedback": "Erreur : `c` ne compte que les tours où le\ntest a réussi. Sur `[3, 7, 3, 3]` avec\n`cible = 3`, à l'entrée du tour 2 on a\n`i = 2` mais `c = 1`.\n"
        },
        {
          "text": "`c` est le nombre d'éléments de `liste[:i]` différents de `cible`",
          "correct": false,
          "feedback": "Erreur : c'est le complémentaire de ce que\ncompte la boucle. À l'entrée du tour 1 sur\n`[3, 7, 3, 3]` avec `cible = 3`, `c` vaut 1\nalors qu'aucun élément déjà vu n'est\ndifférent de 3.\n"
        }
      ],
      "explanation": "Un compteur suit le même schéma qu'une somme :\nl'invariant décrit ce que `c` représente pour\nla partie déjà parcourue, `liste[:i]`. À la\nsortie (`i = len(liste)`), il donne la\npostcondition : `c` est le nombre d'occurrences\ndans toute la liste."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "variant-multivarie"
      ],
      "title": "Variant multivarié",
      "statement": "Pour la dichotomie itérative, peut-on choisir\n`g` (l'indice de gauche) seul comme variant ?",
      "options": [
        {
          "text": "Oui, parfait",
          "correct": false,
          "feedback": "Erreur : `g` peut rester constant si c'est\n`d` qui change.\n"
        },
        {
          "text": "Non, parce que `g` n'est pas vraiment un entier",
          "correct": false,
          "feedback": "Erreur : `g` est bien un entier.\n"
        },
        {
          "text": "Non, `g` ne décroît pas (il augmente)",
          "correct": true,
          "feedback": "Bonne réponse : on cherche une quantité\nqui décroît. Avec `g` qui augmente, il\nfaut prendre par exemple `n - g` ou bien\nla taille de l'intervalle `d - g`.\n"
        },
        {
          "text": "Oui, mais seulement si la liste est triée",
          "correct": false,
          "feedback": "Erreur : la précondition de tri n'a aucun\nrôle dans la **terminaison**.\n"
        }
      ],
      "explanation": "Astuce : si une variable augmente, prendre son\ncomplément (par exemple `n - i`) comme variant.\nLe choix du variant est souvent affaire de\nreformulation."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "bonne-formulation"
      ],
      "title": "Bonne formulation",
      "statement": "Pour qu'un invariant soit utile à la preuve,\nil faut qu'il soit :",
      "options": [
        {
          "text": "Long, lourd et particulièrement compliqué",
          "correct": false,
          "feedback": "Au contraire, on cherche le plus simple\npossible.\n"
        },
        {
          "text": "Toujours faux",
          "correct": false,
          "feedback": "Absurde : un invariant doit être **vrai**.\n"
        },
        {
          "text": "Codé dans Python",
          "correct": false,
          "feedback": "Sans rapport : un invariant est une\npropriété mathématique.\n"
        },
        {
          "text": "Vrai au départ et assez fort en sortie",
          "correct": true,
          "feedback": "Bonne réponse : un invariant trop faible\nne donne rien à la sortie ; un invariant\ntrop fort peut être faux. Trouver le bon\nniveau est tout l'art.\n"
        }
      ],
      "explanation": "Trop faible : ne dit rien d'utile. Trop fort :\nfaux. Le bon niveau est celui qui, combiné à la\ncondition d'arrêt, donne exactement la\npostcondition."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "outils-aides"
      ],
      "title": "Outils",
      "statement": "Quel outil aide à **détecter** un bug dans un\ninvariant ?",
      "options": [
        {
          "text": "Un compteur du nombre de tours de la boucle",
          "correct": false,
          "feedback": "Erreur : compter les tours renseigne sur la\nterminaison (la boucle s'arrête-t-elle ?),\npas sur la correction. Une boucle peut faire\nexactement le bon nombre de tours en\ncalculant un résultat faux.\n"
        },
        {
          "text": "Un commentaire `# TODO` placé dans la boucle",
          "correct": false,
          "feedback": "Erreur : un commentaire n'est pas exécuté et\nne vérifie rien.\n"
        },
        {
          "text": "Un `assert` de l'invariant dans la boucle",
          "correct": true,
          "feedback": "Bonne réponse : par exemple\n`assert m == min(liste[:i+1])`. Si\nl'invariant échoue à un tour, Python lève\n`AssertionError` et indique le bug.\n"
        },
        {
          "text": "Un `print()` qui signale automatiquement l'invariant faux",
          "correct": false,
          "feedback": "Erreur : `print` affiche une valeur sans la\ncomparer à rien. C'est à l'humain de repérer\nl'anomalie dans le flot d'affichages, et rien\nn'arrête le programme.\n"
        }
      ],
      "explanation": "Les `assert` sont une excellente pratique :\nils transforment des invariants implicites en\nvérifications explicites, attrapant les bugs\nau plus tôt."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "mauvais-invariant"
      ],
      "title": "Invariant insuffisant",
      "statement": "Pour le tri par sélection, l'invariant\n« les k premiers éléments sont triés » est-il\nsuffisant ?",
      "options": [
        {
          "text": "Oui, à condition d'utiliser un tri stable",
          "correct": false,
          "feedback": "Erreur : la stabilité n'a aucun rôle ici.\n"
        },
        {
          "text": "Oui, à condition que la liste soit triée à l'avance",
          "correct": false,
          "feedback": "Erreur : on n'a pas cette précondition.\n"
        },
        {
          "text": "Oui, c'est suffisant",
          "correct": false,
          "feedback": "Insuffisant : « les k premiers triés » ne\ndit pas qu'ils sont les plus petits. Si le\nmaximum se retrouvait en `liste[0]` et le\nminimum en `liste[1]`, ce serait « trié »\n(ordre 1 puis 2 éléments), mais incorrect.\n"
        },
        {
          "text": "Non, il manque qu'ils soient les plus petits",
          "correct": true,
          "feedback": "Bonne réponse : sans cette condition\nsupplémentaire, l'algorithme pourrait\nterminer avec `liste[0..k-1]` (en Python :\n`liste[:k]`) triés mais contenant des\n« gros » éléments, et le résultat final\nserait faux.\n"
        }
      ],
      "explanation": "Bon invariant pour la sélection :\n`liste[0..k-1]` (en Python : `liste[:k]`) est\ntrié **et** ne contient que des éléments\nplus petits que tout ceux de `liste[k..n-1]`\n(en Python : `liste[k:]`). Les deux\nconditions sont nécessaires."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "trouver-variant"
      ],
      "title": "Trouver le variant",
      "statement": "```python\nwhile a > b:\n    a = a - b\n```\n(avec `a, b` entiers, `b > 0`). Quel est un\nvariant valide ?",
      "options": [
        {
          "text": "La variable `a`",
          "correct": true,
          "feedback": "Bonne réponse : à chaque tour `a -= b`\navec `b > 0`, donc `a` décroît strictement,\ntout en restant strictement positif tant\nque la boucle tourne (`a > b > 0`). La\nboucle se termine quand `a <= b`.\n"
        },
        {
          "text": "La constante 1",
          "correct": false,
          "feedback": "Erreur : une constante ne décroît pas.\n"
        },
        {
          "text": "La variable `b`",
          "correct": false,
          "feedback": "Erreur : `b` ne change pas dans la boucle,\nce n'est donc pas une quantité décroissante.\n"
        },
        {
          "text": "La quantité `b - a`",
          "correct": false,
          "feedback": "Erreur : tant que la boucle tourne, `a > b`\ndonc `b - a` est strictement **négatif**, et\nil **croît** à chaque tour. Un variant doit\nêtre positif ou nul et décroître strictement.\n"
        }
      ],
      "explanation": "Cette boucle calcule `a mod b` (reste de la\ndivision euclidienne). C'est un cas classique\nillustrant variant et terminaison."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "preuve-formelle"
      ],
      "title": "Preuve par récurrence",
      "statement": "Pourquoi la preuve d'un invariant fait-elle\npenser à une **preuve par récurrence** ?",
      "options": [
        {
          "text": "Parce que les deux utilisent simplement des indices",
          "correct": false,
          "feedback": "Vague et superficiel.\n"
        },
        {
          "text": "Aucun rapport entre les deux",
          "correct": false,
          "feedback": "Erreur : la similarité est profonde.\n"
        },
        {
          "text": "On prouve l'initialisation puis l'hérédité, comme une récurrence",
          "correct": true,
          "feedback": "Bonne réponse : invariant de boucle =\nrécurrence sur le compteur d'itérations.\nC'est la même structure logique.\n"
        },
        {
          "text": "Parce que c'est imposé par les règles du langage",
          "correct": false,
          "feedback": "Aucun langage de\nprogrammation n'impose\nde prouver les\ninvariants par\nrécurrence. C'est une\ndémarche\nmathématique, qui\nrelève du raisonnement,\npas du langage.\n"
        }
      ],
      "explanation": "C'est le pont entre **mathématiques** et\n**informatique** : la preuve algorithmique\nutilise les outils du raisonnement\nmathématique. L'invariant est une propriété\n`P(k)` à prouver par récurrence sur k."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "trouver-invariant"
      ],
      "title": "Inventer l'invariant",
      "statement": "```python\nr = 0\nn = N\nwhile n > 0:\n    n = n // 2\n    r = r + 1\n```\nQue calcule cette boucle, et quel invariant\nrend ce calcul évident ?",
      "options": [
        {
          "text": "La boucle compte simplement de 1 jusqu'à N",
          "correct": false,
          "feedback": "Erreur : `n` est divisé par 2, pas\ndécrémenté.\n"
        },
        {
          "text": "`r` reste égal à 0 pendant toute la boucle",
          "correct": false,
          "feedback": "Erreur : `r = r + 1` l'incrémente à chaque\ntour.\n"
        },
        {
          "text": "Invariant `n == N // 2^r` ; `r` compte les tours",
          "correct": true,
          "feedback": "Bonne réponse : on divise par 2 jusqu'à\natteindre 0, en comptant. C'est le calcul\ndu logarithme entier en base 2. Le\nvariant : `n` (qui décroît\nstrictement par division entière).\n"
        },
        {
          "text": "La boucle ne termine jamais, `n` ne devenant jamais nul",
          "correct": false,
          "feedback": "Erreur : `n // 2` ramène n à 0 en log₂(N)\nétapes (par exemple 8, 4, 2, 1, 0).\n"
        }
      ],
      "explanation": "Cet algorithme calcule le **logarithme entier**.\nC'est aussi la base de la complexité O(log n)\nde la dichotomie : on divise par 2 jusqu'à\natteindre 0."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes sur les\ninvariants et la terminaison, laquelle est\n**fausse** ?",
      "options": [
        {
          "text": "Un programme correct est nécessairement un programme qui termine",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : la correction\nest une propriété **conditionnelle** :\n« si le programme termine, le résultat\nest juste ». La terminaison est une\npropriété **distincte**. Un programme\npeut être correct mais ne pas terminer\n(par exemple s'il calcule une fonction\npartielle).\n"
        },
        {
          "text": "Un variant doit être un entier (ou une quantité minorée) qui décroît strictement",
          "correct": false,
          "feedback": "Vrai : sinon la décroissance peut être\ninfinie.\n"
        },
        {
          "text": "Un invariant doit être vrai à chaque entrée d'itération",
          "correct": false,
          "feedback": "Vrai : c'est sa définition.\n"
        },
        {
          "text": "Toute boucle `for i in range(n)` se termine, sans qu'on ait besoin de prouver de variant",
          "correct": false,
          "feedback": "Vrai : la boucle `for` sur un itérateur\nfini termine automatiquement.\n"
        }
      ],
      "explanation": "Distinction terminologique : **correction partielle** = « si termine, alors juste » ;\n**correction totale** = correction partielle +\nterminaison. C'est la correction totale qu'on\nveut habituellement."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "exemple-insertion"
      ],
      "title": "Invariant : tri par insertion",
      "statement": "Dans le tri par insertion, après l'itération\n`i` de la boucle externe, quel invariant\ncaractérise correctement l'état de la liste ?",
      "options": [
        {
          "text": "`liste[:i+1]` contient les mêmes éléments, mais triés",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'invariant clé du\ntri par insertion. Les `i + 1` premiers\néléments restent les mêmes (à l'ensemble\nprès) qu'avant la boucle, mais maintenant\nordonnés. À la dernière itération\n`i = n - 1`, toute la liste est triée.\n"
        },
        {
          "text": "La liste est entièrement triée",
          "correct": false,
          "feedback": "Erreur : trop fort. À l'itération `i`,\nseuls les `i + 1` premiers éléments sont\ntriés ; les éléments suivants n'ont pas\nencore été traités.\n"
        },
        {
          "text": "Les `i` premiers éléments sont les plus grands",
          "correct": false,
          "feedback": "Erreur : ce serait un tri décroissant ou\nun tri par sélection inversé. Le tri par\ninsertion classique est croissant et ne\nplace pas les plus grands en tête.\n"
        },
        {
          "text": "Les `i` premiers éléments sont triés et plus petits que tous les autres",
          "correct": false,
          "feedback": "Erreur : c'est l'invariant du tri par\n**sélection**. Le tri par insertion ne\nplace pas les plus petits en tête à chaque\nétape ; il insère le `i`-ème élément à sa\nplace dans la partie déjà traitée.\n"
        }
      ],
      "explanation": "Différence clé avec le tri par sélection :\nl'invariant de l'insertion porte sur le tri\nd'une partie initiale (sans hypothèse sur les\nvaleurs), alors que celui de la sélection\najoute la condition « plus petits que les\nautres »."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "euclide"
      ],
      "title": "Terminaison de l'algorithme d'Euclide",
      "statement": "L'algorithme d'Euclide pour le PGCD s'écrit :\n```python\ndef pgcd(a, b):\n    while b != 0:\n        a, b = b, a % b\n    return a\n```\n(avec `a, b` entiers naturels, `b > 0` à\nl'entrée). Quel est un variant qui prouve la\nterminaison ?",
      "options": [
        {
          "text": "La valeur de `b`",
          "correct": true,
          "feedback": "Bonne réponse : à chaque itération, `b`\ndevient `a % b`, qui est strictement\ninférieur à l'ancien `b`. Comme `b` est un\nentier naturel minoré par `0`, la suite des\nvaleurs de `b` est strictement décroissante\net finie. La boucle se termine donc.\n"
        },
        {
          "text": "Le PGCD lui-même",
          "correct": false,
          "feedback": "Erreur : l'invariant de l'algorithme est\nprécisément que `pgcd(a, b)` reste\nconstant à chaque itération. Une quantité\nconstante ne peut servir de variant (qui\ndoit décroître strictement).\n"
        },
        {
          "text": "La somme `a + b`",
          "correct": false,
          "feedback": "Erreur : la somme peut augmenter ou\nstagner selon les cas. Par exemple\n`pgcd(5, 12)` : avant, `a + b = 17` ;\naprès, `a + b = 12 + 5 = 17`. Donc pas de\ndécroissance stricte.\n"
        },
        {
          "text": "La valeur de `a`",
          "correct": false,
          "feedback": "Erreur : `a` ne décroît pas forcément à\nchaque tour. Par exemple avec `a = 5`,\n`b = 12`, après une itération on a\n`a = 12, b = 5`, donc `a` a augmenté.\n"
        }
      ],
      "explanation": "Une remarque profonde : ici, l'**invariant**\nest `pgcd(a, b) = pgcd(a₀, b₀)` (la valeur\ninitiale du PGCD est conservée), tandis que le\n**variant** est `b`. À la sortie, l'invariant\ndonne `pgcd(a, 0) = a`, ce qui est bien le\nPGCD recherché."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "debogage-assertion"
      ],
      "title": "Assertion d'invariant en pratique",
      "statement": "On suspecte qu'une boucle calcule mal une\nsomme. Quel ajout permet de **détecter automatiquement** une violation d'invariant\npendant l'exécution ?",
      "options": [
        {
          "text": "Un commentaire `# vérification de l'invariant`",
          "correct": false,
          "feedback": "Erreur : un commentaire est inerte. Il\nn'est lu que par le programmeur, pas par\nl'interpréteur Python. Aucun bug ne peut\nêtre détecté ainsi.\n"
        },
        {
          "text": "Une instruction `assert` exprimant l'invariant attendu",
          "correct": true,
          "feedback": "Bonne réponse : `assert` lève\nimmédiatement une `AssertionError` si la\ncondition est fausse, ce qui localise\nprécisément l'itération où l'invariant a\nété rompu. C'est un outil de débogage très\nefficace, à retirer ou à désactiver\n(`python -O`) en production.\n"
        },
        {
          "text": "Un `assert` vérifiant que le variant décroît à chaque tour",
          "correct": false,
          "feedback": "Erreur : ce contrôle porte sur la\nterminaison, pas sur la correction. Le\nvariant peut décroître parfaitement pendant\nque la somme se calcule mal : une assertion\nsur le variant ne détecte aucune violation\nd'invariant.\n"
        },
        {
          "text": "Un appel à `print` affichant la valeur courante de la variable",
          "correct": false,
          "feedback": "Possible mais limité : il faut alors\ninspecter la sortie à l'œil nu et\nidentifier visuellement une anomalie. Pas\nde détection automatique.\n"
        }
      ],
      "explanation": "Bonne pratique : exprimer les invariants\ncritiques sous forme d'assertions pendant la\nphase de développement. Cela transforme les\nbugs silencieux en erreurs immédiates et\nréduit considérablement le temps de\ndébogage."
    }
  ]
}