{
  "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é vraie au début et conservée à chaque itération de la boucle",
          "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": "Une variable globale nommée `fin`",
          "correct": false,
          "feedback": "La terminaison est une\npropriété mathématique\nde l'algorithme, et non\nle nom d'une variable\ndans le code.\n"
        },
        {
          "text": "Le moment où l'utilisateur appuie sur Ctrl+C",
          "correct": false,
          "feedback": "Plaisanterie : c'est une interruption\nextérieure, pas la terminaison de\nl'algorithme.\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": "La garantie que l'algorithme s'arrête après un nombre fini d'étapes, quelle que soit l'entrée",
          "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\ntermine**) 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": "Être un entier (ou une quantité minorée) qui décroît strictement à chaque itération",
          "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é\nentiè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 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'a pas été trouvée dans `liste[0..i-1]` (notation algorithmique pour les éléments déjà parcourus ; en Python : `liste[:i]`)",
          "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 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, une boucle `for` sur un itérateur fini termine automatiquement",
          "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 = 0`",
          "correct": false,
          "feedback": "Erreur : la somme augmente.\n"
        },
        {
          "text": "`somme > 0`",
          "correct": false,
          "feedback": "Erreur : la liste peut contenir des nombres\nnégatifs.\n"
        },
        {
          "text": "`somme = liste[0]`",
          "correct": false,
          "feedback": "Erreur : seulement après la première\nitération, et pas un invariant général.\n"
        },
        {
          "text": "`somme = somme des k premiers éléments de liste`",
          "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 chaîne `\"hello\"`",
          "correct": false,
          "feedback": "Erreur : un variant doit être une quantité\nentière (ou minorée) qui décroît.\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 `print` est interdit",
          "correct": false,
          "feedback": "Erreur : `print` est valide.\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 10 n'est pas un entier",
          "correct": false,
          "feedback": "Erreur : 10 est bien un entier.\n"
        },
        {
          "text": "Parce que Python est lent",
          "correct": false,
          "feedback": "Erreur : aucune relation avec la performance.\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": "Parce qu'aucun nombre fini de tests ne couvre tous les cas possibles ; une preuve garantit la correction sur toutes les entrées valides",
          "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, une preuve d'invariant est implicite",
          "correct": false,
          "feedback": "Erreur : il faut bien la justifier.\n"
        },
        {
          "text": "Trois : initialisation, hérédité (préservation), 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 == liste[0]`",
          "correct": false,
          "feedback": "Erreur : `m` peut être mis à jour.\n"
        },
        {
          "text": "`m == len(liste)`",
          "correct": false,
          "feedback": "Erreur : pas de relation avec la taille.\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 == 0`",
          "correct": false,
          "feedback": "Erreur : pas de raison d'être nul.\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",
          "correct": false,
          "feedback": "Possible, mais on préfère exprimer le\nvariant par les variables de l'algorithme.\n"
        },
        {
          "text": "La longueur de la liste",
          "correct": false,
          "feedback": "Erreur : la longueur de la liste est\nconstante.\n"
        },
        {
          "text": "La taille de l'intervalle de recherche : `d - g + 1`",
          "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 cible",
          "correct": false,
          "feedback": "Erreur : la cible est constante.\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, car n = 5 fait boucler",
          "correct": false,
          "feedback": "Erreur : pour n = 5, la suite atteint 1\nrapidement. Aucun contre-exemple connu.\n"
        },
        {
          "text": "Oui, car n décroît à chaque tour",
          "correct": false,
          "feedback": "Erreur : `n = 3n + 1` **augmente** n. Pas de\nvariant évident qui décroît à chaque tour.\n"
        },
        {
          "text": "Oui, parce que Python le détecterait",
          "correct": false,
          "feedback": "Erreur : Python n'a aucun mécanisme de\ndétection de boucle infinie.\n"
        },
        {
          "text": "La terminaison n'est pas connue : c'est la fameuse conjecture de Syracuse (Collatz), non démontrée",
          "correct": true,
          "feedback": "Bonne réponse : c'est 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"
        }
      ],
      "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": "La précondition est ce que la fonction suppose sur ses arguments ; la postcondition est ce qu'elle garantit 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é avant et 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": "Parce qu'une suite d'entiers minorée et qui ne décroît pas strictement peut stagner indéfiniment",
          "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-tri"
      ],
      "title": "Invariant : tri par sélection",
      "statement": "Dans le tri par sélection, après la k-ème\nitération de la boucle externe, quel est\nl'invariant ?",
      "options": [
        {
          "text": "Les k premiers éléments sont triés et plus petits que tous les autres",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'invariant clé du\ntri par sélection. À l'itération k, on a\nfixé les positions 0..k-1 avec les k plus\npetits éléments dans l'ordre.\n"
        },
        {
          "text": "La liste est entièrement triée",
          "correct": false,
          "feedback": "Trop fort : seules les k premières\npositions sont triées.\n"
        },
        {
          "text": "Les k premiers éléments sont les plus grands",
          "correct": false,
          "feedback": "Erreur : le tri par sélection met les plus\npetits en début (tri croissant).\n"
        },
        {
          "text": "La liste est inchangée",
          "correct": false,
          "feedback": "Erreur : on a fait des échanges.\n"
        }
      ],
      "explanation": "Cet invariant rend la correction du tri par\nsélection évidente : à la fin (k = n), les n\npremiers (= toute la liste) sont triés."
    },
    {
      "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 un entier",
          "correct": false,
          "feedback": "Erreur : `g` est bien un entier.\n"
        },
        {
          "text": "Non, parce que `g` ne décroît pas (il augmente ou reste constant)",
          "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 et 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 à l'initialisation et suffisamment fort pour impliquer la postcondition au moment de la 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 débogueur visuel",
          "correct": false,
          "feedback": "Possible mais vague. Quelque chose de plus\nprécis est demandé.\n"
        },
        {
          "text": "Un commentaire `# TODO`",
          "correct": false,
          "feedback": "Erreur : un commentaire ne vérifie rien.\n"
        },
        {
          "text": "Un `assert` placé dans la boucle pour vérifier l'invariant à l'exécution",
          "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": "La fonction `print()`",
          "correct": false,
          "feedback": "Possible mais moins systématique qu'un\n`assert`.\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 la condition que ces éléments soient plus petits que ceux de `liste[k..n-1]` (en Python : `liste[k:]`)",
          "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": "`a` (qui décroît strictement à chaque tour, et est minoré par `b` à la sortie)",
          "correct": true,
          "feedback": "Bonne réponse : à chaque tour `a -= b`\navec `b > 0` donc `a` décroît\nstrictement. La boucle se termine quand\n`a <= b`.\n"
        },
        {
          "text": "La constante 1",
          "correct": false,
          "feedback": "Erreur : une constante ne décroît pas.\n"
        },
        {
          "text": "`b`",
          "correct": false,
          "feedback": "Erreur : `b` ne change pas dans la boucle.\n"
        },
        {
          "text": "`a - b`",
          "correct": false,
          "feedback": "Possible, mais à chaque tour `a - b`\nchange : à voir si décroissance stricte.\nOn peut faire mieux et plus simple.\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 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": "Parce qu'on prouve l'invariant à la 1ʳᵉ itération (initialisation), puis qu'on prouve qu'il est conservé d'une itération à la suivante (hérédité), exactement comme une récurrence sur le numéro d'itération",
          "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 de 1 à N",
          "correct": false,
          "feedback": "Erreur : `n` est divisé par 2, pas\ndécrémenté.\n"
        },
        {
          "text": "`r` est toujours 0",
          "correct": false,
          "feedback": "Erreur : `r` est incrémenté.\n"
        },
        {
          "text": "`r` compte le nombre d'itérations ; à la fin, `r` ≈ ⌊log₂(N)⌋ + 1. Invariant : `2^r * n ≥ N` (avant l'itération courante).",
          "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 pas",
          "correct": false,
          "feedback": "Erreur : `n // 2` ramène n à 0 en log₂(N)\nétapes.\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\npartielle** = « 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": "La sous-liste `liste[0..i]` (en Python : `liste[:i+1]`) contient les mêmes éléments qu'au début, mais elle est triée",
          "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\nautomatiquement** 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, par exemple `assert somme == sum(liste[:i+1])`",
          "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": "Une exception `try/except` autour de la boucle",
          "correct": false,
          "feedback": "Erreur : un `try/except` capture les\nerreurs **levées par Python**, pas les\nviolations d'invariant logique. Si la\nboucle calcule une mauvaise somme sans\nerreur d'exécution, le `try/except` ne\ndétectera rien.\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."
    }
  ]
}