Exercices : programmation fonctionnelle

Exercice 1 : QCM

Pour chaque question, une seule réponse est correcte.

1. Parmi les caractéristiques suivantes, laquelle est propre au paradigme fonctionnel ?

  • A. L’utilisation de boucles for et while
  • B. L’encapsulation de données dans des objets
  • C. La composition de fonctions et l’absence d’effets de bord
  • D. La modification de variables globales

Correction publiée après la séance.

2. Quel est le résultat de list(map(lambda x: x ** 2, [1, 2, 3])) ?

  • A. [2, 4, 6]
  • B. [1, 8, 27]
  • C. [1, 2, 3]
  • D. [1, 4, 9]

Correction publiée après la séance.

3. Qu’est-ce qu’une fonction d’ordre supérieur ?

  • A. Une fonction qui prend une autre fonction en paramètre ou qui renvoie une fonction
  • B. Une fonction qui s’appelle elle-même
  • C. Une fonction définie à l’intérieur d’une autre fonction
  • D. Une fonction qui modifie une variable globale

Correction publiée après la séance.

4. Quelle expression Python est écrite en style fonctionnel ?

  • A. result = 0 ; for x in L: result += x
  • B. while i < len(L): i += 1
  • C. sum(filter(lambda x: x > 0, L))
  • D. obj.calculer(L)

Correction publiée après la séance.


Exercice 2 : reconnaître le paradigme

Pour chaque extrait de code, indiquez s’il est écrit en style impératif, fonctionnel ou objet. Justifiez.

Indice

Impératif : des instructions qui modifient des variables, souvent dans des boucles. Fonctionnel : des fonctions sans effet de bord, la récursivité, map, filter, lambda. Objet : une classe dont les méthodes manipulent les attributs.

a)

def somme(L):
    total = 0
    for x in L:
        total += x
    return total

b)

def somme(L):
    return 0 if L == [] else L[0] + somme(L[1:])

c)

class Calcul:
    def __init__(self, L):
        self.L = L
    def somme(self):
        return sum(self.L)

d)

from functools import reduce
somme = lambda L: reduce(lambda acc, x: acc + x, L, 0)

Correction publiée après la séance.


Exercice 3 : map, filter et lambda (exercice guidé)

a) Complétez les expressions suivantes (notées ...) :

L = [3, 7, 2, 9, 4]

# Doubler chaque élément
list(map(lambda x: ..., L))          # → [6, 14, 4, 18, 8]

# Garder seulement les éléments impairs
list(filter(lambda x: ..., L))       # → [3, 7, 9]

# Convertir en chaînes
list(map(..., L))                     # → ['3', '7', '2', '9', '4']
Indice

map(f, L) applique f à chaque élément ; filter(p, L) garde les éléments pour lesquels p renvoie True. Pour convertir en chaîne, la fonction str se passe directement à map, sans lambda.

b) Écrivez les expressions pour obtenir, à partir de L = [3, 7, 2, 9, 4] :

  1. la liste des carrés : [9, 49, 4, 81, 16] ;
  2. la liste des éléments pairs : [2, 4] ;
  3. la liste des éléments supérieurs à 5 : [7, 9].

Donnez chaque réponse avec map/filter + lambda, puis avec une compréhension de liste.

Indice

Une transformation de chaque élément s’écrit avec map, une sélection avec filter. En compréhension de liste, la transformation se place avant for, la condition après if.

c) À partir de mots = ['Python', 'est', 'un', 'langage'], obtenir la liste des longueurs : [6, 3, 2, 7].

Indice

La fonction len s’applique à chaque mot : map peut la recevoir directement.

d) À partir de L = [-3, 5, -1, 8, -2, 7], obtenir la liste des valeurs absolues des éléments négatifs : [3, 1, 2] (combinaison de filter et map).

Indice

D’abord filter pour garder les éléments négatifs, puis map avec abs sur le résultat : list(map(abs, filter(..., L))).

Correction publiée après la séance.


Exercice 4 : sorted avec key et lambda

a) Trier la liste mots = ['Python', 'est', 'un', 'langage', 'multiparadigme'] par longueur croissante.

Indice

Le paramètre key de sorted reçoit une fonction qui donne, pour chaque élément, la valeur à comparer : quelle fonction donne la longueur d’un mot ?

b) Trier la liste eleves = [('Alice', 15), ('Bob', 12), ('Clara', 18), ('David', 14)] par note décroissante.

Indice

La clé est la note, deuxième élément du tuple : lambda e: e[1]. Pour l’ordre décroissant, ajouter reverse=True.

c) Trier la liste points = [(1, 5), (3, 2), (0, 0), (4, 1)] par distance croissante à l’origine.

Indice

La clé peut être le carré de la distance, x ** 2 + y ** 2 : il range les points dans le même ordre que la distance, sans racine carrée.

Correction publiée après la séance.


Exercice 5 : réécriture impératif vers fonctionnel

Réécrivez chaque fonction en style fonctionnel (pas de variable modifiable, pas de boucle, récursivité uniquement).

a) Longueur d’une liste :

Indice

Cas de base : la liste vide a pour longueur 0. Sinon, la longueur vaut 1 plus la longueur de la liste privée de son premier élément, L[1:].

def longueur(L):
    compteur = 0
    for _ in L:
        compteur += 1
    return compteur

b) Appartenance d’un élément à une liste :

Indice

Une liste vide ne contient pas x. Sinon, x est dans L s’il est égal à L[0] ou s’il appartient à L[1:].

def appartient(x, L):
    for elem in L:
        if elem == x:
            return True
    return False

c) Nombre d’occurrences :

Indice

Liste vide : 0 occurrence. Sinon, compter 1 si L[0] vaut x et 0 sinon, puis ajouter le nombre d’occurrences dans L[1:].

def nb_occurrences(x, L):
    compteur = 0
    for elem in L:
        if elem == x:
            compteur += 1
    return compteur

Correction publiée après la séance.


Exercice 6 : fonctions pures et effets de bord

Pour chaque fonction, indiquez si elle est pure ou impure. Justifiez.

Indice

Une fonction est pure si son résultat ne dépend que de ses paramètres et si elle ne modifie rien en dehors d’elle : ni variable globale, ni liste reçue en paramètre.

a)

def double(x):
    return 2 * x

b)

compteur = 0
def incrementer():
    global compteur
    compteur += 1
    return compteur

c)

def ajouter(x, L):
    L.append(x)
    return L

d)

def ajouter_pur(x, L):
    return L + [x]

Correction publiée après la séance.


Exercice 7 : la fonction forall (exercice guidé)

On souhaite écrire une fonction forall(pred, L) qui renvoie True si tous les éléments de L vérifient le prédicat pred.

Données de test :

pair = lambda x: x % 2 == 0
ex1 = [4, 4, 2, 0, 1]   # attendu : False
ex2 = []                  # attendu : True
ex3 = [4, 4, 2, 0, 8]   # attendu : True

a) Écrivez forall avec une boucle for. C’est la version la plus naturelle : dès qu’un élément ne vérifie pas le prédicat, on renvoie False.

Indice

Parcourir L : si pred(x) est faux pour un élément, renvoyer False. Après la boucle, tous les éléments conviennent : renvoyer True.

b) Écrivez forall en version récursive pure (sans boucle, sans variable). Quel est le cas de base ? Comment se réduit le problème ?

Indice

Cas de base : la liste vide, pour laquelle la réponse est True, car aucun élément ne contredit le prédicat. Sinon, tous les éléments conviennent si le premier convient et si tous ceux de L[1:] conviennent.

c) Écrivez forall avec map et all. Quel est le rôle de chacune de ces fonctions ?

Indice

map(pred, L) donne, pour chaque élément, True ou False ; all renvoie True si toutes ces valeurs sont vraies.

d) Écrivez forall avec une compréhension de liste et all.

Indice

La compréhension [pred(x) for x in L] donne la liste des réponses du prédicat ; il reste à la passer à all.

e) Écrivez forall avec filter et len.

Indice

filter(pred, L) garde les éléments qui vérifient le prédicat : tous le vérifient si ce filtrage en garde autant que L en contient.

Correction publiée après la séance.


Exercice 8 : synthèse – trois paradigmes sur un même problème

On dispose d’une liste d’enregistrements représentant des élèves :

eleves = [
    {"nom": "Alice", "notes": [15, 12, 18]},
    {"nom": "Bob", "notes": [8, 14, 9]},
    {"nom": "Clara", "notes": [16, 17, 19]},
    {"nom": "David", "notes": [11, 7, 13]},
]

On souhaite obtenir la liste des noms des élèves dont la moyenne est supérieure ou égale à 14.

Résultat attendu : ['Alice', 'Clara']

a) Écrire une solution en style impératif (boucle, variable accumulatrice).

Indice

Parcourir les élèves : calculer la moyenne de eleve["notes"] avec sum et len, et ajouter eleve["nom"] à la liste résultat si cette moyenne vaut au moins 14.

Code à copier puis compléter
resultat = []
for eleve in eleves:
    moyenne = sum(eleve["notes"]) / len(eleve["notes"])
    if ...:
        resultat.append(...)
print(resultat)

b) Écrire une solution en style fonctionnel (avec filter, map, lambda), puis en compréhension de liste.

Indice

filter garde les élèves dont la moyenne vaut au moins 14, puis map extrait leur nom avec lambda e: e["nom"]. En compréhension : [e["nom"] for e in eleves if ...].

c) Écrire une solution en style objet (avec une classe Eleve disposant des méthodes moyenne et est_bon).

Indice

Le constructeur range nom et notes dans des attributs ; moyenne calcule la moyenne de self.notes, et est_bon compare cette moyenne au seuil. Créer ensuite un objet Eleve pour chaque dictionnaire de la liste.

Code à copier puis compléter
class Eleve:
    def __init__(self, nom, notes):
        self.nom = nom
        self.notes = ...

    def moyenne(self):
        return sum(self.notes) / ...

    def est_bon(self, seuil=14):
        return self.moyenne() >= ...

d) Comparer les trois approches : laquelle est la plus lisible ? La plus courte ? La plus facilement testable ?

Indice

Comparer le nombre de lignes, la présence de variables modifiées, et la facilité d’appeler chaque partie séparément pour la tester.

Correction publiée après la séance.