Précédent Index Suivant

Itérateurs sur les listes

 
 
Les itérateurs permettent d'appliquer un traitement (une fonction) à tous les termes d'une liste. Ils sont construits selon le schéma de récursion simple sur les listes.
 
La fonction filtre  
Étant donnée une liste L d'entiers, supposons que l'on veuille rendre la liste des éléments de L qui sont pairs. Par exemple:
 
(filtre-pairs (list 1 2 3 5 8 6)) -> (2 8 6)  

 
On peut écrire la fonction suivante:  
;;; filtre-pairs : LISTE[int] -> LISTE[int] 
;;; (filtre-pairs L) rend la liste des éléments de «L» qui sont pairs 
(define (filtre-pairs L)
  (if (pair? L)
      (if (even? (car L))
          (cons (car L) (filtre-pairs (cdr L)))
          (filtre-pairs (cdr L)))
      (list)))
 

 
Si l'on veut rendre la liste des éléments de L qui sont impairs, il faut écrire une autre fonction:  
;;; filtre-impairs : LISTE[int] -> LISTE[int] 
;;; (filtre-impairs L) rend la liste des éléments de «L» qui sont impairs 
(define (filtre-impairs L)
  (if (pair? L)
      (if (odd? (car L))
          (cons (car L) (filtre-impairs (cdr L)))
          (filtre-impairs (cdr L)))
      (list)))
 

 
En fait, toutes ces définitions ont la même forme, autrement dit elles suivent le même schéma:
 
;;; fonction: LISTE[alpha] -> LISTE[alpha] 
(define (fonction L)
  (if (pair? L)
      (if (test? (car L))
          (cons (car L) (fonction (cdr L)))
          (fonction (cdr L)))
      (list)))
 

 
et on pourrait dire: « étant donnée une fonction de test (qui a comme donnée un élément de type alpha et qui rend un booléen), une définition de la fonction qui rend la liste des éléménts d'une liste donnée qui vérifient ce test peut être obtenue en écrivant... ».
 
Mais, plutôt que d'écrire une nouvelle fonction à chaque fois que l'on change le prédicat de filtrage, il vaut mieux écrire une fonction, paramétrée par le prédicat de filtrage, filtre (une telle fonction est dite générique):
 
;;; filtre : (alpha -> bool) * LISTE[alpha] -> LISTE[alpha]  
;;; (filtre test? L) rend la liste des éléments de la liste «L» 
;;; qui vérifient le prédicat «test?». 
(define (filtre test? L)
  (if (pair? L)
      (if (test? (car L))
          (cons (car L) 
                (filtre test? (cdr L)))
          (filtre test? (cdr L)))
      (list)))
 

 
Noter que le premier argument de cette fonction est une fonction -- plus précisément un prédicat -- (on dit que cette fonction est une fonctionnelle).
 
Et alors:
 
(filtre even? (list 1 2 3 4 5)) -> (2 4)
 
(filtre odd? (list 1 2 3 4 5)) -> (1 3 5)

 
(filtre integer? (list 2 2.5 3 3.5 4)) -> (2 3 4)
 

 
Dans tous ces exemples, la fonction de test est une fonction prédéfinie. Comment faire lorsque ce n'est pas le cas? Par exemple, comment filtrer dans une liste de nombres ceux qui sont supérieurs à un nombre donné? Il suffit d'écrire un prédicat ad-hoc et appliquer la fonction filtre à ce prédicat:
 
;;; sup-4? : Nombre -> Nombre 
;;; (sup-4? x) rend vrai ssi «x» est strictement supérieur à 4 
(define (sup-4? x)
  (> x 4))

(filtre sup-4? (list 5 2.5 4 4.5 3)) -> (5 4.5)
 

 
La fonction map  
On veut écrire une définition de la fonction qui rend la liste des carrés des éléments d'une liste de nombres. Par exemple:  
(liste-carres (list 1 2 3 4)) -> (1 4 9 16)  

 
Après avoir défini la fonction qui rend le carré de sa donnée:  
;;; carre : Nombre -> Nombre 
;;; (carre x) rend le carré de «x» 
(define (carre x)
  (* x x))
 

 
on peut écrire une définition de la fonction liste-carres:  
;;; liste-carres : LISTE[Nombre] -> LISTE[Nombre] 
;;; (liste-carres L) rend la liste des carrés des éléments de «L»  
(define (liste-carres L)
  (if (pair? L)
      (cons (carre (car L)) 
            (liste-carres (cdr L)))
      (list)))
 

 
On peut aussi vouloir écrire une définition de la fonction qui rend la liste des racines carrées des éléments d'une liste de nombres. Par exemple:  
(liste-racines-carrees (list 16 25 36)) -> (4 5 6)  

 
voici une définition de la fonction liste-racines-carrees:  
;;; liste-racines-carrees : LISTE[Nombre] -> LISTE[Nombre] 
;;; (liste-racines-carrees L) rend la liste des racines carrées des  
;;; éléments de «L»  
(define (liste-racines-carrees  L)
  (if (pair? L)
      (cons (sqrt (car L)) 
            (liste-racines-carrees (cdr L)))
      (list)))
 

 
Toutes ces définitions suivent le même schéma: la fonction fn-sur-element ayant comme interface:  
;;; fn-sur-element: alpha -> beta 
 

 
la définition de la fonction est:
 
;;; fonction: LISTE[alpha] -> LISTE[beta] 
(define (fonction L)
  (if (pair? L)
      (cons (fn-sur-element (car L)) 
            (fonction (cdr L)))
      (list)))
 

 
On peut encore écrire une fonction générique pour mettre en oeuvre toutes ces définitions:
 
;;; map : (alpha -> beta) * LISTE[alpha] -> LISTE[beta] 
;;; (map fn L) rend la liste dont les éléments résultent de l'application de 
;;; la fonction «fn» aux éléments de «L»  
(define (map fn L)
  (if (pair? L)
      (cons (fn (car L)) 
            (map fn (cdr L)))
      (list)))
 

 
La fonction map reçoit une fonction et une liste, et renvoie une liste de même taille, dans laquelle chaque terme est le résultat de l'application de la fonction.
 
En utilisant cette fonction, voici la mise en oeuvre des exemples précédents:
 
(map carre (list 1 2 3 4)) -> (1 4 9 16))
 
(map sqrt (list 16 25 36)) -> (4 5 6)
 

 
et quelques autres exemples d'utilisation:
 
(map even? (list 1 2 3 4 5)) -> (#F #T #F #T #F)
 
(map odd? (list 1 2 3 4 5)) -> (#T #F #T #F #T)
 

 
Remarque: Noter bien la différence entre map et filtre (qui, toutes les deux, ont comme données une fonction et une liste, la fonction ayant comme type de données le type des éléments de la liste donnée, et rendent une liste):  
Un dernier exemple:
 
(map list (list 1 2 3 4)) -> ((1) (2) (3) (4))  

 
Remarque
 
La fonction map (mais pas filtre) est une primitive de Scheme.
 
La fonction reduce  
Parmi les exemples de récursion simple sur les listes, nous avons vu la fonction somme qui somme tous les éléments d'une liste de nombres. Nous pourrions aussi définir la fonction produit qui rend le produit de tous éléments d'une liste de nombres...
 
Un exemple dont l'interface est plus complexe: on a, au départ, une somme de n francs, on a des rentrées et des dépenses, et on veut connaître le nouveau solde:  
(solde 300 (list 100 -60 -30)) -> 310
 
(solde 300 (list 100.1 -60.2 -30.4)) -> 309.5
 

 
Cette fonction peut être définie comme suit:  
;;; solde : float * LISTE[float] -> float 
;;; (solde s L) rend la somme de «s» et de la somme des éléments de la liste «L» 
(define (solde s L)
  (if (pair? L)
      (+ (car L) (solde s (cdr L)))
      s))
 

 
Notons que (somme L) est alors égal à (solde 0 L): la fonction solde est plus générale que la fonction somme.
 
On fait plus compliqué? Oui: le compte est un compte bancaire et l'informaticien de service a décidé de mettre les centimes dans sa poche (bien sûr le compte initial est alors un entier):  
(solde-b 300 (list 100.1 -60.2 -30.4)) -> 308  

 
La fonction solde-b peut être définie, exactement comme la fonction solde mais en utilisant, à la place de la fonction +, la fonction add-b de spécification:  
;;; add-b : float * int -> int 
;;; (add-b x n) rend l'entier inférieur à la somme de «x» et de «n»  
 
et la définition de solde-b est alors:  
;;; solde-b : int * LISTE[float] -> int 
;;; (solde-b s L) rend la somme de «s» et de la pseudo-somme des éléments de 
;;; la liste «L» (toutes les sommes étant effectuées en arrondissant à  
;;; l'entier inférieur) 
(define (solde-b s L)
  (if (pair? L)
      (add-b (car L) (solde-b s (cdr L)))
      s))
 

 
Noter bien le type des deux fonctions:  
Remarque: floor et inexact->exact étant des fonctions prédéfinies de Scheme de spécifications:
 
;;; floor : float -> float 
;;; (floor x) rend la valeur réelle égale à l'entier immédiatement inférieur à «x». 
;;; Par exemple (floor 3.2) rend 3.0 et (floor -3.2) rend -4.0 
 
;;; inexact->exact : Nombre -> Nombre 
;;; (inexact->exact x) rend la valeur exacte (entière ou rationelle) égale à «x». 
;;; Par exemple, (inexact->exact 3.0) rend 3 

la fonction add-b peut être définie par :  
;;; add-b : float * int -> int 
;;; (add-b x n) rend l'entier inférieur à la somme de «x» et de «n»  
(define (add-b x n)
  (inexact->exact (+ (floor x) n)))
 

 
Généralisons: la fonction reduce reçoit une fonction binaire (de type a × b --> b), une valeur de départ (de type b) et une liste (de type LISTE[a]), et condense la liste en un unique résultat (de type b), en composant la fonction binaire sur les termes de la liste. Exemples d'utilisation:
 
(reduce + 0 (list 1 2 3 4 5)) -> 15
 
(reduce * 1 (list 1 2 3 4 5)) -> 120

 
(reduce + 300 (list 100.1 -60.2 -30.4)) -> 309.5

 
(reduce add-b 300 (list 100.1 -60.2 -30.4)) -> 308
 

 
La fonction reduce peut être définie par:
 
;;; reduce : (alpha * beta -> beta) * beta * LISTE[alpha] -> beta 
;;; (reduce fn base L) rend la valeur résultant de la composition de la  
;;; fonction binaire «fn» sur les éléments de «L», à partir de l'élément «base».  
;;; Par exemple (reduce f e (list e1 e2)) == (f e1 (f e2 e))  
(define (reduce fn base L)  
  (if (pair? L)
      (fn (car L) (reduce fn base (cdr L)))
      base))
 

 
On peut donner des exemples compliqués:
 
(reduce cons (list) (list 1 2 3 4)) -> (1 2 3 4)
 
(reduce cons (list 4 5) (list 1 2 3)) -> (1 2 3 4 5)

 
(reduce + 0 (map carre (list 1 2 3 4))) -> 30
 

 
et encore plus compliqués:
 
(reduce et #T (map even? (list 1 2 3 4 5 6 7))) -> #F
 
(reduce et #T (map odd? (list 1 3 5 7))) -> #T

 
la fonction et étant définie par:  
;;; et : bool * bool -> bool 
;;; (et a b) rend #t ssi «a» et «b» sont égaux à #t 
(define (et a b) 
  (and a b))
 

 
Remarque: dans l'application de la fonction reduce, on ne peut pas utiliser and car c'est une forme spéciale et non une fonction.
 
Un dernier exemple qui utilise les trois itérateurs que nous avons vus (et qui calcule la somme des carrés des éléments pairs de la liste donnée):
 
(reduce + 0 
        (map carre 
             (filtre even? (list 1 2 3 4 5 6))))  ->  56
 

Pour s'auto-évaluer
  • Exercices d'assouplissement
  • Pour s'auto-évaluer
  • Exercices d'assouplissement


  • Auteur(s): titou@ufr-info-p6.jussieu.fr.Mainteneur de la page: titou@ufr-info-p6.jussieu.fr.

    Précédent Index Suivant