{
  "chapter": {
    "id": "programmation-fonctionnelle",
    "level": "terminale",
    "theme": "Langages et programmation",
    "title": "Programmation fonctionnelle",
    "description": "Paradigme fonctionnel, fonctions pures, immuabilité, fonctions\nde première classe et d'ordre supérieur (map, filter, reduce),\nexpressions lambda, récursivité, comparaison avec le paradigme\nimpératif.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "paradigme"
      ],
      "title": "Paradigme fonctionnel",
      "statement": "Quelle est l'idée centrale du **paradigme fonctionnel** ?",
      "options": [
        {
          "text": "Programmer en composant des fonctions, en évitant les modifications d'état (effets de bord)",
          "correct": true,
          "feedback": "Bonne réponse : la programmation fonctionnelle\nprivilégie les fonctions sans effet de bord\n(« fonctions pures »), l'immutabilité des données et\nla composition de fonctions. Cela rend le code plus\nprévisible et plus facile à raisonner.\n"
        },
        {
          "text": "Programmer uniquement avec des boucles `for`",
          "correct": false,
          "feedback": "Erreur : la programmation fonctionnelle remplace\nplutôt les boucles par de la récursivité ou des\nfonctions d'ordre supérieur.\n"
        },
        {
          "text": "Programmer en suivant l'ordre exact des instructions ligne par ligne",
          "correct": false,
          "feedback": "Erreur : c'est le paradigme **impératif**, dont la\nprogrammation fonctionnelle est précisément une\nalternative.\n"
        },
        {
          "text": "Programmer en utilisant uniquement des objets et des classes",
          "correct": false,
          "feedback": "Erreur : c'est le paradigme **orienté objet**, pas\nfonctionnel.\n"
        }
      ],
      "explanation": "Origines : le lambda-calcul de Church ($1936$) et le\nlangage Lisp ($1958$). Aujourd'hui, des langages\npurement fonctionnels (Haskell, OCaml) coexistent avec\ndes langages multi-paradigmes (Python, JavaScript)."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "fonction-pure"
      ],
      "title": "Fonction pure",
      "statement": "Qu'est-ce qu'une **fonction pure** ?",
      "options": [
        {
          "text": "Une fonction qui, pour les mêmes arguments, retourne toujours le même résultat et n'a aucun effet de bord (modification de variable globale, écriture dans un fichier, etc.)",
          "correct": true,
          "feedback": "Bonne réponse : la pureté implique la prévisibilité\ntotale et la facilité de test. C'est aussi ce qui\npermet la mémoïsation transparente.\n"
        },
        {
          "text": "Une fonction qui modifie ses arguments",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse d'une fonction pure. Modifier\nses arguments est un effet de bord, ce qui rend la\nfonction impure.\n"
        },
        {
          "text": "Une fonction écrite uniquement en Python",
          "correct": false,
          "feedback": "Erreur : la pureté est indépendante du langage.\n"
        },
        {
          "text": "Une fonction qui ne contient aucun bug",
          "correct": false,
          "feedback": "Erreur : « pure » est un terme technique précis,\npas un jugement de qualité.\n"
        }
      ],
      "explanation": "Fonction pure typique : `def carre(x): return x * x`.\nFonction **impure** : `def ajoute_a_liste(L, x): L.append(x)`\nmodifie son argument, ce qui est un effet de bord."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "immuabilite"
      ],
      "title": "Immutabilité",
      "statement": "Que signifie qu'une donnée est **immuable** ?",
      "options": [
        {
          "text": "Elle est stockée sur le disque dur",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec le stockage.\n"
        },
        {
          "text": "Elle est protégée par un mot de passe",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec la sécurité. C'est une\npropriété structurelle de la donnée.\n"
        },
        {
          "text": "Elle ne peut pas être modifiée après sa création",
          "correct": true,
          "feedback": "Bonne réponse : pour modifier, on crée une nouvelle\ndonnée à partir de l'ancienne, sans toucher à\nl'originale. Exemple en Python : les chaînes, les\ntuples sont immuables.\n"
        },
        {
          "text": "Elle ne peut pas être lue",
          "correct": false,
          "feedback": "Erreur : une donnée immuable peut être lue\nlibrement, c'est seulement la modification qui est\ninterdite.\n"
        }
      ],
      "explanation": "L'immutabilité a plusieurs avantages : pas de\nmodifications inattendues, partage sûr entre threads,\npossibilité de cacher les valeurs (les chaînes de\ncaractères en Python sont parfois cachées en interne)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "fonction-premiere-classe"
      ],
      "title": "Fonctions de première classe",
      "statement": "Que signifie « les fonctions sont des objets de première\nclasse » dans un langage ?",
      "options": [
        {
          "text": "Elles sont les plus rapides du programme",
          "correct": false,
          "feedback": "Erreur : pas de lien avec la performance.\n"
        },
        {
          "text": "Elles peuvent être passées en paramètre, retournées par d'autres fonctions, et stockées dans des variables, comme n'importe quelle autre valeur",
          "correct": true,
          "feedback": "Bonne réponse : c'est une caractéristique majeure\ndes langages fonctionnels. Python, JavaScript,\nOCaml, Haskell traitent les fonctions ainsi. C++,\nJava l'ont ajouté plus tardivement.\n"
        },
        {
          "text": "Elles sont écrites en majuscules",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec la casse.\n"
        },
        {
          "text": "Elles sont les premières instructions du programme",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec l'ordre des instructions.\nLe terme est conceptuel.\n"
        }
      ],
      "explanation": "Cette propriété rend possibles les fonctions d'ordre\nsupérieur (`map`, `filter`, `reduce`), qui prennent ou\nrenvoient des fonctions, et qui sont au cœur du\nparadigme fonctionnel."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "lambda"
      ],
      "title": "Expression lambda",
      "statement": "En Python, qu'est-ce qu'une **expression lambda** ?",
      "options": [
        {
          "text": "Une variable globale",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec les variables globales.\n"
        },
        {
          "text": "Une fonction anonyme définie en une seule ligne",
          "correct": true,
          "feedback": "Bonne réponse : `lambda x: x * x` est une fonction\nanonyme qui prend `x` et renvoie son carré.\nÉquivalent à `def f(x): return x * x` mais sans\nnom, utilisable directement comme expression.\n"
        },
        {
          "text": "Un type de boucle",
          "correct": false,
          "feedback": "Erreur : ce n'est pas une boucle. C'est une fonction\nanonyme.\n"
        },
        {
          "text": "Une instruction de débogage",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec le débogage.\n"
        }
      ],
      "explanation": "Le mot `lambda` vient du lambda-calcul de Church. En\nPython, les `lambda` sont limitées à une seule\nexpression. Pour une fonction plus complexe, on utilise\n`def`."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "map"
      ],
      "title": "Fonction map",
      "statement": "Que fait la fonction `map(f, L)` en Python ?",
      "options": [
        {
          "text": "Elle filtre la liste selon un critère",
          "correct": false,
          "feedback": "Erreur : c'est le rôle de `filter`, pas `map`.\n"
        },
        {
          "text": "Elle réduit la liste à une seule valeur",
          "correct": false,
          "feedback": "Erreur : c'est le rôle de `reduce`, pas `map`.\n"
        },
        {
          "text": "Elle applique la fonction `f` à chaque élément de `L` et renvoie un itérateur des résultats",
          "correct": true,
          "feedback": "Bonne réponse : pour obtenir une liste, on fait\n`list(map(f, L))`. Par exemple,\n`list(map(lambda x: x * 2, [1, 2, 3]))` renvoie\n`[2, 4, 6]`.\n"
        },
        {
          "text": "Elle modifie la liste `L` en y appliquant `f` directement",
          "correct": false,
          "feedback": "Erreur : `map` ne modifie pas la liste d'origine.\nElle renvoie un nouvel itérateur (fonction pure).\n"
        }
      ],
      "explanation": "`map`, `filter` et `reduce` (dans `functools`) sont les\ntrois fonctions d'ordre supérieur classiques en\nprogrammation fonctionnelle. Elles correspondent à des\nschémas d'usage très courants."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "filter"
      ],
      "title": "Fonction filter",
      "statement": "Que fait `filter(p, L)` en Python ?",
      "options": [
        {
          "text": "Elle compte le nombre d'éléments dans `L`",
          "correct": false,
          "feedback": "Erreur : pour compter, on utilise `len` ou `sum`.\n"
        },
        {
          "text": "Elle conserve uniquement les éléments de `L` pour lesquels `p(element)` est vrai",
          "correct": true,
          "feedback": "Bonne réponse : par exemple,\n`list(filter(lambda x: x > 0, [-1, 0, 1, 2]))`\nrenvoie `[1, 2]`. Les éléments pour lesquels `p`\nrenvoie `True` sont conservés.\n"
        },
        {
          "text": "Elle modifie chaque élément de `L`",
          "correct": false,
          "feedback": "Erreur : c'est plutôt le rôle de `map`.\n"
        },
        {
          "text": "Elle renvoie une liste vide",
          "correct": false,
          "feedback": "Erreur : `filter` renvoie un itérateur des éléments\nconservés, pas vide.\n"
        }
      ],
      "explanation": "Alternative idiomatique en Python avec\ncompréhension de liste :\n`[x for x in L if p(x)]`. Souvent plus lisible que\n`filter` en Python."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "reduce"
      ],
      "title": "Fonction reduce",
      "statement": "Quel est le rôle de `reduce(f, L)` (du module `functools`) ?",
      "options": [
        {
          "text": "Trier la liste",
          "correct": false,
          "feedback": "Erreur : c'est le rôle de `sorted` ou `list.sort`.\n"
        },
        {
          "text": "Combiner les éléments de `L` deux par deux à l'aide de `f` jusqu'à obtenir une seule valeur",
          "correct": true,
          "feedback": "Bonne réponse : par exemple,\n`reduce(lambda a, b: a + b, [1, 2, 3, 4])` calcule\n$((1 + 2) + 3) + 4 = 10$. C'est l'agrégation par\nexcellence.\n"
        },
        {
          "text": "Faire une boucle",
          "correct": false,
          "feedback": "Erreur : `reduce` est une **abstraction** de boucle\nd'agrégation, mais ce n'est pas une boucle au sens\nsyntaxique.\n"
        },
        {
          "text": "Réduire la taille d'une liste en supprimant les doublons",
          "correct": false,
          "feedback": "Erreur : ce n'est pas le rôle de `reduce`. Pour les\ndoublons, on utiliserait `set(L)`.\n"
        }
      ],
      "explanation": "`reduce` (aussi appelé `fold`) est très puissant : il\ncapture les schémas de somme, produit, maximum,\nconcaténation, etc. En Python, il est moins idiomatique\nqu'en Haskell, et souvent on lui préfère `sum`, `max`,\netc."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "composition"
      ],
      "title": "Composition de fonctions",
      "statement": "Que signifie « composer deux fonctions $f$ et $g$ » ?",
      "options": [
        {
          "text": "Les exécuter en parallèle",
          "correct": false,
          "feedback": "Erreur : la composition est séquentielle (l'une\naprès l'autre).\n"
        },
        {
          "text": "Créer une nouvelle fonction qui applique d'abord $g$, puis $f$ au résultat (notée $f \\circ g$)",
          "correct": true,
          "feedback": "Bonne réponse : $(f \\circ g)(x) = f(g(x))$. La\ncomposition de fonctions est la brique de base de la\nprogrammation fonctionnelle, qui voit les programmes\ncomme des chaînes de transformations.\n"
        },
        {
          "text": "Les écrire dans le même fichier",
          "correct": false,
          "feedback": "Erreur : aucun rapport. La composition est une\nopération mathématique.\n"
        },
        {
          "text": "Les fusionner en une seule grosse fonction",
          "correct": false,
          "feedback": "Erreur : la composition crée une nouvelle fonction\nmais ne modifie pas $f$ ni $g$ ; elles restent\nutilisables séparément.\n"
        }
      ],
      "explanation": "En programmation fonctionnelle, on construit des\nprogrammes complexes en composant des fonctions\nsimples. Ce style favorise la **réutilisation** et la\n**modularité**."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "recursion"
      ],
      "title": "Récursivité en programmation fonctionnelle",
      "statement": "Pourquoi la **récursivité** est-elle privilégiée à la\nboucle dans le paradigme fonctionnel ?",
      "options": [
        {
          "text": "Parce qu'elle est plus rapide",
          "correct": false,
          "feedback": "Erreur : la récursivité est généralement aussi\nrapide ou plus lente que les boucles, mais ce\nn'est pas la raison de son emploi.\n"
        },
        {
          "text": "Parce qu'une boucle nécessite la modification d'une variable de compteur, ce qui contredit l'idéal d'immutabilité",
          "correct": true,
          "feedback": "Bonne réponse : `for i in range(n)` modifie\nimplicitement `i`. Une approche purement\nfonctionnelle évite cela en utilisant des appels\nrécursifs avec de nouveaux paramètres.\n"
        },
        {
          "text": "Parce que les boucles n'existent pas en Python",
          "correct": false,
          "feedback": "Erreur : Python a des boucles. C'est par\n**convention** ou par **style** qu'on les évite en\nprogrammation fonctionnelle.\n"
        },
        {
          "text": "Parce que c'est la mode",
          "correct": false,
          "feedback": "Erreur : c'est une raison structurelle, pas un effet\nde mode.\n"
        }
      ],
      "explanation": "Les langages fonctionnels purs (Haskell) n'ont même pas\nde boucles : tout est exprimé par récursivité ou par\nfonctions d'ordre supérieur."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "pure-impure"
      ],
      "title": "Identifier une fonction pure",
      "statement": "Parmi les fonctions Python suivantes, laquelle est\n**pure** ?\n\n```\ndef f1(x): return x + 1\ndef f2(x): print(x); return x\ndef f3(L, x): L.append(x); return L\ndef f4(): return random.random()\n```",
      "options": [
        {
          "text": "`f1` et `f4`",
          "correct": false,
          "feedback": "Erreur : `f4` n'est pas pure. Pour les mêmes\narguments (aucun ici), elle renvoie des résultats\ndifférents (génération aléatoire).\n"
        },
        {
          "text": "`f1` et `f2`",
          "correct": false,
          "feedback": "Erreur : `f2` appelle `print`, ce qui est un effet\nde bord (interaction avec le terminal).\n"
        },
        {
          "text": "`f1` uniquement",
          "correct": true,
          "feedback": "Bonne réponse : `f1` ne modifie rien et retourne\ntoujours la même valeur pour le même argument. `f2`\na un effet de bord (`print`), `f3` modifie son\nargument, `f4` retourne un résultat différent à\nchaque appel.\n"
        },
        {
          "text": "Toutes",
          "correct": false,
          "feedback": "Erreur : seule `f1` respecte les deux conditions de\npureté.\n"
        }
      ],
      "explanation": "Les effets de bord typiques : modifier une variable\nglobale, écrire dans un fichier, appeler `print`,\nutiliser le hasard, lire l'horloge système, etc. Toutes\nces opérations rendent une fonction impure."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "lambda",
        "exemple"
      ],
      "title": "Lambda avec map",
      "statement": "Que renvoie l'expression Python suivante ?\n\n```\nlist(map(lambda x: x ** 2, [1, 2, 3, 4]))\n```",
      "options": [
        {
          "text": "`[1, 4, 9, 16]`",
          "correct": true,
          "feedback": "Bonne réponse : la lambda calcule le carré, et `map`\nl'applique à chaque élément. On obtient\n$[1^2, 2^2, 3^2, 4^2] = [1, 4, 9, 16]$.\n"
        },
        {
          "text": "`[1, 2, 3, 4]`",
          "correct": false,
          "feedback": "Erreur : `map` applique la fonction à chaque\nélément, donc transforme la liste.\n"
        },
        {
          "text": "`[1, 16]`",
          "correct": false,
          "feedback": "Erreur : `map` applique à **chaque** élément, on\nn'en perd pas en chemin.\n"
        },
        {
          "text": "`[2, 4, 6, 8]`",
          "correct": false,
          "feedback": "Erreur : ce serait `lambda x: x * 2`, pas `x ** 2`.\n`**` est l'opérateur **puissance** en Python.\n"
        }
      ],
      "explanation": "Cette construction est très idiomatique en\nprogrammation fonctionnelle. En Python, on peut aussi\nécrire `[x ** 2 for x in [1, 2, 3, 4]]`, qui est\nsouvent considéré plus lisible."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "filter",
        "exemple"
      ],
      "title": "Filter pour les nombres pairs",
      "statement": "Que renvoie cette expression ?\n\n```\nlist(filter(lambda x: x % 2 == 0, range(10)))\n```",
      "options": [
        {
          "text": "`[]`",
          "correct": false,
          "feedback": "Erreur : il y a bien des nombres pairs dans\n`range(10)`.\n"
        },
        {
          "text": "`[1, 3, 5, 7, 9]`",
          "correct": false,
          "feedback": "Erreur : `x % 2 == 0` teste si `x` est **pair**.\nDonc on garde les pairs, pas les impairs.\n"
        },
        {
          "text": "`[0, 1, 2, ..., 9]`",
          "correct": false,
          "feedback": "Erreur : ce serait `range(10)` complet, sans filtre.\n"
        },
        {
          "text": "`[0, 2, 4, 6, 8]`",
          "correct": true,
          "feedback": "Bonne réponse : `filter` conserve les éléments pour\nlesquels la condition est vraie. `range(10)` produit\n$0, 1, \\ldots, 9$ ; on garde les pairs $0, 2, 4, 6, 8$.\n"
        }
      ],
      "explanation": "Équivalent en compréhension de liste :\n`[x for x in range(10) if x % 2 == 0]`. Les deux\nformes sont fréquentes en Python."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "comprehension"
      ],
      "title": "Compréhension de liste",
      "statement": "Quelle est la **compréhension de liste** Python équivalente\nà `list(map(lambda x: x * 2, filter(lambda x: x > 0, L)))` ?",
      "options": [
        {
          "text": "`[x > 0 * 2 for x in L]`",
          "correct": false,
          "feedback": "Erreur : syntaxiquement valide mais sémantiquement\nfaux. La condition est ailleurs.\n"
        },
        {
          "text": "`[x for x in L * 2]`",
          "correct": false,
          "feedback": "Erreur : cela multiplie la liste par $2$ (deux\nrépétitions de la liste), pas chaque élément. De\nplus, le filtre est absent.\n"
        },
        {
          "text": "`[L if x > 0 for x in L]`",
          "correct": false,
          "feedback": "Erreur : syntaxe incorrecte de compréhension.\n"
        },
        {
          "text": "`[x * 2 for x in L if x > 0]`",
          "correct": true,
          "feedback": "Bonne réponse : on combine map et filter dans une\nseule compréhension. Cette forme est plus lisible\net idiomatique en Python.\n"
        }
      ],
      "explanation": "Les compréhensions de liste, inspirées des langages\nfonctionnels, s'intègrent naturellement à Python.\nElles combinent élégamment `map` et `filter` en\nune seule expression. Pour réduire une liste à une\nvaleur unique, on utilise `sum`, `max`, `min`, etc."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "haut-niveau"
      ],
      "title": "Fonction d'ordre supérieur",
      "statement": "Quelle est la définition d'une **fonction d'ordre\nsupérieur** ?",
      "options": [
        {
          "text": "Une fonction définie au niveau global",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec la portée.\n"
        },
        {
          "text": "Une fonction qui appelle d'autres fonctions",
          "correct": false,
          "feedback": "Erreur : la plupart des fonctions appellent d'autres\nfonctions. Le critère est plus précis.\n"
        },
        {
          "text": "Une fonction qui prend une fonction en argument et/ou en retourne une",
          "correct": true,
          "feedback": "Bonne réponse : `map`, `filter`, `reduce`, `sorted`\n(avec `key`), les décorateurs sont des fonctions\nd'ordre supérieur. Elles manipulent les fonctions\ncomme n'importe quelle autre valeur.\n"
        },
        {
          "text": "Une fonction très complexe",
          "correct": false,
          "feedback": "Erreur : « ordre supérieur » est un terme technique,\npas un jugement de complexité.\n"
        }
      ],
      "explanation": "Les fonctions d'ordre supérieur sont la signature du\nparadigme fonctionnel. Elles permettent l'abstraction\ndu comportement : par exemple, `sorted(L, key=f)`\nlaisse l'utilisateur définir comment trier sans changer\nl'algorithme de tri."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "memoisation"
      ],
      "title": "Mémoïsation et fonctions pures",
      "statement": "Pourquoi la mémoïsation est-elle particulièrement adaptée\naux fonctions pures ?",
      "options": [
        {
          "text": "Une fonction pure renvoie toujours la même valeur pour les mêmes arguments, donc le cache est toujours fiable",
          "correct": true,
          "feedback": "Bonne réponse : si la fonction n'était pas pure (par\nexemple, dépendait de l'horloge), le cache pourrait\nrenvoyer une valeur obsolète. La pureté garantit que\nle résultat est invariable.\n"
        },
        {
          "text": "La mémoïsation modifie la fonction",
          "correct": false,
          "feedback": "Erreur : la mémoïsation enveloppe la fonction sans\nla modifier. C'est la pureté qui rend cette\nenveloppe correcte.\n"
        },
        {
          "text": "Les fonctions pures sont plus rapides",
          "correct": false,
          "feedback": "Erreur : ce n'est pas une question de vitesse\nintrinsèque, mais de **correction** de la mémoïsation.\n"
        },
        {
          "text": "Les fonctions pures n'ont pas besoin de mémoire",
          "correct": false,
          "feedback": "Erreur : la mémoïsation **utilise** précisément de\nla mémoire (un cache). C'est la fiabilité de cette\nmise en cache qui dépend de la pureté.\n"
        }
      ],
      "explanation": "C'est aussi pour cela que des langages purs comme\nHaskell peuvent automatiquement mémoïser des fonctions\nsans surprise. Avec Python, il faut être prudent,\n`lru_cache` ne fonctionne correctement qu'avec des\nfonctions pures."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "reduce",
        "calcul"
      ],
      "title": "Calcul avec reduce",
      "statement": "Que calcule cette expression ?\n\n```\nfrom functools import reduce\nreduce(lambda a, b: a * b, [1, 2, 3, 4, 5])\n```",
      "options": [
        {
          "text": "Le maximum ($5$)",
          "correct": false,
          "feedback": "Erreur : ce serait\n`reduce(lambda a, b: a if a > b else b, ...)`.\n"
        },
        {
          "text": "La somme des éléments ($15$)",
          "correct": false,
          "feedback": "Erreur : la lambda multiplie (`*`), pas additionne.\n"
        },
        {
          "text": "La longueur ($5$)",
          "correct": false,
          "feedback": "Erreur : ce serait `len([...])`, pas `reduce`.\n"
        },
        {
          "text": "Le produit des éléments ($120$)",
          "correct": true,
          "feedback": "Bonne réponse : on calcule\n$((((1 \\cdot 2) \\cdot 3) \\cdot 4) \\cdot 5) = 120$,\nautrement dit $5! = 120$. C'est la **factorielle**.\n"
        }
      ],
      "explanation": "`reduce` exprime des agrégations très variées : somme,\nproduit, max, min, concaténation. C'est l'opération\n« repli » (*fold*) en programmation fonctionnelle."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "referentielle-transparence"
      ],
      "title": "Transparence référentielle",
      "statement": "Que désigne la **transparence référentielle** d'une\nexpression ?",
      "options": [
        {
          "text": "L'expression contient des commentaires",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec les commentaires.\n"
        },
        {
          "text": "L'expression est écrite en blanc sur fond noir",
          "correct": false,
          "feedback": "Erreur : sigle inventé.\n"
        },
        {
          "text": "L'expression peut être remplacée par sa valeur sans changer le comportement du programme",
          "correct": true,
          "feedback": "Bonne réponse : si `f(3)` renvoie toujours $9$, alors\non peut remplacer `f(3)` par $9$ partout dans le\ncode, sans rien casser. C'est une propriété forte\ndes fonctions pures.\n"
        },
        {
          "text": "L'expression renvoie une référence vers un objet",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec les références ou les\npointeurs.\n"
        }
      ],
      "explanation": "La transparence référentielle est synonyme de pureté.\nElle facilite le raisonnement sur le code : on peut\nremplacer mentalement une expression par sa valeur\npour comprendre un programme."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "imperatif-vs-fonctionnel"
      ],
      "title": "Impératif vs fonctionnel",
      "statement": "Quel est l'équivalent **fonctionnel** du code impératif\nsuivant ?\n\n```\ntotal = 0\nfor x in L:\n    if x > 0:\n        total += x ** 2\n```",
      "options": [
        {
          "text": "`L.sum() if L > 0`",
          "correct": false,
          "feedback": "Erreur : syntaxe incorrecte. Les listes Python n'ont\npas de méthode `sum`.\n"
        },
        {
          "text": "`sum(x  2 for x in L if x > 0)`",
          "correct": true,
          "feedback": "Bonne réponse : on combine `filter` (`if x > 0`),\n`map` (`x ** 2`) et `reduce` (`sum`) en une seule\nexpression. Pas de variable mutable, pas de boucle\nexplicite.\n"
        },
        {
          "text": "`reduce(L)`",
          "correct": false,
          "feedback": "Erreur : `reduce` requiert une fonction et une\nséquence, pas seulement une séquence.\n"
        },
        {
          "text": "`for x in L: total += x  2`",
          "correct": false,
          "feedback": "Erreur : c'est une boucle impérative qui modifie\n`total`. On veut s'en abstraire.\n"
        }
      ],
      "explanation": "Le code fonctionnel est plus court, sans variable\nmodifiable, et exprime directement l'**intention**\n(« la somme des carrés des positifs ») plutôt que la\n**mécanique** d'accumulation."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "recursion-fonctionnelle"
      ],
      "title": "Récursivité sans boucle",
      "statement": "Quelle est l'écriture **récursive pure** de la fonction\nqui calcule la somme des éléments d'une liste ?",
      "options": [
        {
          "text": "```\ndef somme(L):\n    return sum(L)\n```\n",
          "correct": false,
          "feedback": "Erreur : `sum` est correcte mais elle ne montre pas\nla récursivité. Et elle utilise en interne une\nboucle impérative.\n"
        },
        {
          "text": "```\ndef somme(L):\n    total = 0\n    for x in L: total += x\n    return total\n```\n",
          "correct": false,
          "feedback": "Erreur : c'est l'écriture **impérative**. Elle\nutilise une variable mutable (`total`), contraire à\nl'idéal fonctionnel.\n"
        },
        {
          "text": "```\ndef somme(L):\n    if L == []:\n        return 0\n    return L[0] + somme(L[1:])\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : aucune variable mutable, aucune\nboucle. Juste un cas de base et un appel récursif.\nC'est l'archétype du style fonctionnel pur.\n"
        },
        {
          "text": "```\ndef somme(L):\n    return reduce(lambda a, b: a + b, L)\n```\n",
          "correct": false,
          "feedback": "Erreur : c'est aussi correct et fonctionnel, mais ce\nn'est pas une définition **récursive** au sens\nstrict. La question demandait la récursivité\nexplicite.\n"
        }
      ],
      "explanation": "Cette implémentation récursive est belle et simple,\nmais en Python elle est limitée par la pile (~$1000$\néléments). Pour des listes plus longues, on combine\navec une stratégie (récursivité terminale, division\npar moitié, ou plus simplement `sum`)."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "paradigme-mixte"
      ],
      "title": "Mélange de paradigmes",
      "statement": "Un programme Python utilise une boucle `for` pour\nparcourir une liste, puis appelle `sorted` avec une\nfonction lambda comme critère. Combien de paradigmes\nde programmation différents ce code combine-t-il ?",
      "options": [
        {
          "text": "Aucun, ce n'est pas un vrai programme",
          "correct": false,
          "feedback": "Erreur : c'est bien un programme valide, et il\nutilise plusieurs paradigmes.\n"
        },
        {
          "text": "Deux : impératif (la boucle `for`) et fonctionnel (le tri par lambda)",
          "correct": true,
          "feedback": "Bonne réponse : c'est exactement ce que dit le\nprogramme officiel : « Dans un même programme,\non peut utiliser des paradigmes différents. »\n"
        },
        {
          "text": "Trois : impératif, fonctionnel et objet",
          "correct": false,
          "feedback": "Erreur : aucune classe ni objet n'est défini\ndans cet exemple, donc le paradigme objet n'est\npas mobilisé.\n"
        },
        {
          "text": "Un seul, l'impératif",
          "correct": false,
          "feedback": "Erreur : la boucle `for` est bien impérative,\nmais l'usage de `sorted` avec une lambda relève\ndu style fonctionnel.\n"
        }
      ],
      "explanation": "Python est un langage **multi-paradigme** : il\npermet d'écrire dans le style impératif (boucles,\naffectations), fonctionnel (lambda, `map`,\ncompréhensions) ou objet (classes), et de\ncombiner librement les trois selon les besoins."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "comparaison-imperatif-fonctionnel"
      ],
      "title": "Calculer la somme des carrés",
      "statement": "On veut calculer la somme des carrés des entiers\nd'une liste `nombres`. Quel code suit le **style\nfonctionnel** ?",
      "options": [
        {
          "text": "```python\ntotal = 0\nfor n in nombres:\n    total = total + n * n\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code est **impératif** : il modifie\nune variable `total` à chaque itération.\n"
        },
        {
          "text": "```python\nclass Sommateur:\n    def __init__(self):\n        self.total = 0\n    def ajouter(self, n):\n        self.total += n * n\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code définit une classe ; c'est le\nstyle **objet**, pas fonctionnel.\n"
        },
        {
          "text": "```python\ntotal = nombres\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code ne calcule pas du tout la\nsomme des carrés.\n"
        },
        {
          "text": "```python\ntotal = sum(n * n for n in nombres)\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : ce code est **fonctionnel** : il\nconstruit une expression sans variable d'état\nintermédiaire et passe le résultat à la fonction\n`sum`. Pas d'effet de bord.\n"
        }
      ],
      "explanation": "Le style fonctionnel privilégie les expressions\nsans état modifiable. La compréhension de liste et\nles fonctions comme `sum`, `map`, `filter`\npermettent souvent d'écrire des traitements de\ndonnées très concis dans ce style."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "choix-paradigme"
      ],
      "title": "Choisir un paradigme",
      "statement": "Pour écrire un script court qui transforme une\nliste de prix en y appliquant une remise de $10\\%$,\nquel **paradigme** est le plus naturel en Python ?",
      "options": [
        {
          "text": "Une approche fonctionnelle avec une compréhension de liste ou un `map` sur une lambda",
          "correct": true,
          "feedback": "Bonne réponse : la transformation d'une liste en\nune autre par une fonction simple est typique du\nstyle fonctionnel. Une compréhension de liste\ncomme `[p * 0.9 for p in prix]` est concise et\nclaire.\n"
        },
        {
          "text": "Un paradigme logique type Prolog",
          "correct": false,
          "feedback": "Possible théoriquement mais inadapté ici, et\nProlog n'est pas Python. Le paradigme logique\nn'est pas étudié au lycée.\n"
        },
        {
          "text": "Une approche objet avec une classe `Remiseur` et une méthode `appliquer`",
          "correct": false,
          "feedback": "Possible mais nettement excessif pour une\ntransformation de liste de quelques lignes. La\nPOO se justifie quand on modélise des entités\navec un état et plusieurs comportements.\n"
        },
        {
          "text": "Aucun paradigme : il faut tout écrire à la main, avec des `goto`",
          "correct": false,
          "feedback": "Erreur : il n'existe pas de `goto` en Python, et\naucun paradigme moderne ne le promeut.\n"
        }
      ],
      "explanation": "Une même tâche peut souvent s'écrire dans plusieurs\nparadigmes. Le bon choix dépend de la nature du\nproblème : transformations de données pour le\nfonctionnel, modélisation d'entités pour l'objet,\nséquences d'instructions pour l'impératif. Python\npermet de mélanger les trois."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "parallelisme"
      ],
      "title": "Programmation fonctionnelle et parallélisme",
      "statement": "Pourquoi la programmation fonctionnelle est-elle\nparticulièrement adaptée au calcul **parallèle** ?",
      "options": [
        {
          "text": "Elle utilise plus de cœurs du processeur",
          "correct": false,
          "feedback": "Erreur : ce n'est pas la cause, c'est l'effet\nrecherché. La cause est plus profonde.\n"
        },
        {
          "text": "Elle utilise moins de mémoire",
          "correct": false,
          "feedback": "Erreur : au contraire, l'immutabilité peut consommer\nplus de mémoire (création de nouveaux objets plutôt\nque modification en place).\n"
        },
        {
          "text": "Elle est plus simple à apprendre",
          "correct": false,
          "feedback": "Erreur : c'est subjectif, et de toute façon pas la\nraison de l'aptitude au parallélisme.\n"
        },
        {
          "text": "L'absence d'effets de bord et l'immutabilité éliminent les conflits d'accès aux données partagées",
          "correct": true,
          "feedback": "Bonne réponse : en programmation impérative, deux\nthreads modifiant la même variable doivent se\nsynchroniser, ce qui est complexe et lent. Avec des\ndonnées immuables, aucune synchronisation n'est\nrequise, on peut paralléliser sans crainte.\n"
        }
      ],
      "explanation": "C'est l'argument phare des promoteurs du fonctionnel à\nl'ère des processeurs multi-cœurs. Spark, MapReduce,\nles architectures d'informatique en nuage reposent\nlargement sur ces principes."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese",
        "paradigmes"
      ],
      "title": "Comparaison des paradigmes",
      "statement": "Parmi les affirmations suivantes, laquelle est **vraie** ?",
      "options": [
        {
          "text": "La programmation fonctionnelle remplace définitivement l'orientée objet",
          "correct": false,
          "feedback": "Erreur : les deux paradigmes coexistent et se\ncomplètent. La plupart des langages modernes sont\nmulti-paradigmes (Python, JavaScript, Scala).\n"
        },
        {
          "text": "La programmation fonctionnelle ne fonctionne que sur les ordinateurs récents",
          "correct": false,
          "feedback": "Erreur : Lisp date de $1958$ et tournait sur les\npremiers ordinateurs. La programmation fonctionnelle\nest antérieure à l'orientée objet.\n"
        },
        {
          "text": "La programmation fonctionnelle interdit l'utilisation des classes",
          "correct": false,
          "feedback": "Erreur : on peut utiliser des classes en\nprogrammation fonctionnelle, surtout dans des\nlangages multi-paradigmes. Les puristes les évitent,\nmais ce n'est pas une interdiction du paradigme\nlui-même.\n"
        },
        {
          "text": "La programmation fonctionnelle et l'orientée objet sont des paradigmes complémentaires, chacun adapté à certaines situations",
          "correct": true,
          "feedback": "Bonne réponse : OO modélise des entités avec état\net comportement (interfaces graphiques, simulation,\njeux). Fonctionnelle modélise des transformations\nde données (calcul, science des données). Beaucoup\nde programmes mélangent les deux.\n"
        }
      ],
      "explanation": "L'idéal pragmatique : utiliser le bon paradigme pour\nle bon problème. Une bonne formation NSI inclut donc\nles deux pour développer la flexibilité de pensée\nalgorithmique."
    },
    {
      "id": "q26",
      "difficulty": 3,
      "skills": [
        "closure"
      ],
      "title": "Fermeture (closure)",
      "statement": "On considère le code Python suivant :\n```\ndef faire_compteur():\n    n = 0\n    def incrementer():\n        nonlocal n\n        n += 1\n        return n\n    return incrementer\n\nc = faire_compteur()\nprint(c(), c(), c())\n```\nQue renvoie l'appel `print(c(), c(), c())` et de\nquel concept s'agit-il ?",
      "options": [
        {
          "text": "Affiche `1 2 3`. C'est un exemple de fermeture\n(closure) : la fonction interne `incrementer`\ncapture la variable `n` de la fonction englobante,\nmême après que celle-ci a terminé son exécution\n",
          "correct": true,
          "feedback": "Bonne réponse : `incrementer` « se souvient » de\nla variable `n` de l'environnement où elle a été\ndéfinie. Chaque appel à `c()` incrémente cette\nvariable persistante. La fermeture est un concept\nfondamental qui montre que les fonctions de\npremière classe peuvent porter avec elles un\ncontexte.\n"
        },
        {
          "text": "Lève une erreur car `n` est une variable locale à\n`faire_compteur`\n",
          "correct": false,
          "feedback": "Erreur : grâce au mot-clé `nonlocal` (ou même sans\nen lecture), Python autorise l'accès à `n`.\nC'est précisément le mécanisme de fermeture.\n"
        },
        {
          "text": "Affiche `1 1 1` car `n` est réinitialisé à chaque\nappel\n",
          "correct": false,
          "feedback": "Erreur : c'est précisément ce que la fermeture\nempêche. La variable `n` est capturée et persistée\nentre les appels. Sans `nonlocal`, on aurait une\nerreur car on tenterait de modifier une variable\nqui semblerait locale.\n"
        },
        {
          "text": "Affiche `0 0 0` car `n` ne peut pas être modifié\ndans une fonction interne\n",
          "correct": false,
          "feedback": "Erreur : avec `nonlocal n`, on indique à Python\nque `n` se réfère à la variable de la fonction\nenglobante, et qu'on peut la modifier. La sortie\nest bien `1 2 3`.\n"
        }
      ],
      "explanation": "Les fermetures permettent de créer des fonctions avec\nétat, sans recourir aux classes. Très utilisées en\nJavaScript, Python, Lisp. Elles sont à la base des\ndécorateurs et des fonctions partiellement\nappliquées."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "decorateur"
      ],
      "title": "Décorateur Python",
      "statement": "Les décorateurs Python (`@nom`) sont une application\ndirecte d'un concept fondamental de la programmation\nfonctionnelle. Lequel ?",
      "options": [
        {
          "text": "La compréhension de liste\n",
          "correct": false,
          "feedback": "Erreur : la compréhension de liste est une\nsyntaxe fonctionnelle pour transformer des\ncollections, pas pour modifier des fonctions.\nAucun lien avec les décorateurs.\n"
        },
        {
          "text": "Le filtrage par motif (*pattern matching*)\n",
          "correct": false,
          "feedback": "Erreur : le filtrage par motif est une\nfonctionnalité distincte (introduite en Python\n3.10 avec `match`). Il n'a aucun rapport avec\nles décorateurs.\n"
        },
        {
          "text": "La récursivité\n",
          "correct": false,
          "feedback": "Erreur : un décorateur n'a aucun rapport avec la\nrécursivité. Il s'agit d'un mécanisme totalement\ndifférent. La récursivité est l'auto-appel d'une\nfonction.\n"
        },
        {
          "text": "Les fonctions d'ordre supérieur : un décorateur\nest une fonction qui prend une fonction en\nparamètre et renvoie une nouvelle fonction\n(souvent enrichie d'un comportement\nsupplémentaire)\n",
          "correct": true,
          "feedback": "Bonne réponse : la syntaxe `@decorator` au-dessus\nde `def f(): ...` est strictement équivalente à\n`f = decorator(f)`. Le décorateur reçoit la\nfonction `f` en paramètre et renvoie une nouvelle\nfonction qui généralement appelle `f` en y\najoutant des comportements (mesure du temps,\njournalisation, mémoïsation, vérification de\ndroits, etc.).\n"
        }
      ],
      "explanation": "Exemple de décorateur classique : `@functools.lru_cache`\nqui ajoute une mémoïsation à n'importe quelle fonction\npure. Autres : `@property` (transformer une méthode\nen attribut), `@staticmethod`, `@classmethod`. Tous\nreposent sur l'idée de manipuler les fonctions comme\ndes valeurs."
    }
  ]
}