{
  "chapter": {
    "id": "conditions-boucles",
    "level": "premiere",
    "theme": "Programmation",
    "title": "Conditions et boucles",
    "description": "Structures de contrôle Python : conditions\n(`if`/`elif`/`else`), opérateurs booléens\n(`and`/`or`/`not`), boucles bornées (`for`/`range`),\nboucles non bornées (`while`), boucles imbriquées et\ntraces d'exécution.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "if"
      ],
      "title": "Syntaxe du if",
      "statement": "Quelle est la syntaxe correcte d'une condition\nsimple en Python ?",
      "options": [
        {
          "text": "```python\nif (x > 0)\n    print(\"positif\");\n```\n",
          "correct": false,
          "feedback": "Erreur : syntaxe à la C/Java. En Python, pas\nde parenthèses obligatoires, deux-points\nobligatoires, indentation à la place des\naccolades.\n"
        },
        {
          "text": "```python\nif x > 0 then\n    print(\"positif\")\nend\n```\n",
          "correct": false,
          "feedback": "Erreur : `then` et `end` n'existent pas en\nPython (Lua, Ruby).\n"
        },
        {
          "text": "```python\nif x > 0:\n    print(\"positif\")\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : deux-points en fin de ligne,\nindentation (4 espaces conventionnels) pour\nle bloc.\n"
        },
        {
          "text": "```python\nif {x > 0} print(\"positif\")\n```\n",
          "correct": false,
          "feedback": "Erreur : pas d'accolades en Python.\n"
        }
      ],
      "explanation": "Python utilise l'**indentation significative**.\nTous les blocs (`if`, `for`, `while`, `def`...)\nse reconnaissent à leur indentation, pas à des\naccolades."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "elif"
      ],
      "title": "elif",
      "statement": "À quoi sert le mot-clé `elif` en Python ?",
      "options": [
        {
          "text": "C'est simplement un alias pour le mot-clé `if`",
          "correct": false,
          "feedback": "Erreur : `elif` ne s'utilise qu'**après** un\n`if`.\n"
        },
        {
          "text": "C'est un alias pour `else`",
          "correct": false,
          "feedback": "Erreur : `else` n'a pas de condition. `elif`\na une condition.\n"
        },
        {
          "text": "Cela teste si une variable existe",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec l'existence.\n"
        },
        {
          "text": "C'est l'équivalent du `else if` en C/Java.",
          "correct": true,
          "feedback": "Bonne réponse : `elif` permet d'enchaîner\nplusieurs conditions sans imbrication\nexcessive. Mot-clé Python spécifique.\n"
        }
      ],
      "explanation": "Schéma général :\n```python\nif cond1:\n    ...\nelif cond2:\n    ...\nelse:\n    ...\n```\nAu plus **un** des blocs s'exécute (le premier\ndont la condition est vraie)."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "and-or-not"
      ],
      "title": "Opérateurs logiques",
      "statement": "Comment écrit-on « x est positif **et** pair »\nen Python ?",
      "options": [
        {
          "text": "`x > 0 && x % 2 == 0`",
          "correct": false,
          "feedback": "Erreur : `&&` n'existe pas en Python. C'est\nla syntaxe C/Java.\n"
        },
        {
          "text": "`x > 0 and x % 2 == 0`",
          "correct": true,
          "feedback": "Bonne réponse : Python utilise les\nmots-clés `and`, `or`, `not`.\n"
        },
        {
          "text": "`x > 0 et x % 2 == 0`",
          "correct": false,
          "feedback": "Erreur : Python est en anglais.\n"
        },
        {
          "text": "`x > 0, x % 2 == 0`",
          "correct": false,
          "feedback": "Erreur : la virgule produit un tuple, pas\nune conjonction logique.\n"
        }
      ],
      "explanation": "Trois opérateurs : `and` (et), `or` (ou\ninclusif), `not` (négation). À noter :\névaluation paresseuse, `False and ...` ne\nteste pas la suite."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "for-range"
      ],
      "title": "Boucle for range",
      "statement": "Combien d'itérations effectue\n`for i in range(5):` ?",
      "options": [
        {
          "text": "Une infinité",
          "correct": false,
          "feedback": "Erreur : `range(5)` est borné.\n"
        },
        {
          "text": "4",
          "correct": false,
          "feedback": "Erreur : `range(5)` produit 5 valeurs (0, 1,\n2, 3, 4).\n"
        },
        {
          "text": "5",
          "correct": true,
          "feedback": "Bonne réponse : `range(n)` produit les\nentiers de 0 à n-1, soit **n valeurs** au\ntotal.\n"
        },
        {
          "text": "6",
          "correct": false,
          "feedback": "Erreur : 5 est exclu de la séquence.\n"
        }
      ],
      "explanation": "Convention Python : la borne supérieure est\n**exclue**. `range(5)` = [0, 1, 2, 3, 4]."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "for-range-bornes"
      ],
      "title": "range avec deux arguments",
      "statement": "Que produit `list(range(2, 7))` ?",
      "options": [
        {
          "text": "`[3, 4, 5, 6, 7]`",
          "correct": false,
          "feedback": "Erreur : 2 est **inclus**, 7 est exclu.\n"
        },
        {
          "text": "`[2, 3, 4, 5, 6, 7]`",
          "correct": false,
          "feedback": "Erreur : 7 est **exclu**.\n"
        },
        {
          "text": "`[2, 3, 4, 5, 6]`",
          "correct": true,
          "feedback": "Bonne réponse : `range(a, b)` produit les\nentiers de `a` (inclus) à `b` (exclu).\n"
        },
        {
          "text": "`[2, 7]`",
          "correct": false,
          "feedback": "Erreur : ce serait `range(2, 7, 5)` qui\ndonne `[2]` seulement.\n"
        }
      ],
      "explanation": "Trois formes : `range(n)` (0 à n-1),\n`range(a, b)` (a à b-1),\n`range(a, b, pas)` (a à b-1 par pas)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "while"
      ],
      "title": "Boucle while",
      "statement": "Que fait la boucle suivante ?\n```python\nn = 10\nwhile n > 0:\n    print(n)\n    n -= 1\n```",
      "options": [
        {
          "text": "Affiche les nombres de 1 à 10 dans l'ordre croissant",
          "correct": false,
          "feedback": "Erreur : la boucle décrémente, donc affiche\nen ordre **décroissant**.\n"
        },
        {
          "text": "Tourne sans fin, car `n` reste toujours positif",
          "correct": false,
          "feedback": "Erreur : `n -= 1` fait décroître `n` à chaque\ntour ; après dix tours, `n` vaut 0 et la\ncondition devient fausse.\n"
        },
        {
          "text": "N'affiche rien, car la condition est fausse au départ",
          "correct": false,
          "feedback": "Erreur : `n` vaut 10 au départ, donc `n > 0`\nest vrai et la boucle s'exécute au moins une\nfois.\n"
        },
        {
          "text": "Affiche les nombres de 10 à 1",
          "correct": true,
          "feedback": "Bonne réponse : à chaque tour, on affiche\n`n`, puis on le diminue de 1, d'où un\naffichage décroissant. Quand n atteint 0,\non sort.\n"
        }
      ],
      "explanation": "`while` est une boucle **non bornée** : on\nteste une condition à chaque tour. Bien\nvérifier que la condition finira par devenir\nfausse pour éviter une boucle infinie."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "for",
        "indentation"
      ],
      "title": "Indentation et corps de boucle",
      "statement": "Qu'affiche ce code ?\n```python\nfor i in range(3):\n    print(\"a\")\nprint(\"b\")\n```",
      "options": [
        {
          "text": "`a a a b`",
          "correct": true,
          "feedback": "Bonne réponse : seule la ligne indentée fait\npartie du corps de la boucle, donc `a` est\naffiché trois fois ; `print(\"b\")` n'est\nexécuté qu'une fois, après la boucle.\n"
        },
        {
          "text": "`a b a b a b`",
          "correct": false,
          "feedback": "Erreur : `print(\"b\")` n'est pas indenté, il ne\nfait pas partie du corps de la boucle et n'est\nexécuté qu'une seule fois, après les trois\ntours.\n"
        },
        {
          "text": "`a b`",
          "correct": false,
          "feedback": "Erreur : `range(3)` fournit trois valeurs, le\ncorps de la boucle s'exécute donc trois fois\net `a` est affiché trois fois.\n"
        },
        {
          "text": "`a a a`",
          "correct": false,
          "feedback": "Erreur : le programme ne s'arrête pas à la fin\nde la boucle ; l'instruction `print(\"b\")`, qui\nla suit, est exécutée une fois.\n"
        }
      ],
      "explanation": "L'indentation délimite le corps de la boucle :\ntout ce qui est décalé se répète, ce qui revient\nau niveau de la ligne `for` ne s'exécute qu'une\nfois, après le dernier tour."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "while-trace"
      ],
      "title": "Valeur finale d'un while",
      "statement": "Que vaut `x` à la fin de ce code ?\n```python\nx = 1\nwhile x < 20:\n    x = x * 3\n```",
      "options": [
        {
          "text": "9",
          "correct": false,
          "feedback": "Erreur : pour `x = 9`, la condition `9 < 20`\nest encore vraie, donc un tour supplémentaire\na lieu et `x` passe à 27.\n"
        },
        {
          "text": "20",
          "correct": false,
          "feedback": "Erreur : `x` ne prend que les valeurs 1, 3, 9,\n27 ; rien ne l'arrête exactement sur la borne\n20.\n"
        },
        {
          "text": "27",
          "correct": true,
          "feedback": "Bonne réponse : trace : 1 → 3 → 9 → 27. Pour\n`x = 27`, la condition `27 < 20` est fausse,\nla boucle s'arrête.\n"
        },
        {
          "text": "81",
          "correct": false,
          "feedback": "Erreur : un tour de trop. Dès que `x` vaut 27,\nla condition est fausse et le corps n'est plus\nexécuté.\n"
        }
      ],
      "explanation": "Une boucle `while` peut dépasser la borne de sa\ncondition : le corps s'exécute une dernière fois\nquand la condition est encore vraie, et c'est\ncette valeur qui reste à la sortie."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "parite"
      ],
      "title": "Test de parité",
      "statement": "Quelle expression vaut `True` si et seulement\nsi `n` est **pair** ?",
      "options": [
        {
          "text": "`n // 2 == 0`",
          "correct": false,
          "feedback": "Erreur : ce test est vrai pour n = 0 ou\nn = 1, pas pour la parité.\n"
        },
        {
          "text": "`n % 2 == 0`",
          "correct": true,
          "feedback": "Bonne réponse : `%` donne le reste. Reste\nnul ⇔ pair.\n"
        },
        {
          "text": "`n / 2 == 0`",
          "correct": false,
          "feedback": "Erreur : ce test est rarement vrai (n = 0\nuniquement).\n"
        },
        {
          "text": "`n == 2`",
          "correct": false,
          "feedback": "Erreur : seulement vrai pour n = 2.\n"
        }
      ],
      "explanation": "Idiome très courant. Variante :\n`bool(n % 2)` est `True` ⇔ `n` est impair."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "print-loop"
      ],
      "title": "Boucle simple",
      "statement": "Que fait ce code ?\n```python\ntotal = 0\nfor i in range(1, 5):\n    total += i\nprint(total)\n```",
      "options": [
        {
          "text": "Affiche `10` (= 1+2+3+4)",
          "correct": true,
          "feedback": "Bonne réponse : `range(1, 5)` produit\n`[1, 2, 3, 4]`. La somme vaut 10. C'est\nle schéma « accumulateur ».\n"
        },
        {
          "text": "Boucle infinie",
          "correct": false,
          "feedback": "Erreur : `for` sur un range est toujours\nfini.\n"
        },
        {
          "text": "Affiche `4`",
          "correct": false,
          "feedback": "Erreur : on additionne plusieurs valeurs.\n"
        },
        {
          "text": "Affiche `15` (= 1+2+3+4+5)",
          "correct": false,
          "feedback": "Erreur : 5 est **exclu** de `range(1, 5)`.\n"
        }
      ],
      "explanation": "Schéma d'accumulation à connaître. En Python,\non peut aussi écrire `sum(range(1, 5))`."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "imbrication"
      ],
      "title": "Boucles imbriquées",
      "statement": "Combien de fois `print(...)` est-il exécuté ?\n```python\nfor i in range(3):\n    for j in range(2):\n        print(i, j)\n```",
      "options": [
        {
          "text": "5",
          "correct": false,
          "feedback": "Erreur : ce serait additif (3 + 2), mais\nc'est multiplicatif.\n"
        },
        {
          "text": "3",
          "correct": false,
          "feedback": "Erreur : la double boucle multiplie les\nitérations.\n"
        },
        {
          "text": "9",
          "correct": false,
          "feedback": "Erreur : ce serait `range(3) × range(3)`.\n"
        },
        {
          "text": "6",
          "correct": true,
          "feedback": "Bonne réponse : 3 × 2 = 6 itérations.\nCouples : (0,0), (0,1), (1,0), (1,1),\n(2,0), (2,1).\n"
        }
      ],
      "explanation": "Règle : pour des boucles imbriquées, le total\nest le produit des nombres d'itérations."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "logique-courte"
      ],
      "title": "Évaluation paresseuse",
      "statement": "Pourquoi `False and 1/0 == 0` ne lève-t-il pas\n`ZeroDivisionError` en Python ?",
      "options": [
        {
          "text": "Parce que `==` est prioritaire sur `/`, donc la division n'est jamais calculée",
          "correct": false,
          "feedback": "Erreur : `/` est prioritaire sur `==`. Évaluée\nseule, l'expression `1/0 == 0` lève bien\n`ZeroDivisionError`.\n"
        },
        {
          "text": "Parce que la division par zéro est tolérée par Python",
          "correct": false,
          "feedback": "Erreur : `1/0` lève bien `ZeroDivisionError`.\n"
        },
        {
          "text": "Parce que les nombres entiers ne plantent jamais",
          "correct": false,
          "feedback": "Erreur : `1/0` plante en général.\n"
        },
        {
          "text": "Parce que `and` est paresseux : si le premier est `False`, le second est ignoré",
          "correct": true,
          "feedback": "Bonne réponse : Python optimise. Cela permet\ndes idiomes comme `if liste and liste[0] >\n0:` (test de non-vide avant d'accéder à\nl'indice 0).\n"
        }
      ],
      "explanation": "Évaluation paresseuse : `and` s'arrête au\npremier `False` ; `or` s'arrête au premier\n`True`. Très utile pour court-circuiter des\ntests coûteux ou risqués."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "while-trace",
        "arret-anticipe"
      ],
      "title": "Trace d'un arrêt anticipé",
      "statement": "Que produit ce code ?\n```python\ni = 0\nfini = False\nwhile i < 10 and not fini:\n    if i == 3:\n        fini = True\n    else:\n        print(i)\n    i = i + 1\n```",
      "options": [
        {
          "text": "0 1 2 3 4 5 6 7 8 9",
          "correct": false,
          "feedback": "Erreur : dès que `fini` passe à `True`, la\ncondition `not fini` devient fausse et la\nboucle s'arrête, bien avant `i = 10`.\n"
        },
        {
          "text": "Aucune sortie",
          "correct": false,
          "feedback": "Erreur : au départ `fini` vaut `False`, donc\n`not fini` vaut `True` et la boucle démarre ;\nl'arrêt n'intervient qu'à i = 3.\n"
        },
        {
          "text": "0 1 2",
          "correct": true,
          "feedback": "Bonne réponse : on affiche 0, 1, 2 ; au tour\ni = 3, la branche `if` met `fini` à `True` sans\nafficher, puis la condition du `while` devient\nfausse.\n"
        },
        {
          "text": "0 1 2 3",
          "correct": false,
          "feedback": "Erreur : au tour i = 3, c'est la branche `if`\nqui s'exécute, pas le `else` : le 3 n'est pas\naffiché.\n"
        }
      ],
      "explanation": "Tracer pas à pas : i = 0 → print(0) ; i = 1 →\nprint(1) ; i = 2 → print(2) ; i = 3 → `fini`\npasse à `True`, puis le test `not fini` échoue."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "trace-filtre"
      ],
      "title": "Trace avec négation",
      "statement": "Que produit ce code ?\n```python\nfor i in range(5):\n    if not (i % 2 == 0):\n        print(i)\n```",
      "options": [
        {
          "text": "Boucle infinie",
          "correct": false,
          "feedback": "Erreur : `for` sur un range est borné.\n"
        },
        {
          "text": "1 3 (les impairs)",
          "correct": true,
          "feedback": "Bonne réponse : pour i ∈ {0, 2, 4} (pairs),\n`i % 2 == 0` est vrai, donc sa négation est\nfausse et le `print` est sauté. Restent 1 et 3.\n"
        },
        {
          "text": "0 1 2 3 4",
          "correct": false,
          "feedback": "Erreur : la condition filtre les valeurs ;\nl'affichage n'a lieu que pour les impairs.\n"
        },
        {
          "text": "0 2 4 (les pairs)",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse, le `not` renverse le\ntest de parité : on affiche ceux qui ne sont\n**pas** pairs.\n"
        }
      ],
      "explanation": "`not` inverse un booléen : `not (i % 2 == 0)`\nest vrai exactement quand `i` est impair.\nÉquivalent plus direct : `if i % 2 != 0:`."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "while-condition"
      ],
      "title": "While et condition",
      "statement": "Que fait ce code, supposant la saisie d'un\nentier non nul ?\n```python\nn = int(input())\nwhile n != 1:\n    if n % 2 == 0:\n        n = n // 2\n    else:\n        n = 3 * n + 1\n    print(n)\n```",
      "options": [
        {
          "text": "Boucle infinie systématique",
          "correct": false,
          "feedback": "Erreur : pour tout n testé jusqu'à présent,\nla suite atteint 1.\n"
        },
        {
          "text": "Affiche la suite de Fibonacci",
          "correct": false,
          "feedback": "Erreur : aucune relation avec Fibonacci.\n"
        },
        {
          "text": "Calcule la suite de Syracuse jusqu'à atteindre 1",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'algorithme classique\ndont la terminaison est une **conjecture**\n(non démontrée, mais vérifiée pour de très\ngrandes valeurs).\n"
        },
        {
          "text": "Lève systématiquement une exception à l'exécution",
          "correct": false,
          "feedback": "Erreur : code valide.\n"
        }
      ],
      "explanation": "La conjecture de Syracuse (1937) demeure ouverte\nen mathématiques : on ne sait pas prouver que la\nsuite atteint toujours 1 pour tout n entier\npositif."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "bug-condition"
      ],
      "title": "Bug d'imbrication",
      "statement": "```python\nx = 5\nif x > 0:\n    if x > 10:\n        print(\"grand\")\n    else:\n        print(\"moyen\")\nelse:\n    print(\"négatif ou nul\")\n```\nQu'affiche le code ?",
      "options": [
        {
          "text": "Rien",
          "correct": false,
          "feedback": "Erreur : un cas est forcément exécuté.\n"
        },
        {
          "text": "`négatif ou nul`",
          "correct": false,
          "feedback": "Erreur : 5 est positif.\n"
        },
        {
          "text": "`moyen`",
          "correct": true,
          "feedback": "Bonne réponse : x = 5 entre dans le premier\n`if` (5 > 0) ; puis comme 5 ≤ 10, on\nexécute le `else` interne.\n"
        },
        {
          "text": "`grand`",
          "correct": false,
          "feedback": "Erreur : 5 n'est pas > 10.\n"
        }
      ],
      "explanation": "Important : l'`else` est toujours apparié au\n`if` du même niveau d'indentation. Lecture\nattentive nécessaire."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "chained"
      ],
      "title": "Chaînage",
      "statement": "L'expression `0 < x < 100` en Python est\néquivalente à :",
      "options": [
        {
          "text": "`0 < x or x < 100`",
          "correct": false,
          "feedback": "Erreur : c'est `and` (et), pas `or` (ou).\n"
        },
        {
          "text": "`0 < x and x < 100`",
          "correct": true,
          "feedback": "Bonne réponse : Python autorise le\nchaînage. Idiomatique pour les bornes.\n"
        },
        {
          "text": "Une erreur de syntaxe",
          "correct": false,
          "feedback": "Erreur : la syntaxe est valide en Python.\n"
        },
        {
          "text": "`(0 < x) < 100`",
          "correct": false,
          "feedback": "Erreur : ce serait du C ou du JavaScript, pas\ndu Python.\n"
        }
      ],
      "explanation": "Cas pratique : `if 0 <= i < len(liste):` pour\nvérifier qu'un indice est valide. Bien plus\nlisible que `if i >= 0 and i < len(liste):`."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "parcours-liste"
      ],
      "title": "Parcours de liste",
      "statement": "Quelle est la façon la plus pythonique d'afficher\nles éléments d'une liste ?",
      "options": [
        {
          "text": "```python\nwhile i in liste:\n    print(i)\n```\n",
          "correct": false,
          "feedback": "Erreur : `i in liste` est un test, pas une\nitération. Et `i` n'est pas défini.\n"
        },
        {
          "text": "```python\nfor x in liste:\n    print(x)\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : forme idiomatique Python.\nPlus lisible et moins sujette aux erreurs\nd'indice.\n"
        },
        {
          "text": "```python\nfor i in range(len(liste)):\n    print(liste[i])\n```\n",
          "correct": false,
          "feedback": "Code correct mais peu pythonique. Préférer\nla boucle directe sur les éléments.\n"
        },
        {
          "text": "```python\ndo print(x) for x in liste\n```\n",
          "correct": false,
          "feedback": "Erreur : syntaxe invalide en Python (peut-\nêtre Ruby ou Perl).\n"
        }
      ],
      "explanation": "Bonne pratique : `for x in liste:` quand on\nn'a pas besoin de l'indice. Si l'indice est\nutile aussi, on parcourt les indices avec\n`for i in range(len(liste)):` et on lit\nl'élément par `liste[i]`."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "comprehension"
      ],
      "title": "Compréhension de liste",
      "statement": "Quel est l'équivalent **pythonique** de :\n```python\ncarres = []\nfor i in range(10):\n    carres.append(i * i)\n```",
      "options": [
        {
          "text": "`carres = [i * i for i in range(10)]`",
          "correct": true,
          "feedback": "Bonne réponse : compréhension de liste,\nplus concise et idiomatique.\n"
        },
        {
          "text": "`carres = [10][i * i]`",
          "correct": false,
          "feedback": "Erreur : syntaxe invalide.\n"
        },
        {
          "text": "`carres = [i * 2 for i in range(10)]`",
          "correct": false,
          "feedback": "Erreur : `i * 2` calcule le **double**, pas le\ncarré : on obtient `[0, 2, 4, ..., 18]` au\nlieu de `[0, 1, 4, ..., 81]`.\n"
        },
        {
          "text": "`carres = range(10) ** 2`",
          "correct": false,
          "feedback": "Erreur : on ne peut pas élever un range au\ncarré.\n"
        }
      ],
      "explanation": "Compréhension de liste générale :\n`[expr for x in iterable if cond]`. Très\nutilisée en Python pour les transformations\nsimples."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "trace-boucle"
      ],
      "title": "Trace de while",
      "statement": "Que vaut `n` à la fin de :\n```python\nn = 100\nwhile n > 1:\n    n = n // 2\n```",
      "options": [
        {
          "text": "Une infinité",
          "correct": false,
          "feedback": "Erreur : `n // 2` réduit n à chaque tour.\n"
        },
        {
          "text": "1",
          "correct": true,
          "feedback": "Bonne réponse : trace : 100 → 50 → 25 → 12\n→ 6 → 3 → 1. À ce moment, `1 > 1` est faux,\non sort.\n"
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : la boucle s'arrête quand n ≤ 1, donc\nn vaut au moins 1 à la fin.\n"
        },
        {
          "text": "2",
          "correct": false,
          "feedback": "Erreur : 2 // 2 = 1, donc on continue.\n"
        }
      ],
      "explanation": "Cette boucle calcule combien de fois on peut\ndiviser n par 2. C'est le **logarithme entier**\nen base 2 (motif de complexité O(log n))."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "boucle-infinie-bug"
      ],
      "title": "Boucle infinie",
      "statement": "Pourquoi cette boucle est-elle infinie ?\n```python\ni = 0\nwhile i < 10:\n    print(i)\n```",
      "options": [
        {
          "text": "Parce que `print` ne renvoie rien",
          "correct": false,
          "feedback": "Le fait que `print` ne\nrenvoie pas de valeur\n(autre que `None`) n'a\naucun lien avec la\nterminaison de la boucle.\nCe qui pose problème ici,\nc'est que `i` n'évolue\njamais.\n"
        },
        {
          "text": "Parce que `while` est toujours infini",
          "correct": false,
          "feedback": "Erreur : `while` peut très bien terminer.\n"
        },
        {
          "text": "Parce que l'initialisation `i = 0` serait fausse",
          "correct": false,
          "feedback": "Erreur : 0 < 10 est vrai.\n"
        },
        {
          "text": "Parce que `i` n'est jamais modifié dans la boucle",
          "correct": true,
          "feedback": "Bonne réponse : il faut ajouter `i += 1`\npour que la condition `i < 10` finisse par\ndevenir fausse.\n"
        }
      ],
      "explanation": "Bug ultra-classique chez les débutants. Toujours\nvérifier qu'un **variant** (ici, `i`) progresse\nvers la condition de sortie."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "for-range",
        "trace",
        "borne-figee"
      ],
      "title": "Borne d'un range modifiée en cours de boucle",
      "statement": "Qu'affiche ce code ?\n```python\nn = 3\nfor i in range(n):\n    n = n + 1\nprint(i, n)\n```",
      "options": [
        {
          "text": "`2 6`, car `range(3)` est calculé une fois pour toutes au début de la boucle",
          "correct": true,
          "feedback": "Bonne réponse : `range(n)` est évalué une seule\nfois, avec `n` valant 3 ; la boucle fait donc\ntrois tours (`i` vaut 0, 1 puis 2) et `n` est\nincrémenté trois fois, de 3 à 6.\n"
        },
        {
          "text": "La boucle ne s'arrête jamais, car `n` augmente d'une unité à chaque tour",
          "correct": false,
          "feedback": "Erreur : la borne n'est pas relue à chaque tour.\nModifier `n` dans le corps ne change pas la\nsuite `range(3)` déjà construite, contrairement\nà une liste que l'on allonge en la parcourant.\n"
        },
        {
          "text": "`3 6`, car `i` termine à la valeur de la borne de départ, c'est-à-dire 3",
          "correct": false,
          "feedback": "Erreur : `range(3)` produit 0, 1 et 2, jamais 3.\nLa dernière valeur prise par `i` est 2, car la\nborne d'un `range` est exclue.\n"
        },
        {
          "text": "Une erreur à l'exécution, car `n` ne peut pas être modifiée pendant le parcours de `range(n)`",
          "correct": false,
          "feedback": "Erreur : aucune règle n'interdit d'affecter `n`\ndans le corps de la boucle ; le code s'exécute\nsans erreur. Une fois la boucle lancée, `n` et\nla suite `range(3)` sont indépendants.\n"
        }
      ],
      "explanation": "`range(n)` est construit une seule fois, à l'entrée\ndans la boucle. Le nombre de tours est figé à ce\nmoment-là : modifier `n` ensuite n'allonge pas la\nboucle, à la différence d'une liste que l'on\nmodifie pendant son parcours."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "piege-augmenter-liste"
      ],
      "title": "Modifier en parcourant",
      "statement": "Que se passe-t-il à l'exécution de ce code ?\n```python\nliste = [1, 2, 3]\nfor x in liste:\n    liste.append(x * 2)\n```",
      "options": [
        {
          "text": "La boucle ne s'arrête jamais, car la liste s'allonge à chaque tour",
          "correct": true,
          "feedback": "Bonne réponse : la boucle `for` lit la liste\ntelle qu'elle est au moment de chaque tour ;\ncomme chaque tour ajoute un élément, la fin\nn'est jamais atteinte. Ne jamais modifier une\nliste pendant qu'on la parcourt.\n"
        },
        {
          "text": "La boucle fait trois tours et la liste finale vaut `[1, 2, 3, 2, 4, 6]`",
          "correct": false,
          "feedback": "Erreur : le `for` ne fige pas la liste au\ndépart ; il consulte sa longueur courante à\nchaque tour et trouve toujours un élément de\nplus. Ce résultat exigerait de parcourir une\ncopie, `for x in liste[:]:`.\n"
        },
        {
          "text": "Python lève une erreur, car une liste ne peut pas être modifiée pendant son parcours",
          "correct": false,
          "feedback": "Erreur : Python n'interdit rien ici, aucune\nexception n'est levée ; c'est justement ce\nsilence qui rend le piège dangereux.\n"
        },
        {
          "text": "La boucle fait trois tours, car `append` ne modifie pas `liste` mais en renvoie une copie",
          "correct": false,
          "feedback": "Erreur : `append` modifie la liste **sur\nplace** (et renvoie `None`) : c'est bien la\nliste parcourue qui grandit.\n"
        }
      ],
      "explanation": "Règle de sécurité : ne **jamais** modifier une\ncollection pendant qu'on la parcourt. Parcourir\nune copie (`liste[:]`) ou construire une nouvelle\nliste."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "for-range",
        "elif",
        "compteur"
      ],
      "title": "Compteur avec if et elif",
      "statement": "Qu'affiche ce code ?\n```python\nc = 0\nfor n in range(1, 16):\n    if n % 3 == 0:\n        c = c + 1\n    elif n % 5 == 0:\n        c = c + 2\nprint(c)\n```",
      "options": [
        {
          "text": "9",
          "correct": true,
          "feedback": "Bonne réponse : cinq multiples de 3 (3, 6, 9,\n12, 15) ajoutent 1 chacun ; 5 et 10 ajoutent 2\nchacun ; 15, déjà pris par le `if`, n'entre\npas dans le `elif`. Total 5 + 4 = 9.\n"
        },
        {
          "text": "11",
          "correct": false,
          "feedback": "Erreur : 15 est compté deux fois, comme avec\ndeux `if` indépendants. Avec `elif`, la\nseconde condition n'est pas testée quand la\npremière est vraie.\n"
        },
        {
          "text": "5",
          "correct": false,
          "feedback": "Erreur : la branche `elif` est ignorée ; 5 et\n10, qui ne sont pas multiples de 3, y entrent\net ajoutent 2 chacun.\n"
        },
        {
          "text": "7",
          "correct": false,
          "feedback": "Erreur : la branche `elif` ajoute 2, pas 1 ;\n5 et 10 apportent donc 4 au total, et non 2.\n"
        }
      ],
      "explanation": "Dans un `if`/`elif`, une seule branche s'exécute\npar tour. Il faut donc trier chaque valeur de\n`range(1, 16)` : multiples de 3 (cinq valeurs),\npuis multiples de 5 restants (5 et 10)."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Synthèse",
      "statement": "Parmi les affirmations suivantes, laquelle est\n**fausse** ?",
      "options": [
        {
          "text": "La condition d'un `while` n'est vérifiée qu'après chaque exécution du corps",
          "correct": true,
          "feedback": "Faux (donc bonne réponse) : la condition est\ntestée **avant** chaque tour, y compris le\npremier. Si elle est fausse d'emblée, le corps\nne s'exécute jamais.\n"
        },
        {
          "text": "La boucle `for` sur un `range` fini termine toujours sans intervention",
          "correct": false,
          "feedback": "Vrai : terminaison automatique.\n"
        },
        {
          "text": "Les opérateurs logiques `and`, `or`, `not` permettent de combiner des conditions",
          "correct": false,
          "feedback": "Vrai : ce sont les trois opérateurs\nbooléens.\n"
        },
        {
          "text": "La boucle `while` peut être infinie si la condition ne devient jamais fausse",
          "correct": false,
          "feedback": "Vrai : risque classique de bug.\n"
        }
      ],
      "explanation": "Le `while` teste d'abord, exécute ensuite : une\nboucle `while` peut ne faire aucun tour, alors\nqu'une boucle `for` sur un `range` fini termine\ntoujours."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "while",
        "saisie",
        "initialisation"
      ],
      "title": "Boucle de saisie validée",
      "statement": "On veut que l'utilisateur saisisse un entier\nstrictement positif et redemander tant que la\nréponse n'est pas correcte. Quel code convient le\nmieux ?",
      "options": [
        {
          "text": "```python\nfor i in range(10):\n    n = int(input(\"n ? \"))\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code demande **dix fois** la valeur,\nquelle que soit la qualité des saisies, et\nconserve la dernière. Il ne s'arrête pas dès\nqu'une bonne valeur est donnée.\n"
        },
        {
          "text": "```python\nn = 0\nwhile n <= 0:\n    n = int(input(\"n ? \"))\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : `n` est initialisé à une valeur\ninvalide (0 n'est pas strictement positif), ce\nqui garantit un premier passage ; la boucle\nredemande ensuite tant que la saisie reste\nnégative ou nulle.\n"
        },
        {
          "text": "```python\nn = -1\nwhile n > 0:\n    n = int(input(\"n ? \"))\n```\n",
          "correct": false,
          "feedback": "Erreur : la condition initiale `n > 0` est\nfausse (puisque `n = -1`), donc la boucle ne\ns'exécute **jamais**. Aucune saisie n'est\nréellement effectuée.\n"
        },
        {
          "text": "```python\nn = int(input(\"n ? \"))\nif n <= 0:\n    n = int(input(\"n ? \"))\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code ne redemande qu'**une seule fois** la valeur. Si l'utilisateur entre encore\nune valeur invalide à la deuxième saisie, le\nprogramme continue avec cette valeur erronée.\n"
        }
      ],
      "explanation": "Schéma de validation de saisie : initialiser la\nvariable avec une valeur qui ne convient pas, pour\nforcer un premier tour, puis répéter tant que la\nvaleur ne convient pas. La condition du `while`\ndit exactement « tant que la saisie est mauvaise »."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "if",
        "elif",
        "structure"
      ],
      "title": "if/elif vs plusieurs if",
      "statement": "Considérons les deux extraits suivants. En quoi\ndiffèrent-ils ?\n\n```python\n# Extrait A\nif x > 0:\n    y = 1\nelif x > 10:\n    y = 2\n\n# Extrait B\nif x > 0:\n    y = 1\nif x > 10:\n    y = 2\n```",
      "options": [
        {
          "text": "Ils produisent toujours le même résultat, quelle que soit la valeur de `x`",
          "correct": false,
          "feedback": "Erreur : ils diffèrent justement quand `x` est\nstrictement supérieur à $10$. L'extrait A donne\n$y = 1$ (la branche `elif` n'est pas testée\nparce que la première est vraie), l'extrait B\ndonne $y = 2$ (les deux conditions s'évaluent).\n"
        },
        {
          "text": "A n'exécute qu'une seule branche ; B peut exécuter les deux",
          "correct": true,
          "feedback": "Bonne réponse : `elif` indique que l'on\nn'évalue cette branche que si la précédente\nétait fausse. Avec deux `if` séparés, les deux\nconditions sont toujours vérifiées\nindépendamment, ce qui peut conduire à des\nréaffectations indésirables.\n"
        },
        {
          "text": "L'extrait A produit une erreur car `elif` ne fonctionne pas seul",
          "correct": false,
          "feedback": "Erreur : `elif` est un mot-clé valide en Python\ntant qu'il suit un `if`. Sa présence ne\nprovoque aucune erreur de syntaxe.\n"
        },
        {
          "text": "L'extrait B est plus rapide, car il évite le mot-clé `elif`",
          "correct": false,
          "feedback": "Erreur : la différence ne porte pas sur la\nvitesse mais sur la **logique**. L'extrait A\npeut au contraire être plus rapide, car il\nn'évalue pas la deuxième condition lorsque la\npremière est vraie.\n"
        }
      ],
      "explanation": "Règle pratique : utiliser `elif` lorsque les\nbranches s'excluent mutuellement (un seul cas est\nvrai à la fois). Utiliser des `if` séparés\nlorsqu'on veut **vraiment** que plusieurs\nconditions puissent déclencher chacune leur action."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "parcours-parallele",
        "indice"
      ],
      "title": "Parcours parallèle de deux listes",
      "statement": "Deux listes `noms` et `notes` de même longueur\nrangent au même rang le nom d'un élève et sa note.\nQuel code affiche chaque nom suivi de sa note ?",
      "options": [
        {
          "text": "```python\nprint(noms + notes)\n```\n",
          "correct": false,
          "feedback": "Erreur : la concaténation `noms + notes` produit\nune seule liste juxtaposant les deux. Il n'y a\npas d'appariement, et tout est affiché en bloc.\n"
        },
        {
          "text": "```python\nfor i in range(len(noms)):\n    print(noms[i], notes[i])\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : l'indice `i` parcourt les\npositions de 0 à `len(noms) - 1` et sert à lire\nau même rang le nom et la note qui lui\ncorrespond.\n"
        },
        {
          "text": "```python\nfor nom in noms:\n    for note in notes:\n        print(nom, note)\n```\n",
          "correct": false,
          "feedback": "Erreur : ces boucles imbriquées affichent\n**toutes les paires** (nom, note), pas\nuniquement celles de même rang. Pour deux\nlistes de longueur $n$, on obtient $n^2$\nlignes au lieu de $n$.\n"
        },
        {
          "text": "```python\nfor nom in noms:\n    print(nom, notes[nom])\n```\n",
          "correct": false,
          "feedback": "Erreur : `nom` est une chaîne, pas un indice.\n`notes[nom]` lève `TypeError`, car une liste ne\ns'indexe qu'avec un entier. Pour retrouver le\nrang, il faut parcourir les indices.\n"
        }
      ],
      "explanation": "Le parcours par indice `for i in range(len(...))`\nest le moyen d'accéder au même rang dans deux\nlistes. L'hypothèse « de même longueur » garantit\nque `notes[i]` existe pour tout indice valide de\n`noms`."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "while",
        "condition",
        "dichotomie"
      ],
      "title": "Calcul d'une racine entière",
      "statement": "On exécute le code suivant :\n\n```python\nn = 30\nr = 0\nwhile r * r <= n:\n    r = r + 1\n```\n\nQuelle est la valeur de `r` à la sortie de la boucle ?",
      "options": [
        {
          "text": "$7$",
          "correct": false,
          "feedback": "Erreur : la boucle s'arrête dès que la condition\ndevient fausse, sans incrémenter une fois de\nplus. Pour $r = 6$, on a déjà $36 > 30$ : la\nboucle est sortie avec $r = 6$.\n"
        },
        {
          "text": "$6$",
          "correct": true,
          "feedback": "Bonne réponse : la boucle s'arrête dès que\n$r \\times r > n$. Pour $r = 5$, $25 \\leq 30$ :\non incrémente. Pour $r = 6$, $36 > 30$ : on\nsort. La valeur finale est donc $r = 6$.\n"
        },
        {
          "text": "$30$",
          "correct": false,
          "feedback": "Erreur : confusion entre la valeur cible $n$ et\nla variable `r`. La boucle calcule la **racine carrée entière** par majorant, pas la valeur de\n$n$.\n"
        },
        {
          "text": "$5$",
          "correct": false,
          "feedback": "Erreur : pour $r = 5$, on a $r \\times r = 25\n\\leq 30$, donc la condition est encore vraie.\nLa boucle fait au moins une itération de plus.\n"
        }
      ],
      "explanation": "Cet algorithme calcule le plus petit entier $r$\ntel que $r^2 > n$. À la sortie, $r - 1$ est la\npartie entière de $\\sqrt{n}$. Pour $n = 30$, on\nobtient $r = 6$, et $r - 1 = 5$, conforme à\n$\\lfloor\\sqrt{30}\\rfloor = 5$."
    }
  ]
}