{
  "chapter": {
    "id": "programmation",
    "level": "snt",
    "theme": "Programmation",
    "title": "Notions transversales de programmation",
    "description": "Variables et affectation, types de base, séquence d'instructions,\ninstruction conditionnelle, boucles bornées et non bornées,\ndéfinition et appel de fonctions, listes simples, opérateurs\narithmétiques, de comparaison et booléens, affichage et saisie,\nlecture d'un message d'erreur.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "affectation",
        "variables"
      ],
      "title": "Une suite d'affectations",
      "statement": "Ce programme note les dimensions d'une photographie, en pixels.\n\n```python\nlargeur = 1024\nhauteur = 768\nhauteur = largeur\nlargeur = 640\n```\n\nQue vaut `hauteur` à la fin ?",
      "options": [
        {
          "text": "768",
          "correct": false,
          "feedback": "Erreur : c'est la valeur du départ, mais la troisième ligne l'a\nremplacée par celle de `largeur`. Une affectation écrase sans\nprévenir ce que la variable contenait."
        },
        {
          "text": "1024",
          "correct": true,
          "feedback": "Bonne réponse : la troisième ligne recopie dans `hauteur` la valeur\nque `largeur` porte à cet instant, soit 1024. La ligne suivante ne\ntouche qu'à `largeur`."
        },
        {
          "text": "640",
          "correct": false,
          "feedback": "Erreur : suppose qu'une affectation crée un lien durable entre les\ndeux variables. Elle recopie une valeur une fois pour toutes, puis\nchaque variable vit de son côté."
        },
        {
          "text": "1792",
          "correct": false,
          "feedback": "Erreur : additionne les deux valeurs. Le signe `=` ne calcule rien,\nil range dans la variable de gauche la valeur trouvée à droite."
        }
      ],
      "explanation": "En Python, le signe `=` n'énonce pas une égalité mathématique, il range\nune valeur dans une variable. Une affectation se lit de droite à gauche,\net seule la variable écrite à gauche change."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "types",
        "chaine"
      ],
      "title": "Repérer une chaîne de caractères",
      "statement": "Un objet connecté range quatre informations dans quatre variables.\n\n```python\nville = \"Lyon\"\naltitude = 173\nhumide = True\nvitesse = 12.5\n```\n\nLaquelle contient une chaîne de caractères ?",
      "options": [
        {
          "text": "`altitude`, qui contient 173",
          "correct": false,
          "feedback": "Erreur : 173 est un nombre entier. Il s'écrit sans guillemets et\nsans point décimal, et se prête aux calculs."
        },
        {
          "text": "`humide`, qui contient True",
          "correct": false,
          "feedback": "Erreur : `True` est un booléen, l'une des deux seules valeurs de\nvérité. Il s'écrit sans guillemets et ne peut valoir que vrai ou\nfaux."
        },
        {
          "text": "`vitesse`, qui contient 12.5",
          "correct": false,
          "feedback": "Erreur : 12.5 est un nombre flottant, reconnaissable à son point\ndécimal. Les guillemets sont absents."
        },
        {
          "text": "`ville`, qui contient \"Lyon\"",
          "correct": true,
          "feedback": "Bonne réponse : les guillemets font de `Lyon` une suite de\ncaractères, et non un nom de variable."
        }
      ],
      "explanation": "Les quatre types de base se reconnaissent à l'écriture : guillemets pour\nune chaîne, chiffres seuls pour un entier, point décimal pour un\nflottant, `True` ou `False` pour un booléen."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "sequence",
        "print"
      ],
      "title": "Un compteur de messages",
      "statement": "Une application compte les messages reçus.\n\n```python\nmessages = 0\nmessages = messages + 3\nmessages = messages + 2\nprint(messages)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "5",
          "correct": true,
          "feedback": "Bonne réponse : la variable part de 0, reçoit 3, puis 2 de plus.\nChaque ligne lit la valeur courante, ajoute un nombre et range le\ntotal au même endroit."
        },
        {
          "text": "3",
          "correct": false,
          "feedback": "Erreur : ne retient que la première addition. La ligne d'après\nrepart de 3 et ajoute encore 2."
        },
        {
          "text": "2",
          "correct": false,
          "feedback": "Erreur : suppose que la dernière addition remplace tout ce qui a\nété calculé avant. Elle ajoute 2 à ce que la variable contient\ndéjà."
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : retient la valeur du départ. Les deux lignes qui suivent\nmodifient bel et bien la variable avant l'affichage."
        }
      ],
      "explanation": "La ligne `messages = messages + 3` se lit en deux temps : on calcule\nd'abord `messages + 3` avec la valeur actuelle, puis on range le\nrésultat dans `messages`. C'est le schéma de tout compteur."
    },
    {
      "id": "q04",
      "difficulty": 2,
      "skills": [
        "affectation",
        "trace"
      ],
      "title": "Un échange de coordonnées manqué",
      "statement": "Ce programme voudrait échanger la latitude et la longitude relevées par\nun récepteur.\n\n```python\nlat = 45\nlon = 4\nlat = lon\nlon = lat\nprint(lat, lon)\n```\n\nQu'affiche-t-il ?",
      "options": [
        {
          "text": "`45 4`",
          "correct": false,
          "feedback": "Erreur : ce sont les valeurs du départ, comme si les deux\naffectations n'avaient rien fait. La troisième ligne modifie\npourtant `lat`."
        },
        {
          "text": "`4 45`",
          "correct": false,
          "feedback": "Erreur : c'est l'échange souhaité, mais le programme ne l'obtient\npas. La valeur 45 disparaît dès que `lat` reçoit celle de `lon`."
        },
        {
          "text": "`4 4`",
          "correct": true,
          "feedback": "Bonne réponse : `lat` prend la valeur 4, puis `lon` reçoit cette\nmême valeur 4. L'ancienne valeur de `lat` n'existe plus nulle part."
        },
        {
          "text": "`45 45`",
          "correct": false,
          "feedback": "Erreur : c'est le résultat du même enchaînement mené dans l'autre\nsens, où `lon` recevrait d'abord la valeur de `lat`."
        }
      ],
      "explanation": "Échanger deux variables réclame une troisième variable, chargée de\ngarder la valeur qui va être écrasée. Sans elle, la première\naffectation détruit ce que la seconde devait recopier."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "operateurs",
        "calcul"
      ],
      "title": "La division entière",
      "statement": "Un programme range 7 photographies sur des pages de 2 photographies.\n\n```python\nprint(7 // 2)\n```\n\nQu'affiche-t-il ?",
      "options": [
        {
          "text": "3.5",
          "correct": false,
          "feedback": "Erreur : c'est le résultat de `7 / 2`, la division ordinaire, qui\ndonne un flottant. La barre doublée demande le quotient entier."
        },
        {
          "text": "3",
          "correct": true,
          "feedback": "Bonne réponse : `//` donne le nombre de fois que 2 tient dans 7,\nsoit 3, en abandonnant la partie décimale."
        },
        {
          "text": "1",
          "correct": false,
          "feedback": "Erreur : c'est le résultat de `7 % 2`, qui donne le reste de la\ndivision et non le quotient."
        },
        {
          "text": "4",
          "correct": false,
          "feedback": "Erreur : arrondit 3,5 au plus proche. La division entière\nn'arrondit pas, elle abandonne la partie décimale."
        }
      ],
      "explanation": "Trois opérateurs cohabitent : `/` donne un quotient décimal, `//` le\nquotient entier, `%` le reste. Ici, 7 vaut 3 fois 2, plus 1."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "booleens",
        "and-or-not"
      ],
      "title": "Une condition avec and",
      "statement": "L'éclairage d'un escalier lit deux capteurs.\n\n```python\nsombre = True\npresence = False\nprint(sombre and presence)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`False`",
          "correct": true,
          "feedback": "Bonne réponse : `and` n'est vrai que si ses deux côtés le sont. Le\nsecond vaut ici `False`, donc le résultat aussi."
        },
        {
          "text": "`True`",
          "correct": false,
          "feedback": "Erreur : ce serait le résultat de `or`, vrai dès qu'un seul côté\nl'est. Avec `and`, les deux sont exigés en même temps."
        },
        {
          "text": "`True False`",
          "correct": false,
          "feedback": "Erreur : suppose que `print` affiche les deux variables. Il ne\nreçoit qu'une seule valeur, celle que `and` vient de calculer."
        },
        {
          "text": "`None`",
          "correct": false,
          "feedback": "Erreur : `None` marque l'absence de valeur. Une opération entre\nbooléens donne toujours `True` ou `False`."
        }
      ],
      "explanation": "`and` exige les deux conditions, `or` se contente d'une seule, `not`\ninverse. Ces trois mots suffisent à écrire la règle de commande d'un\nobjet connecté."
    },
    {
      "id": "q07",
      "difficulty": 2,
      "skills": [
        "condition",
        "and-or-not"
      ],
      "title": "La bonne condition d'alerte",
      "statement": "Un capteur sur pile doit lancer une alerte lorsque son niveau de\nbatterie est strictement inférieur à 20 et que l'appareil n'est pas en\ncharge. La variable `en_charge` vaut `True` pendant une recharge.\nQuelle ligne convient ?",
      "options": [
        {
          "text": "```python\nif niveau < 20 or not en_charge:\n```",
          "correct": false,
          "feedback": "Erreur : avec `or`, une seule des deux conditions suffit. L'alerte\npartirait pour un capteur chargé à 80 %, au seul motif qu'il n'est\npas branché."
        },
        {
          "text": "```python\nif niveau < 20 and en_charge:\n```",
          "correct": false,
          "feedback": "Erreur : cette ligne réclame l'inverse, une batterie basse pendant\nla recharge. L'alerte ne partirait jamais pour un appareil\ndébranché."
        },
        {
          "text": "```python\nif not niveau < 20 and not en_charge:\n```",
          "correct": false,
          "feedback": "Erreur : `not niveau < 20` exige un niveau d'au moins 20. La\ncondition serait vraie pour un capteur plein et débranché, jamais\npour un capteur vide."
        },
        {
          "text": "```python\nif niveau < 20 and not en_charge:\n```",
          "correct": true,
          "feedback": "Bonne réponse : `and` réunit les deux exigences et `not` inverse la\nvaleur de `en_charge`, ce qui traduit « pas en charge »."
        }
      ],
      "explanation": "Traduire une phrase en condition demande de repérer le mot de liaison,\nici « et », et la négation, ici « n'est pas ». `and` traduit le premier,\n`not` la seconde."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "input",
        "saisie",
        "types"
      ],
      "title": "Ce que renvoie input",
      "statement": "Un questionnaire en ligne réclame un nombre.\n\n```python\nreponse = input(\"Combien de messages as-tu reçus ? \")\n```\n\nL'utilisateur tape 12. Que contient `reponse` ?",
      "options": [
        {
          "text": "L'entier 12, converti par Python",
          "correct": false,
          "feedback": "Erreur : aucune conversion n'a lieu d'elle-même. Obtenir un entier\ndemande de l'écrire, avec `int(reponse)`."
        },
        {
          "text": "Le flottant 12.0, issu du clavier",
          "correct": false,
          "feedback": "Erreur : aucun point décimal n'a été saisi, et le clavier ne\nconvertit rien. La saisie arrive telle qu'elle a été tapée."
        },
        {
          "text": "La chaîne de caractères « 12 »",
          "correct": true,
          "feedback": "Bonne réponse : `input` rend toujours une chaîne, faite ici des deux\ncaractères 1 et 2, même quand la saisie ressemble à un nombre."
        },
        {
          "text": "Le booléen True, la saisie n'étant pas vide",
          "correct": false,
          "feedback": "Erreur : un booléen ne sert qu'à répondre par vrai ou faux. `input`\nrend le texte saisi, sans le juger."
        }
      ],
      "explanation": "`input` rend toujours une chaîne de caractères. Pour calculer avec la\nsaisie, il faut la convertir, par exemple avec `int(reponse)` pour\nobtenir un entier."
    },
    {
      "id": "q09",
      "difficulty": 2,
      "skills": [
        "input",
        "types",
        "erreur"
      ],
      "title": "Une addition qui échoue",
      "statement": "Ce programme s'arrête sur une erreur.\n\n```python\nreponse = input(\"Nombre de messages : \")\ntotal = reponse + 1\nprint(total)\n```\n\nPourquoi ?",
      "options": [
        {
          "text": "Parce que la variable `total` n'a pas été créée avant d'être\nutilisée",
          "correct": false,
          "feedback": "Erreur : la deuxième ligne crée justement `total`. Une variable naît\nde sa première affectation, elle n'a pas à être annoncée."
        },
        {
          "text": "Parce que `input` renvoie une chaîne, et non un entier",
          "correct": true,
          "feedback": "Bonne réponse : Python refuse d'ajouter un entier à une chaîne et\nsignale `TypeError: can only concatenate str (not \"int\") to str`.\nIl faut écrire `int(reponse) + 1`."
        },
        {
          "text": "Parce que le texte affiché par `input` contient un deux-points",
          "correct": false,
          "feedback": "Erreur : le texte passé à `input` est un simple message d'invite.\nSon contenu n'a aucun effet sur le calcul qui suit."
        },
        {
          "text": "Parce que `print` ne sait afficher que des chaînes de caractères",
          "correct": false,
          "feedback": "Erreur : `print` affiche aussi bien un entier qu'une chaîne.\nL'erreur survient avant l'affichage, sur la ligne de l'addition."
        }
      ],
      "explanation": "Le signe `+` additionne deux nombres et met deux chaînes bout à bout,\nmais ne mélange pas les deux genres. Toute saisie destinée à un calcul\ndoit d'abord être convertie."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "comparaison",
        "condition"
      ],
      "title": "Tester une égalité",
      "statement": "Un thermostat doit réagir quand la température relevée vaut exactement\n30. Quelle ligne écrit ce test ?",
      "options": [
        {
          "text": "`if temperature == 30:`",
          "correct": true,
          "feedback": "Bonne réponse : le signe doublé compare deux valeurs et donne `True`\nou `False`. C'est exactement ce qu'une condition attend."
        },
        {
          "text": "`if temperature = 30:`",
          "correct": false,
          "feedback": "Erreur : le signe simple range une valeur dans une variable, il ne\ncompare rien. Python refuse la ligne et signale une `SyntaxError`."
        },
        {
          "text": "`if temperature === 30:`",
          "correct": false,
          "feedback": "Erreur : le signe triplé vient d'un autre langage. Python ne le\nconnaît pas et signale `SyntaxError: invalid syntax`."
        },
        {
          "text": "`if egale(temperature, 30):`",
          "correct": false,
          "feedback": "Erreur : aucune fonction de ce nom n'existe. Python signale\n`NameError: name 'egale' is not defined` au moment de l'appel."
        }
      ],
      "explanation": "Un signe simple affecte, un signe doublé compare. La confusion est si\nrépandue que Python propose lui-même la correction dans son message\nd'erreur."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "condition",
        "elif"
      ],
      "title": "La branche retenue",
      "statement": "Un capteur de qualité de l'air range son relevé en trois catégories.\n\n```python\nindice = 55\nif indice < 50:\n    print(\"bon\")\nelif indice < 100:\n    print(\"moyen\")\nelse:\n    print(\"mauvais\")\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`bon`",
          "correct": false,
          "feedback": "Erreur : 55 n'est pas inférieur à 50. La première condition est\ndonc fausse et sa branche est sautée."
        },
        {
          "text": "`mauvais`",
          "correct": false,
          "feedback": "Erreur : la branche `else` ne sert que si aucune condition n'a été\ntrouvée vraie. Celle du `elif` l'est ici."
        },
        {
          "text": "`moyen`",
          "correct": true,
          "feedback": "Bonne réponse : la première condition est fausse, la deuxième est\nvraie puisque 55 est inférieur à 100. Seule sa branche s'exécute."
        },
        {
          "text": "`bon` puis `moyen`",
          "correct": false,
          "feedback": "Erreur : suppose que plusieurs branches peuvent s'exécuter. Un\nenchaînement `if`, `elif`, `else` n'en retient jamais qu'une."
        }
      ],
      "explanation": "Les conditions sont examinées de haut en bas et la première vraie\nl'emporte. Celles qui la suivent ne sont même pas évaluées."
    },
    {
      "id": "q12",
      "difficulty": 3,
      "skills": [
        "condition",
        "elif",
        "erreur"
      ],
      "title": "Une branche jamais atteinte",
      "statement": "Ce programme classe une vitesse relevée par un radar, mais il n'affiche\njamais « autoroute », quelle que soit la vitesse.\n\n```python\nvitesse = 120\nif vitesse > 50:\n    print(\"hors agglomération\")\nelif vitesse > 110:\n    print(\"autoroute\")\nelse:\n    print(\"agglomération\")\n```\n\nComment l'expliquer ?",
      "options": [
        {
          "text": "Parce qu'une instruction `elif` ne peut pas tester deux fois la\nmême variable",
          "correct": false,
          "feedback": "Erreur : rien n'interdit de tester plusieurs fois la même variable.\nC'est même le procédé habituel pour découper un intervalle."
        },
        {
          "text": "Parce que la branche `else` est choisie dès que deux conditions\nsont vraies",
          "correct": false,
          "feedback": "Erreur : `else` n'entre en jeu que si toutes les conditions sont\nfausses. La première étant vraie, `else` est écarté."
        },
        {
          "text": "Parce qu'il manque un `if` devant la seconde condition, qui n'est\njamais lue",
          "correct": false,
          "feedback": "Erreur : `elif` joue déjà le rôle d'un `if` rattaché au premier\ntest. La ligne est correctement écrite, c'est son rang qui pose\nproblème."
        },
        {
          "text": "Parce que toute vitesse dépassant 110 dépasse déjà 50, si bien que\nla première branche l'emporte",
          "correct": true,
          "feedback": "Bonne réponse : 120 est supérieur à 50, donc la première branche est\nretenue et le reste de l'enchaînement est ignoré. Il faudrait\ntester d'abord le seuil le plus élevé."
        }
      ],
      "explanation": "Dans un enchaînement de conditions, l'ordre compte autant que les\nseuils. Quand les intervalles se recouvrent, la condition la plus\nexigeante doit venir en tête."
    },
    {
      "id": "q13",
      "difficulty": 1,
      "skills": [
        "boucle-bornee",
        "for",
        "range"
      ],
      "title": "Le nombre de tours d'une boucle",
      "statement": "Un programme de test envoie plusieurs requêtes à un serveur.\n\n```python\nfor i in range(5):\n    print(\"requête envoyée\")\n```\n\nCombien de fois le message est-il affiché ?",
      "options": [
        {
          "text": "4 fois, la valeur 0 ne comptant pas pour un tour",
          "correct": false,
          "feedback": "Erreur : `range(5)` commence bel et bien à 0, et ce premier passage\nexécute la ligne comme tous les autres."
        },
        {
          "text": "5 fois, une par valeur parcourue",
          "correct": true,
          "feedback": "Bonne réponse : `range(5)` fournit 0, 1, 2, 3 et 4, soit cinq\nvaleurs, donc cinq tours de boucle."
        },
        {
          "text": "6 fois, les valeurs allant de 0 à 5 comprises",
          "correct": false,
          "feedback": "Erreur : la borne écrite est exclue. `range(5)` s'arrête à 4 et ne\nfournit jamais la valeur 5."
        },
        {
          "text": "1 fois, seul le dernier tour affichant le message",
          "correct": false,
          "feedback": "Erreur : la ligne indentée est exécutée à chaque tour, et non au\nseul dernier. C'est tout l'intérêt d'une boucle."
        }
      ],
      "explanation": "`range(n)` parcourt les entiers de 0 à `n - 1`, soit exactement `n`\nvaleurs. Le nombre de tours se lit donc directement sur l'argument."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "boucle-bornee",
        "range",
        "trace"
      ],
      "title": "Une plage de valeurs",
      "statement": "Un objet connecté effectue un relevé à chaque heure d'une plage\nhoraire.\n\n```python\ncompte = 0\nfor heure in range(2, 8):\n    compte = compte + 1\nprint(compte)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "6",
          "correct": true,
          "feedback": "Bonne réponse : la boucle parcourt 2, 3, 4, 5, 6 et 7, soit six\nvaleurs. La borne 8 est exclue."
        },
        {
          "text": "5",
          "correct": false,
          "feedback": "Erreur : retire une valeur de trop. La borne de départ, elle, est\nbien parcourue."
        },
        {
          "text": "7",
          "correct": false,
          "feedback": "Erreur : compte la borne 8, qui est pourtant exclue. La boucle\ns'arrête à 7."
        },
        {
          "text": "8",
          "correct": false,
          "feedback": "Erreur : prend la borne de fin pour le nombre de tours. Le départ\nà 2 retire deux valeurs au compte."
        }
      ],
      "explanation": "`range(a, b)` parcourt les entiers de `a` à `b - 1`, soit `b - a`\nvaleurs. Ici, 8 moins 2 donne bien six tours."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "boucle-non-bornee",
        "while",
        "trace"
      ],
      "title": "Une file de signalements traitée par lots",
      "statement": "Une équipe de modération traite 100 signalements par lots de 30.\n\n```python\nrestants = 100\ntours = 0\nwhile restants > 0:\n    restants = restants - 30\n    tours = tours + 1\nprint(tours, restants)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`3 10`",
          "correct": false,
          "feedback": "Erreur : après trois tours il reste 10 signalements, ce qui est\nstrictement supérieur à 0. La condition est encore vraie et un\nquatrième tour a lieu."
        },
        {
          "text": "`4 0`",
          "correct": false,
          "feedback": "Erreur : la soustraction retire 30 à chaque tour, sans s'ajuster au\nreste. Le quatrième tour fait passer le total sous zéro."
        },
        {
          "text": "`4 -20`",
          "correct": true,
          "feedback": "Bonne réponse : les valeurs passent par 70, 40, 10 puis -20, soit\nquatre tours. La condition n'est examinée qu'en début de tour."
        },
        {
          "text": "`5 -50`",
          "correct": false,
          "feedback": "Erreur : ajoute un tour de trop. Dès que `restants` vaut -20, la\ncondition est fausse et la boucle s'arrête."
        }
      ],
      "explanation": "Une boucle `while` examine sa condition avant chaque tour, jamais au\nmilieu. Le dernier lot est donc entamé en entier, même s'il dépasse ce\nqui restait à traiter."
    },
    {
      "id": "q16",
      "difficulty": 3,
      "skills": [
        "boucle-non-bornee",
        "while",
        "erreur"
      ],
      "title": "Une boucle qui ne s'arrête pas",
      "statement": "Ce programme doit décompter les blocs d'un fichier restant à recevoir.\n\n```python\nrestants = 10\nwhile restants > 0:\n    print(restants)\n```\n\nQue fait-il réellement ?",
      "options": [
        {
          "text": "Il affiche 10 une seule fois, puis passe à la suite",
          "correct": false,
          "feedback": "Erreur : `while` n'exécute pas son bloc une fois pour toutes. Il le\nrépète tant que la condition reste vraie."
        },
        {
          "text": "Il n'affiche rien, la condition étant fausse au départ",
          "correct": false,
          "feedback": "Erreur : 10 est bien supérieur à 0. La condition est donc vraie dès\nle premier examen et la boucle démarre."
        },
        {
          "text": "Il affiche les nombres de 10 jusqu'à 1, puis s'arrête",
          "correct": false,
          "feedback": "Erreur : c'est le comportement attendu, mais aucune ligne ne\ndiminue `restants`. Il faudrait ajouter `restants = restants - 1`\ndans le bloc."
        },
        {
          "text": "Il affiche 10 sans jamais s'arrêter",
          "correct": true,
          "feedback": "Bonne réponse : rien ne modifie `restants`, qui vaut 10 à chaque\nexamen de la condition. Le même nombre défile indéfiniment."
        }
      ],
      "explanation": "Toute boucle `while` réclame une instruction qui rapproche la condition\nde sa fin. Sans elle, le programme tourne sans terme et il faut\nl'interrompre à la main."
    },
    {
      "id": "q17",
      "difficulty": 3,
      "skills": [
        "while",
        "trace"
      ],
      "title": "Une mémoire qui se remplit",
      "statement": "Le nombre de relevés stockés par un capteur est multiplié par trois\nchaque jour.\n\n```python\nreleves = 5\njours = 0\nwhile releves < 100:\n    releves = releves * 3\n    jours = jours + 1\nprint(jours, releves)\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`3 135`",
          "correct": true,
          "feedback": "Bonne réponse : les valeurs passent par 15, 45 puis 135, soit trois\ntours. Le troisième a lieu parce que 45 est encore inférieur à 100."
        },
        {
          "text": "`2 45`",
          "correct": false,
          "feedback": "Erreur : s'arrête à 45, qui est pourtant inférieur à 100. La\ncondition reste vraie, donc un tour de plus a lieu."
        },
        {
          "text": "`3 45`",
          "correct": false,
          "feedback": "Erreur : compte trois tours mais garde la valeur du deuxième. Le\ntroisième tour multiplie encore par 3."
        },
        {
          "text": "`4 135`",
          "correct": false,
          "feedback": "Erreur : ajoute un tour de trop. Une fois `releves` à 135, la\ncondition est fausse et la boucle s'arrête."
        }
      ],
      "explanation": "Une multiplication répétée fait grimper une valeur très vite : trois\ntours suffisent à passer de 5 à 135. La boucle s'arrête au premier\nexamen où la condition devient fausse."
    },
    {
      "id": "q18",
      "difficulty": 1,
      "skills": [
        "boucle-bornee",
        "range"
      ],
      "title": "Une boucle de cinq en cinq",
      "statement": "Un programme de cartographie place un repère tous les cinq\nkilomètres le long d'un parcours.\n```python\ncompteur = 0\nfor borne in range(5, 31, 5):\n    compteur = compteur + 1\nprint(compteur)\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`6`",
          "correct": true,
          "feedback": "Bonne réponse : la boucle parcourt 5, 10, 15, 20, 25 et 30,\nsoit six valeurs, car le pas de 5 part de 5 et s'arrête avant\n31. Le compteur est donc incrémenté six fois."
        },
        {
          "text": "`5`",
          "correct": false,
          "feedback": "Erreur : tu as sans doute compté 30 divisé par 5 en oubliant\nque la borne 30 est atteinte. Les valeurs parcourues sont 5,\n10, 15, 20, 25 et 30."
        },
        {
          "text": "`26`",
          "correct": false,
          "feedback": "Erreur : tu as compté les entiers de 5 à 30 en ignorant le\npas. Le troisième nombre de `range` fixe justement l'écart\nentre deux valeurs successives."
        },
        {
          "text": "`31`",
          "correct": false,
          "feedback": "Erreur : tu as pris la borne de fin pour le nombre de tours.\nCette borne est exclue, et le pas de 5 ne retient que six\nvaleurs."
        }
      ],
      "explanation": "Dans `range(debut, fin, pas)`, la boucle part de `debut`, avance\nde `pas` à chaque tour et s'arrête avant `fin`. Ici elle prend les\nsix valeurs 5, 10, 15, 20, 25 et 30."
    },
    {
      "id": "q19",
      "difficulty": 1,
      "skills": [
        "condition",
        "elif"
      ],
      "title": "La branche d'un relevé doux",
      "statement": "Une station météo qualifie la température relevée.\n```python\ntemp = 19\nif temp < 5:\n    resultat = \"gel\"\nelif temp < 18:\n    resultat = \"frais\"\nelif temp < 25:\n    resultat = \"doux\"\nelse:\n    resultat = \"chaud\"\nprint(resultat)\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`frais`",
          "correct": false,
          "feedback": "Erreur : la condition `temp < 18` est fausse pour 19, donc\ncette branche est ignorée et l'examen se poursuit avec la\nsuivante."
        },
        {
          "text": "`doux`",
          "correct": true,
          "feedback": "Bonne réponse : 19 n'est ni inférieur à 5 ni inférieur à 18,\nmais il est inférieur à 25. C'est donc la troisième branche\nqui s'exécute."
        },
        {
          "text": "`chaud`",
          "correct": false,
          "feedback": "Erreur : la branche `else` ne s'exécute que si toutes les\nconditions précédentes sont fausses. Or `temp < 25` est vraie\npour 19."
        },
        {
          "text": "`frais` puis `doux`",
          "correct": false,
          "feedback": "Erreur : dans une suite `if`, `elif`, `else`, une seule\nbranche s'exécute. Dès qu'une condition est vraie, les autres\nne sont plus examinées."
        }
      ],
      "explanation": "Les conditions d'une suite `if`, `elif`, `else` sont examinées\ndans l'ordre, et la première qui est vraie emporte tout : son bloc\ns'exécute, les suivants sont abandonnés."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "boucle-non-bornee",
        "while"
      ],
      "title": "Un partage répété par deux",
      "statement": "Un programme réduit de moitié la taille d'une image tant qu'elle\ndépasse dix unités.\n```python\nreste = 100\ntours = 0\nwhile reste > 10:\n    reste = reste // 2\n    tours = tours + 1\nprint(tours)\n```\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`4`",
          "correct": true,
          "feedback": "Bonne réponse : `reste` prend successivement 50, 25, 12 puis\n6. À ce moment la condition devient fausse, après quatre\ntours."
        },
        {
          "text": "`3`",
          "correct": false,
          "feedback": "Erreur : tu t'es arrêté à 12, qui dépasse encore 10. Un tour\nsupplémentaire a lieu et ramène `reste` à 6."
        },
        {
          "text": "`5`",
          "correct": false,
          "feedback": "Erreur : tu as compté un tour de trop. Une fois `reste` à 6,\nla condition est fausse et la boucle s'arrête sans exécuter\nson bloc."
        },
        {
          "text": "`10`",
          "correct": false,
          "feedback": "Erreur : tu as pris le seuil de la condition pour le nombre\nde tours. Comme la valeur est divisée par deux à chaque fois,\nelle descend bien plus vite."
        }
      ],
      "explanation": "Une boucle non bornée répète son bloc tant que sa condition est\nvraie, sans qu'on sache d'avance combien de tours auront lieu.\nIci, la division entière par deux mène de 100 à 6 en quatre tours."
    },
    {
      "id": "q21",
      "difficulty": 2,
      "skills": [
        "fonction",
        "arguments"
      ],
      "title": "Un argument manquant",
      "statement": "Un programme calcule le coût d'un trajet.\n```python\ndef cout(distance, prix_km):\n    return distance * prix_km\n\nprint(cout(12))\n```\nQue se passe-t-il à l'exécution ?",
      "options": [
        {
          "text": "Python signale une erreur, car il manque un argument\nà l'appel",
          "correct": true,
          "feedback": "Bonne réponse : la fonction attend deux arguments et n'en\nreçoit qu'un. Python refuse l'appel en signalant l'argument\nabsent, ici `prix_km`."
        },
        {
          "text": "Le programme affiche `12`, la valeur du seul\nargument fourni",
          "correct": false,
          "feedback": "Erreur : la fonction ne peut pas calculer son produit sans\nconnaître le prix. Aucun calcul n'a lieu, l'appel est refusé\navant."
        },
        {
          "text": "Le programme affiche `0`, le paramètre absent\nvalant zéro",
          "correct": false,
          "feedback": "Erreur : un paramètre sans argument ne prend aucune valeur par\ndéfaut. Il faut soit fournir l'argument, soit prévoir une\nvaleur par défaut dans la définition."
        },
        {
          "text": "Le programme demande la valeur manquante à\nl'utilisateur",
          "correct": false,
          "feedback": "Erreur : une fonction ne dialogue jamais d'elle-même avec\nl'utilisateur. Elle reçoit ses arguments au moment de l'appel,\net seulement là."
        }
      ],
      "explanation": "Un appel doit fournir autant d'arguments que la définition compte\nde paramètres, dans le même ordre. Ici, `cout(12, 3)` renverrait\n36, tandis que `cout(12)` est refusé."
    },
    {
      "id": "q22",
      "difficulty": 2,
      "skills": [
        "liste",
        "parcours",
        "accumulateur"
      ],
      "title": "La somme de quatre relevés",
      "statement": "Un capteur a enregistré quatre températures.\n\n```python\nmesures = [18, 20, 22, 24]\ntotal = 0\nfor m in mesures:\n    total = total + m\nprint(total, len(mesures))\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`24 4`",
          "correct": false,
          "feedback": "Erreur : 24 est le dernier relevé, pas la somme. La variable `total`\naccumule les quatre valeurs au fil de la boucle."
        },
        {
          "text": "`84 3`",
          "correct": false,
          "feedback": "Erreur : `len` donne le nombre d'éléments, soit 4, et non le plus\ngrand indice de la liste."
        },
        {
          "text": "`84 4`",
          "correct": true,
          "feedback": "Bonne réponse : la boucle ajoute 18, 20, 22 puis 24, ce qui fait 84,\net la liste compte quatre éléments."
        },
        {
          "text": "`18 4`",
          "correct": false,
          "feedback": "Erreur : 18 est le premier relevé. La boucle ne s'arrête pas au\npremier tour, elle parcourt toute la liste."
        }
      ],
      "explanation": "Le schéma de l'accumulateur revient sans cesse : une variable mise à 0\navant la boucle, augmentée à chaque tour. Divisé par `len`, ce total\ndonne la moyenne."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "liste",
        "parcours",
        "condition"
      ],
      "title": "Un maximum jamais trouvé",
      "statement": "Ce programme cherche la température la plus haute d'une nuit d'hiver.\n\n```python\ntemperatures = [-3, -8, -1, -5]\nmaxi = 0\nfor t in temperatures:\n    if t > maxi:\n        maxi = t\nprint(maxi)\n```\n\nQu'affiche-t-il ?",
      "options": [
        {
          "text": "-1",
          "correct": false,
          "feedback": "Erreur : c'est la réponse attendue, mais le programme ne la trouve\npas. Aucun relevé ne dépasse la valeur du départ, qui reste donc en\nplace."
        },
        {
          "text": "0",
          "correct": true,
          "feedback": "Bonne réponse : les quatre relevés sont négatifs, donc aucun n'est\nsupérieur à 0. La variable `maxi` garde la valeur de son\ninitialisation."
        },
        {
          "text": "-8",
          "correct": false,
          "feedback": "Erreur : c'est le relevé le plus bas, que la comparaison `>` ne\nretient jamais. Le test cherche les valeurs supérieures."
        },
        {
          "text": "-3",
          "correct": false,
          "feedback": "Erreur : c'est le premier relevé, qui aurait servi de point de\ndépart si l'initialisation avait été mieux choisie. Elle vaut 0."
        }
      ],
      "explanation": "Initialiser un maximum à 0 revient à supposer que toutes les valeurs\nsont positives. Mieux vaut partir du premier élément de la liste, avec\n`maxi = temperatures[0]`."
    },
    {
      "id": "q24",
      "difficulty": 2,
      "skills": [
        "completer-code",
        "condition",
        "parcours"
      ],
      "title": "Compter les mesures fortes",
      "statement": "Ce programme doit afficher le nombre de mesures strictement supérieures\nà 25.\n\n```python\nmesures = [12, 31, 8, 45, 27]\ncompte = 0\nfor m in mesures:\n    ...\nprint(compte)\n```\n\nPar quoi remplacer les points de suspension ?",
      "options": [
        {
          "text": "```python\nif m > 25:\n    compte = m\n```",
          "correct": false,
          "feedback": "Erreur : range la mesure elle-même dans le compteur au lieu de\nl'augmenter. Le programme afficherait 27, la dernière mesure\nretenue."
        },
        {
          "text": "```python\nif compte > 25:\n    compte = compte + 1\n```",
          "correct": false,
          "feedback": "Erreur : teste le compteur au lieu de la mesure. Comme il part de 0\net n'atteint jamais 25, le programme afficherait 0."
        },
        {
          "text": "```python\nif m > 25:\n    compte = compte + m\n```",
          "correct": false,
          "feedback": "Erreur : ajoute la valeur de la mesure au lieu d'ajouter 1. Le\nprogramme afficherait 103, la somme des mesures retenues."
        },
        {
          "text": "```python\nif m > 25:\n    compte = compte + 1\n```",
          "correct": true,
          "feedback": "Bonne réponse : à chaque mesure supérieure à 25, le compteur avance\nde 1. Le programme affiche 3, pour 31, 45 et 27."
        }
      ],
      "explanation": "Un compteur avance toujours de 1, quelle que soit la valeur examinée.\nC'est ce qui le distingue d'un accumulateur, qui ajoute la valeur\nelle-même."
    },
    {
      "id": "q25",
      "difficulty": 2,
      "skills": [
        "fonction",
        "retour",
        "appel"
      ],
      "title": "Le volume d'un groupe de discussion",
      "statement": "Dans un groupe de discussion, chaque membre envoie le même nombre de\nmessages, et chaque message pèse 2 kilo-octets.\n\n```python\ndef volume(membres, messages):\n    return membres * messages * 2\n\nprint(volume(20, 15))\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "600",
          "correct": true,
          "feedback": "Bonne réponse : 20 membres envoient chacun 15 messages, soit 300\nmessages, et chacun pèse 2 kilo-octets."
        },
        {
          "text": "300",
          "correct": false,
          "feedback": "Erreur : oublie les 2 kilo-octets par message. C'est le nombre de\nmessages, et non le volume échangé."
        },
        {
          "text": "70",
          "correct": false,
          "feedback": "Erreur : additionne les deux paramètres au lieu de les multiplier,\navant de multiplier par 2."
        },
        {
          "text": "40",
          "correct": false,
          "feedback": "Erreur : ne tient compte que du nombre de membres. Les deux\nparamètres interviennent dans le calcul."
        }
      ],
      "explanation": "Un appel de fonction remplace les paramètres par les arguments donnés,\nexécute le corps, puis rend la valeur écrite après `return`. C'est\ncette valeur que `print` affiche."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "fonction",
        "parametres",
        "appel"
      ],
      "title": "L'ordre des arguments",
      "statement": "Cette fonction calcule l'écart entre deux bornes kilométriques.\n\n```python\ndef ecart(depart, arrivee):\n    return arrivee - depart\n\nprint(ecart(12, 5), ecart(5, 12))\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`7 -7`",
          "correct": false,
          "feedback": "Erreur : intervertit les deux résultats. Au premier appel, `depart`\nvaut 12 et `arrivee` vaut 5, donc la soustraction donne un nombre\nnégatif."
        },
        {
          "text": "`7 7`",
          "correct": false,
          "feedback": "Erreur : suppose que la fonction rend toujours un écart positif.\nElle applique la soustraction telle qu'elle est écrite, sans\ncorriger le signe."
        },
        {
          "text": "`-7 7`",
          "correct": true,
          "feedback": "Bonne réponse : les arguments prennent la place des paramètres dans\nl'ordre, d'où 5 moins 12 au premier appel et 12 moins 5 au second."
        },
        {
          "text": "`-7 -7`",
          "correct": false,
          "feedback": "Erreur : donne le même signe aux deux appels. Échanger les\narguments change bien le signe du résultat."
        }
      ],
      "explanation": "Les arguments d'un appel prennent la place des paramètres dans l'ordre\noù ils sont écrits. Deux appels aux mêmes valeurs rangées autrement ne\ndonnent donc pas le même résultat."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "fonction",
        "retour",
        "print"
      ],
      "title": "Afficher ou renvoyer",
      "statement": "Ce programme emploie une fonction qui double une valeur.\n\n```python\ndef doubler(valeur):\n    print(valeur * 2)\n\nresultat = doubler(5)\nprint(resultat)\n```\n\nQu'affiche-t-il ?",
      "options": [
        {
          "text": "`10` puis `10`",
          "correct": false,
          "feedback": "Erreur : suppose que la fonction rend ce qu'elle affiche. Sans\n`return`, l'appel ne rapporte aucune valeur utilisable."
        },
        {
          "text": "`10` puis `None`",
          "correct": true,
          "feedback": "Bonne réponse : la fonction affiche 10, puis `resultat` reçoit\n`None`, faute de `return`. La dernière ligne montre cette absence\nde valeur."
        },
        {
          "text": "`10` seulement",
          "correct": false,
          "feedback": "Erreur : oublie la dernière ligne, qui affiche `resultat`. Elle\ns'exécute et montre ce que l'appel a rapporté."
        },
        {
          "text": "`None` puis `10`",
          "correct": false,
          "feedback": "Erreur : inverse l'ordre. L'affichage écrit dans le corps de la\nfonction a lieu pendant l'appel, donc avant la dernière ligne."
        }
      ],
      "explanation": "Afficher et renvoyer sont deux actions distinctes. `print` montre une\nvaleur à l'écran, `return` la rend au programme qui a fait l'appel. Une\nfonction sans `return` rend `None`."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "fonction",
        "parametres",
        "retour"
      ],
      "title": "Un résultat qui se perd",
      "statement": "Ce programme veut doubler la largeur d'une image.\n\n```python\ndef agrandir(valeur):\n    valeur = valeur * 2\n    return valeur\n\nlargeur = 640\nagrandir(largeur)\nprint(largeur)\n```\n\nQu'affiche-t-il ?",
      "options": [
        {
          "text": "1280",
          "correct": false,
          "feedback": "Erreur : c'est la valeur calculée par la fonction, mais personne ne\nla recueille. Il faudrait écrire `largeur = agrandir(largeur)`."
        },
        {
          "text": "None",
          "correct": false,
          "feedback": "Erreur : la fonction contient bien un `return`, et de toute façon la\ndernière ligne affiche `largeur`, pas le résultat de l'appel."
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : la variable n'est jamais remise à zéro. Elle garde la\nvaleur reçue au moment de sa création."
        },
        {
          "text": "640",
          "correct": true,
          "feedback": "Bonne réponse : le paramètre `valeur` est une variable propre à la\nfonction. La modifier ne change rien à `largeur`, et le résultat\nrendu n'est rangé nulle part."
        }
      ],
      "explanation": "Une fonction travaille sur ses propres variables. Pour conserver ce\nqu'elle calcule, il faut ranger la valeur rendue dans une variable au\nmoment de l'appel."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "fonction",
        "booleens",
        "retour"
      ],
      "title": "Une fonction qui renvoie un booléen",
      "statement": "Une moyenne n'a de sens qu'à partir de trois relevés.\n\n```python\ndef assez(liste):\n    return len(liste) >= 3\n\nmatin = [18, 21]\njournee = [18, 21, 22, 25]\nprint(assez(matin), assez(journee), assez(matin) or assez(journee))\n```\n\nQu'affiche ce programme ?",
      "options": [
        {
          "text": "`False True True`",
          "correct": true,
          "feedback": "Bonne réponse : `matin` compte 2 éléments et `journee` en compte 4.\nLe `or` est vrai dès qu'un seul des deux appels l'est."
        },
        {
          "text": "`False True False`",
          "correct": false,
          "feedback": "Erreur : `or` est vrai dès qu'une des deux valeurs l'est, et\nl'appel sur `journee` rend justement `True`."
        },
        {
          "text": "`True False True`",
          "correct": false,
          "feedback": "Erreur : intervertit les deux listes. C'est `journee`, avec ses\nquatre éléments, qui atteint le seuil de trois."
        },
        {
          "text": "`False False True`",
          "correct": false,
          "feedback": "Erreur : `journee` compte quatre éléments, ce qui est bien\nsupérieur ou égal à 3. Le deuxième appel rend `True`."
        }
      ],
      "explanation": "Une fonction peut rendre un booléen, qui s'emploie alors comme une\ncondition ou se combine avec `and`, `or` et `not`. La comparaison\nécrite après `return` est évaluée avant d'être rendue."
    },
    {
      "id": "q30",
      "difficulty": 3,
      "skills": [
        "fonction",
        "appel",
        "erreur"
      ],
      "title": "Un appel incomplet",
      "statement": "Ce programme s'arrête sur une erreur au moment de l'appel.\n\n```python\ndef surface(largeur, hauteur):\n    return largeur * hauteur\n\nprint(surface(12))\n```\n\nQue reproche Python à ce programme ?",
      "options": [
        {
          "text": "La fonction est appelée avant d'avoir été définie dans le fichier",
          "correct": false,
          "feedback": "Erreur : la définition est bien écrite avant l'appel. Ce reproche\nvaudrait pour un appel placé plus haut dans le fichier, et Python\nsignalerait alors une `NameError`."
        },
        {
          "text": "La valeur 12 n'est pas d'un type admis pour une multiplication",
          "correct": false,
          "feedback": "Erreur : 12 est un entier, parfaitement multipliable. Le calcul n'a\nmême pas lieu, l'appel échouant avant."
        },
        {
          "text": "La fonction attend deux paramètres, or un seul argument est fourni",
          "correct": true,
          "feedback": "Bonne réponse : Python signale `TypeError: surface() missing 1\nrequired positional argument: 'hauteur'`. Il faut fournir autant\nd'arguments que de paramètres."
        },
        {
          "text": "La fonction ne renvoie rien, donc `print` n'a rien à afficher",
          "correct": false,
          "feedback": "Erreur : la fonction contient un `return` et rendrait bien une\nvaleur si l'appel aboutissait. L'erreur survient avant."
        }
      ],
      "explanation": "Un appel doit fournir une valeur pour chaque paramètre, dans l'ordre.\nLe message d'erreur nomme même le paramètre qui manque, ici `hauteur`."
    }
  ]
}