{
  "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).",
    "parties": [
      {
        "id": "invariants",
        "title": "Invariants de boucle"
      },
      {
        "id": "terminaison",
        "title": "Terminaison et variants"
      },
      {
        "id": "assertions",
        "title": "Assertions et postconditions"
      }
    ],
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition-invariant"
      ],
      "title": "Invariant de boucle",
      "partie": "invariants",
      "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",
      "partie": "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",
      "partie": "terminaison",
      "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",
      "partie": "invariants",
      "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 ne se trouve à aucun indice situé avant `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",
      "partie": "terminaison",
      "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 `range` 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\n`range` ou une liste.\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",
      "partie": "invariants",
      "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",
      "partie": "invariants",
      "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",
      "partie": "terminaison",
      "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",
      "partie": "terminaison",
      "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 ?",
      "partie": "invariants",
      "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",
      "partie": "invariants",
      "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": "Trois temps à retenir : l'invariant est vrai\nau départ, chaque tour le conserve, et à la\nsortie il donne, avec la condition d'arrêt,\nle résultat voulu."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "exemple-min"
      ],
      "title": "Invariant : minimum",
      "partie": "invariants",
      "statement": "```python\nm = liste[0]\nfor i in range(1, len(liste)):\n    if liste[i] < m:\n        m = liste[i]\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",
      "partie": "terminaison",
      "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": [
        "variant",
        "terminaison"
      ],
      "title": "La boucle de Syracuse",
      "partie": "terminaison",
      "statement": "La suite de Syracuse part d'un entier $n \\geq 1$ : si $n$ est pair, on le\ndivise par $2$, sinon on le remplace par $3n + 1$. La **conjecture de\nSyracuse** affirme que cette suite finit toujours par atteindre $1$,\nquel que soit l'entier de départ. Elle a été vérifiée par ordinateur\npour d'immenses valeurs de $n$, mais personne ne l'a démontrée.\n\n```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```\n\nQue peut-on affirmer sur la terminaison de cette boucle pour tout\nentier $n \\geq 1$ ?",
      "options": [
        {
          "text": "On ne sait pas : il faudrait pour cela démontrer la conjecture",
          "correct": true,
          "feedback": "Bonne réponse : la boucle s'arrête exactement quand la suite atteint $1$.\nProuver qu'elle termine pour tout $n$ revient donc à démontrer la\nconjecture, ce que personne n'a encore réussi.\n"
        },
        {
          "text": "Elle termine pour tout $n$, puisque la conjecture a été vérifiée par ordinateur",
          "correct": false,
          "feedback": "Erreur : une vérification, même sur d'immenses valeurs, ne couvre pas\ntous les entiers. Seule une démonstration permettrait d'affirmer que la\nboucle termine pour tout $n$.\n"
        },
        {
          "text": "Elle ne termine pas pour tout $n$, puisque `3 * n + 1` fait augmenter $n$",
          "correct": false,
          "feedback": "Erreur : $n$ augmente parfois, mais cela ne prouve pas que la boucle\ntourne sans fin. Pour $n = 3$, la suite vaut $10$, $5$, $16$, $8$, $4$,\n$2$, $1$ : elle monte puis redescend jusqu'à $1$.\n"
        },
        {
          "text": "Elle termine pour tout $n$, car un tour sur deux au moins divise $n$ par $2$",
          "correct": false,
          "feedback": "Erreur : après `3 * n + 1`, le nombre obtenu est pair et le tour suivant\nle divise par $2$, mais le résultat $(3n + 1)/2$ reste plus grand que\n$n$. Cette remarque ne fournit donc aucun variant.\n"
        }
      ],
      "explanation": "Pour prouver qu'une boucle termine, on cherche un variant : une quantité\nentière positive qui décroît strictement à chaque tour. Ici, $n$ monte\net descend, et personne n'a trouvé de variant : la terminaison de cette\nboucle très simple reste une question ouverte."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "pre-postcondition"
      ],
      "title": "Pré et post-conditions",
      "partie": "assertions",
      "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 tests écrits après coup pour chercher les bugs",
          "correct": false,
          "feedback": "Erreur : tu confonds spécification et jeu de\ntests. Les tests vérifient le programme sur\nquelques exemples ; les préconditions et\npostconditions énoncent ce qui doit être vrai\npour tous les appels.\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. En Python, des\nassertions (`assert`) peuvent vérifier ces\nconditions pendant l'exécution."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "decroissance-stricte"
      ],
      "title": "Décroissance stricte",
      "partie": "terminaison",
      "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 n'est pas le motif :\nsans décroissance stricte, le variant peut\nstagner et la boucle ne jamais s'arrêter.\n"
        },
        {
          "text": "Une suite minorée non strictement décroissante peut stagner",
          "correct": true,
          "feedback": "Bonne réponse : un variant qui stagne\nautorise une boucle infinie. La décroissance\nstricte garantit que l'on s'approche du\nseuil de sortie à chaque 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": "Une suite d'entiers strictement décroissante\net minorée par 0 ne peut prendre qu'un nombre\nfini de valeurs. Cette propriété fonde la\npreuve de terminaison."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "exemple-compteur"
      ],
      "title": "Invariant : compteur d'occurrences",
      "partie": "invariants",
      "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` avant l'indice `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 différents de `cible` avant l'indice `i`",
          "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, les éléments situés\navant l'indice `i`. À la sortie\n(`i = len(liste)`), il donne la postcondition :\n`c` est le nombre d'occurrences dans toute la\nliste."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "variant-multivarie"
      ],
      "title": "Variant multivarié",
      "partie": "terminaison",
      "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",
      "partie": "invariants",
      "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",
      "partie": "assertions",
      "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",
      "partie": "invariants",
      "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 que la liste ne contienne aucun doublon",
          "correct": false,
          "feedback": "Erreur : les doublons ne gênent ni le tri\npar sélection ni sa preuve ; ce qui manque,\nc'est que les k premiers éléments soient les\nplus petits de la liste.\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, on ne peut pas prouver que\nl'invariant se conserve : les k premiers\néléments pourraient être triés tout en\ncontenant de « gros » éléments, et le\nrésultat final serait faux.\n"
        }
      ],
      "explanation": "Bon invariant pour la sélection : les k\npremiers éléments sont triés **et** chacun\nd'eux est inférieur ou égal à tous les\néléments d'indice k à n - 1. Les deux\nconditions sont nécessaires."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "trouver-variant"
      ],
      "title": "Trouver le variant",
      "partie": "terminaison",
      "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": [
        "postcondition",
        "assertion"
      ],
      "title": "Vérifier une postcondition par assertion",
      "partie": "assertions",
      "statement": "La fonction suivante doit renvoyer l'indice d'un\nplus petit élément d'une liste non vide `t` :\n\n```python\ndef indice_min(t):\n    assert len(t) > 0\n    k = 0\n    for i in range(1, len(t)):\n        if t[i] < t[k]:\n            k = i\n    return k\n```\n\nOn veut vérifier, juste avant `return k`, la\npostcondition « `t[k]` est inférieur ou égal à\nchacun des éléments de `t` ». Quelles lignes\nconviennent ?",
      "options": [
        {
          "text": "```python\nfor j in range(len(t)):\n    assert t[k] < t[j]\n```\n",
          "correct": false,
          "feedback": "Erreur : pour `j` égal à `k`, `t[k] < t[k]`\nest faux, donc l'assertion échoue à chaque\nappel, même quand la fonction est juste. Il\nfaut une inégalité large.\n"
        },
        {
          "text": "```python\nassert t[k] <= t[i]\n```\n",
          "correct": false,
          "feedback": "Erreur : après la boucle, `i` vaut le\ndernier indice parcouru, donc on ne compare\n`t[k]` qu'au dernier élément. Pour une liste\nd'un seul élément, `i` n'est même pas défini\net cette ligne provoque une erreur.\n"
        },
        {
          "text": "```python\nfor j in range(len(t)):\n    assert t[k] <= t[j]\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : la boucle compare `t[k]` à\ntous les éléments, et l'assertion échoue dès\nque l'un d'eux est strictement plus petit.\nL'inégalité large accepte le cas où `j` vaut\n`k`, ainsi que les minimums en double.\n"
        },
        {
          "text": "```python\nassert 0 <= k and k < len(t)\n```\n",
          "correct": false,
          "feedback": "Erreur : cette assertion vérifie seulement\nque `k` est un indice valide. Elle resterait\nvraie si la fonction renvoyait l'indice du\nplus grand élément.\n"
        }
      ],
      "explanation": "Des assertions garantissent des préconditions,\ncomme `assert len(t) > 0` en début de fonction,\net des postconditions, vérifiées juste avant de\nrenvoyer le résultat. Une postcondition qui\nporte sur tous les éléments se vérifie par une\nboucle d'assertions."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "trouver-invariant"
      ],
      "title": "Inventer l'invariant",
      "partie": "invariants",
      "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",
      "partie": "assertions",
      "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**, qu'il faut\nprouver à part.\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 `range` fini\ntermine automatiquement.\n"
        }
      ],
      "explanation": "On prouve donc deux choses séparément : la\ncorrection, à l'aide d'un invariant, et la\nterminaison, à l'aide d'un variant. Il faut\nles deux pour être sûr d'obtenir le bon\nrésultat."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "exemple-insertion"
      ],
      "title": "Invariant : tri par insertion",
      "partie": "invariants",
      "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",
      "partie": "terminaison",
      "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",
      "partie": "assertions",
      "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.\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."
    },
    {
      "id": "q29",
      "difficulty": 1,
      "skills": [
        "assertion",
        "comportement"
      ],
      "title": "Effet d'une assertion fausse",
      "partie": "assertions",
      "statement": "Pendant l'exécution d'un programme Python, la condition d'une\ninstruction `assert` s'avère fausse. Que se passe-t-il ?",
      "options": [
        {
          "text": "Le programme s'arrête aussitôt en levant une `AssertionError`",
          "correct": true,
          "feedback": "Bonne réponse : une assertion fausse interrompt l'exécution en\nsignalant la ligne fautive. C'est ce qui en fait un outil de\ndétection : l'erreur apparaît au plus près de sa cause.\n"
        },
        {
          "text": "L'instruction est ignorée et le programme continue normalement",
          "correct": false,
          "feedback": "Erreur : une assertion n'est pas un simple commentaire. Si sa\ncondition est fausse, l'exécution s'arrête sur une\n`AssertionError`.\n"
        },
        {
          "text": "Python corrige la valeur fautive pour rendre la condition vraie",
          "correct": false,
          "feedback": "Erreur : une assertion vérifie, elle ne répare rien. Python ne\nsait pas quelle valeur serait correcte, il signale seulement que\nl'hypothèse du programmeur est violée.\n"
        },
        {
          "text": "La fonction en cours renvoie `False` et l'exécution se poursuit",
          "correct": false,
          "feedback": "Erreur : `assert` ne renvoie aucune valeur. Une condition fausse\ndéclenche une erreur qui arrête le programme.\n"
        }
      ],
      "explanation": "`assert condition` vérifie une hypothèse à un instant précis de\nl'exécution. Tant que la condition est vraie, l'instruction est\nsans effet ; dès qu'elle est fausse, le programme s'arrête avec une\n`AssertionError`, ce qui évite qu'une erreur se propage en silence."
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "precondition",
        "assertion"
      ],
      "title": "Précondition d'une division",
      "partie": "assertions",
      "statement": "La fonction suivante ne doit être appelée qu'avec un argument non\nnul :\n```python\ndef inverse(x):\n    return 1 / x\n```\nQuelle instruction, placée en première ligne du corps de la\nfonction, exprime cette précondition ?",
      "options": [
        {
          "text": "`assert x != 0`",
          "correct": true,
          "feedback": "Bonne réponse : la précondition porte sur l'argument reçu, avant\ntout calcul. Si l'appelant fournit `0`, l'assertion arrête le\nprogramme avec un message clair au lieu de laisser la division\néchouer.\n"
        },
        {
          "text": "`assert x > 0`",
          "correct": false,
          "feedback": "Erreur : cette condition rejette aussi les nombres négatifs, pour\nlesquels l'inverse existe pourtant. La précondition serait plus\nrestrictive que nécessaire.\n"
        },
        {
          "text": "`assert 1 / x != 0`",
          "correct": false,
          "feedback": "Erreur : pour évaluer cette condition, Python calcule d'abord\n`1 / x`. Avec `x` égal à `0`, la division échoue avant même que\nl'assertion ne soit testée.\n"
        },
        {
          "text": "`assert x == 0`",
          "correct": false,
          "feedback": "Erreur : la condition est inversée. Elle arrêterait le programme\npour tous les arguments valides et laisserait passer le seul cas\ninterdit.\n"
        }
      ],
      "explanation": "Une précondition s'écrit comme une assertion sur les arguments,\nplacée au début de la fonction. Elle doit être exactement aussi\nstricte que nécessaire, ni plus, ni moins."
    },
    {
      "id": "q31",
      "difficulty": 3,
      "skills": [
        "postcondition",
        "assertion",
        "tri"
      ],
      "title": "Postcondition d'une fonction de tri",
      "partie": "assertions",
      "statement": "Une fonction `trier(t)` doit réordonner la liste `t` par ordre\ncroissant, en acceptant les valeurs répétées. Quelle assertion,\nplacée juste avant le `return`, vérifie correctement cette\npostcondition ?",
      "options": [
        {
          "text": "```python\nfor i in range(len(t) - 1):\n    assert t[i] <= t[i + 1]\n```",
          "correct": true,
          "feedback": "Bonne réponse : une liste est triée par ordre croissant si\nchaque élément est inférieur ou égal au suivant. Le parcours\nvérifie cette propriété pour tous les couples d'éléments\nvoisins.\n"
        },
        {
          "text": "```python\nfor i in range(len(t) - 1):\n    assert t[0] <= t[i + 1]\n```",
          "correct": false,
          "feedback": "Erreur : cette boucle vérifie seulement que le premier élément\nest le plus petit. La liste `[1, 5, 3, 7]` passe ce test sans\nêtre triée.\n"
        },
        {
          "text": "```python\nfor i in range(len(t) - 1):\n    assert t[i] < t[i + 1]\n```",
          "correct": false,
          "feedback": "Erreur : l'inégalité stricte interdit les valeurs répétées. La\nliste `[2, 2, 3]` est pourtant correctement triée, et cette\nassertion l'arrêterait.\n"
        },
        {
          "text": "```python\nfor i in range(len(t)):\n    assert t[i] <= t[i + 1]\n```",
          "correct": false,
          "feedback": "Erreur : au dernier tour, `i` vaut `len(t) - 1` et `t[i + 1]`\ndésigne un indice hors limites. L'assertion échoue par une\nerreur d'indice, même sur une liste triée.\n"
        }
      ],
      "explanation": "Une postcondition décrit le résultat garanti par la fonction. Pour\nun tri, c'est l'ordre croissant au sens large : chaque élément est\ninférieur ou égal à son voisin de droite. L'assertion doit tester\nexactement cette propriété, sur toute la liste."
    },
    {
      "id": "q32",
      "difficulty": 2,
      "skills": [
        "assertion",
        "message"
      ],
      "title": "Une assertion avec message",
      "partie": "assertions",
      "statement": "Que se passe-t-il à l'exécution de `assert n > 0, \"n doit être\nstrictement positif\"` lorsque `n` vaut `-3` ?",
      "options": [
        {
          "text": "Le programme s'arrête, l'erreur affichant le message",
          "correct": true,
          "feedback": "Bonne réponse : le texte après la virgule accompagne l'erreur.\nIl explique au programmeur quelle hypothèse a été violée.\n"
        },
        {
          "text": "Le message est affiché et le programme continue normalement",
          "correct": false,
          "feedback": "Erreur : une assertion fausse interrompt toujours le programme.\n"
        },
        {
          "text": "Le message est ignoré, `assert` n'acceptant qu'une condition",
          "correct": false,
          "feedback": "Erreur : le second argument, facultatif, est bien pris en\ncompte et affiché avec l'erreur.\n"
        },
        {
          "text": "`n` est remplacé par `3`, valeur absolue de `-3`",
          "correct": false,
          "feedback": "Erreur : une assertion ne corrige rien, elle signale.\n"
        }
      ],
      "explanation": "Un message d'assertion rend l'erreur lisible : au lieu d'une\ncondition cryptique, le programmeur voit l'hypothèse violée en\nfrançais."
    },
    {
      "id": "q33",
      "difficulty": 3,
      "skills": [
        "assertion",
        "cout",
        "invariant"
      ],
      "title": "Le coût d'une assertion dans une boucle",
      "partie": "assertions",
      "statement": "Pour vérifier l'invariant d'un tri, Nora ajoute dans la boucle\nprincipale une assertion qui parcourt toute la partie triée. Quelle\nconséquence ?",
      "options": [
        {
          "text": "Un ralentissement net, l'assertion parcourant la liste à chaque tour",
          "correct": true,
          "feedback": "Bonne réponse : un parcours par tour sur $n$ tours ajoute un\ncoût quadratique. Utile pour mettre au point, l'assertion se\nretire ou se désactive une fois le programme validé.\n"
        },
        {
          "text": "Aucune conséquence, une assertion ne coûtant rien à l'exécution",
          "correct": false,
          "feedback": "Erreur : l'expression de l'assertion est évaluée à chaque\npassage, avec son coût propre.\n"
        },
        {
          "text": "Le tri devient incorrect, l'assertion modifiant la liste pendant le parcours",
          "correct": false,
          "feedback": "Erreur : une assertion bien écrite ne modifie rien ; elle lit\nseulement.\n"
        },
        {
          "text": "Le programme refuse de démarrer, une assertion étant interdite dans une boucle",
          "correct": false,
          "feedback": "Erreur : rien n'interdit une assertion dans une boucle, c'est\nmême courant pendant la mise au point.\n"
        }
      ],
      "explanation": "Les assertions sont un outil de développement : on accepte leur\ncoût pour traquer les erreurs, puis on les allège dans la version\nfinale."
    },
    {
      "id": "q34",
      "difficulty": 2,
      "skills": [
        "variant",
        "tours",
        "calcul"
      ],
      "title": "Compter les tours grâce au variant",
      "partie": "terminaison",
      "statement": "Combien de tours effectue cette boucle, et quel variant permet de\nprouver qu'elle termine ?\n```python\ni = 0\nwhile i < 10:\n    i = i + 3\n```",
      "options": [
        {
          "text": "$4$ tours ; le variant est `10 - i`",
          "correct": true,
          "feedback": "Bonne réponse : `10 - i` est un entier qui diminue strictement\nà chaque tour et la boucle s'arrête dès qu'il devient négatif\nou nul. `i` prend les valeurs $3$, $6$, et ainsi de suite,\njusqu'à atteindre ou dépasser $10$ en $4$ tours.\n"
        },
        {
          "text": "$3$ tours ; le variant est `i`, qui augmente",
          "correct": false,
          "feedback": "Erreur : `i` augmente, il ne peut pas servir de variant, et le\ndernier tour, qui fait dépasser $10$, compte aussi.\n"
        },
        {
          "text": "$10$ tours ; le variant est `i`, de $1$ en $1$",
          "correct": false,
          "feedback": "Erreur : `i` avance de $3$ en $3$, pas de $1$ en $1$.\n"
        },
        {
          "text": "Une infinité de tours, `i` ne valant jamais exactement $10$",
          "correct": false,
          "feedback": "Erreur : la condition est `i < 10`, pas `i != 10` ; dépasser\n$10$ suffit à sortir.\n"
        }
      ],
      "explanation": "Un variant est une quantité entière, positive tant que la boucle\ntourne, et qui décroît strictement à chaque tour : ici `10 - i`.",
      "variants": [
        {
          "statement": "Combien de tours effectue cette boucle, et quel variant permet de\nprouver qu'elle termine ?\n```python\ni = 0\nwhile i < 10:\n    i = i + 3\n```",
          "options": [
            {
              "text": "$4$ tours ; le variant est `10 - i`",
              "correct": true,
              "feedback": "Bonne réponse : `10 - i` est un entier qui diminue strictement\nà chaque tour et la boucle s'arrête dès qu'il devient négatif\nou nul. `i` prend les valeurs $3$, $6$, et ainsi de suite,\njusqu'à atteindre ou dépasser $10$ en $4$ tours.\n"
            },
            {
              "text": "$3$ tours ; le variant est `i`, qui augmente",
              "correct": false,
              "feedback": "Erreur : `i` augmente, il ne peut pas servir de variant, et le\ndernier tour, qui fait dépasser $10$, compte aussi.\n"
            },
            {
              "text": "$10$ tours ; le variant est `i`, de $1$ en $1$",
              "correct": false,
              "feedback": "Erreur : `i` avance de $3$ en $3$, pas de $1$ en $1$.\n"
            },
            {
              "text": "Une infinité de tours, `i` ne valant jamais exactement $10$",
              "correct": false,
              "feedback": "Erreur : la condition est `i < 10`, pas `i != 10` ; dépasser\n$10$ suffit à sortir.\n"
            }
          ],
          "explanation": "Un variant est une quantité entière, positive tant que la boucle\ntourne, et qui décroît strictement à chaque tour : ici `10 - i`."
        },
        {
          "statement": "Combien de tours effectue cette boucle, et quel variant permet de\nprouver qu'elle termine ?\n```python\ni = 0\nwhile i < 7:\n    i = i + 3\n```",
          "options": [
            {
              "text": "$3$ tours ; le variant est `7 - i`",
              "correct": true,
              "feedback": "Bonne réponse : `7 - i` est un entier qui diminue strictement\nà chaque tour et la boucle s'arrête dès qu'il devient négatif\nou nul. `i` prend les valeurs $3$, $6$, et ainsi de suite,\njusqu'à atteindre ou dépasser $7$ en $3$ tours.\n"
            },
            {
              "text": "$2$ tours ; le variant est `i`, qui augmente",
              "correct": false,
              "feedback": "Erreur : `i` augmente, il ne peut pas servir de variant, et le\ndernier tour, qui fait dépasser $7$, compte aussi.\n"
            },
            {
              "text": "$7$ tours ; le variant est `i`, de $1$ en $1$",
              "correct": false,
              "feedback": "Erreur : `i` avance de $3$ en $3$, pas de $1$ en $1$.\n"
            },
            {
              "text": "Une infinité de tours, `i` ne valant jamais exactement $7$",
              "correct": false,
              "feedback": "Erreur : la condition est `i < 7`, pas `i != 7` ; dépasser\n$7$ suffit à sortir.\n"
            }
          ],
          "explanation": "Un variant est une quantité entière, positive tant que la boucle\ntourne, et qui décroît strictement à chaque tour : ici `7 - i`."
        },
        {
          "statement": "Combien de tours effectue cette boucle, et quel variant permet de\nprouver qu'elle termine ?\n```python\ni = 0\nwhile i < 13:\n    i = i + 3\n```",
          "options": [
            {
              "text": "$5$ tours ; le variant est `13 - i`",
              "correct": true,
              "feedback": "Bonne réponse : `13 - i` est un entier qui diminue strictement\nà chaque tour et la boucle s'arrête dès qu'il devient négatif\nou nul. `i` prend les valeurs $3$, $6$, et ainsi de suite,\njusqu'à atteindre ou dépasser $13$ en $5$ tours.\n"
            },
            {
              "text": "$4$ tours ; le variant est `i`, qui augmente",
              "correct": false,
              "feedback": "Erreur : `i` augmente, il ne peut pas servir de variant, et le\ndernier tour, qui fait dépasser $13$, compte aussi.\n"
            },
            {
              "text": "$13$ tours ; le variant est `i`, de $1$ en $1$",
              "correct": false,
              "feedback": "Erreur : `i` avance de $3$ en $3$, pas de $1$ en $1$.\n"
            },
            {
              "text": "Une infinité de tours, `i` ne valant jamais exactement $13$",
              "correct": false,
              "feedback": "Erreur : la condition est `i < 13`, pas `i != 13` ; dépasser\n$13$ suffit à sortir.\n"
            }
          ],
          "explanation": "Un variant est une quantité entière, positive tant que la boucle\ntourne, et qui décroît strictement à chaque tour : ici `13 - i`."
        }
      ]
    },
    {
      "id": "q35",
      "difficulty": 3,
      "skills": [
        "variant",
        "variable-croissante"
      ],
      "title": "Un variant pour une variable qui augmente",
      "partie": "terminaison",
      "statement": "Quel variant prouve la terminaison de cette boucle, où `n` est un\nentier positif ?\n```python\nx = 0\nwhile x * x < n:\n    x = x + 1\n```",
      "options": [
        {
          "text": "`n - x`, qui diminue de $1$ à chaque tour",
          "correct": true,
          "feedback": "Bonne réponse : tant que la boucle tourne, `x * x < n` impose\n`x < n`, donc `n - x` est strictement positif, et chaque tour le\nréduit de $1$.\n"
        },
        {
          "text": "`x`, qui augmente de $1$ à chaque tour",
          "correct": false,
          "feedback": "Erreur : un variant doit décroître ; une quantité qui augmente\nne borne pas le nombre de tours.\n"
        },
        {
          "text": "`x * x`, qui finit par dépasser `n`",
          "correct": false,
          "feedback": "Erreur : `x * x` augmente lui aussi ; c'est l'écart avec `n`\nqui diminue.\n"
        },
        {
          "text": "`n`, qui est positif pendant toute la boucle",
          "correct": false,
          "feedback": "Erreur : `n` ne change pas pendant la boucle, il ne décroît\ndonc pas.\n"
        }
      ],
      "explanation": "Quand une variable grandit vers une borne, le variant est la\ndistance à cette borne : `n - x` ou `n - x * x`."
    },
    {
      "id": "q36",
      "difficulty": 2,
      "skills": [
        "invariant",
        "puissance"
      ],
      "title": "L'invariant d'un calcul de puissance",
      "partie": "invariants",
      "statement": "Quel invariant relie `p` et `i` à chaque passage dans la condition\nde cette boucle ?\n```python\np = 1\ni = 0\nwhile i < n:\n    p = p * 2\n    i = i + 1\n```",
      "options": [
        {
          "text": "`p` vaut $2^{i}$",
          "correct": true,
          "feedback": "Bonne réponse : vrai au départ, $p = 1 = 2^0$, et conservé par\nchaque tour, qui double `p` et augmente `i` de $1$. À la sortie,\n`i` vaut `n` et `p` vaut $2^n$.\n"
        },
        {
          "text": "`p` vaut $2 \\times i$",
          "correct": false,
          "feedback": "Erreur : après deux tours, `p` vaut $4$ et `i` vaut $2$, mais\naprès trois tours `p` vaut $8$ et $2 \\times 3 = 6$.\n"
        },
        {
          "text": "`p` vaut $i^2$",
          "correct": false,
          "feedback": "Erreur : après trois tours, `p` vaut $8$ et $i^2$ vaut $9$.\n"
        },
        {
          "text": "`p` vaut $n$",
          "correct": false,
          "feedback": "Erreur : `p` change à chaque tour, il ne peut pas rester égal à\n`n`.\n"
        }
      ],
      "explanation": "L'invariant `p = 2^i` décrit ce que la boucle a construit jusqu'à\nprésent ; combiné à la condition de sortie, il prouve le résultat."
    }
  ]
}