Cours alternatif d’OCaml

Logo OCaml
VIII — Le type list d’OCaml

Le module List

Pour manipuler les listes de manière efficace, OCaml nous fournit un module List. Je vais présenter ici les fonctions les plus importantes de ce module. Certaines sont similaires à celles qui ont été réalisées dans les exercices du chapitre précédent.

Pour chaque fonction, j’utiliserais la présentation suivante :

nom de la fonction

List.nom_de_la_fonction : type -> de -> la -> fonction

Description.

(* Exemples *)

Pour utiliser ces fonctions, vous pouvez soit écrire leur nom complet, de la forme List.nom, soit ajouter l’instruction open List au début de votre code pour importer toutes le module, et donc écrire seuleument leur nom.

Attention aussi, List avec une majuscule est bien le nom du module, pas la variable qui correspond à votre liste. On passe toujours la liste à manipuler en paramètre de la fonction.

Enfin, dernier conseil avant de se lancer dans cette énumération de fonctions pas toujours passionnantes : pour chaque fonction, essayez de bien comprendre comment elle marche, et de l’utiliser vous-même au moins une fois. C’est comme ça que vous les maîtriserez le plus facilement (c’est valable pour le reste de ce cours d’ailleurs, et pour beaucoup d’autres cours en général).

Fonctions de base

length

List.lenght : 'a list -> int

Cette fonction prend une liste en paramètre, et renvoie sa taille.

List.length [ 'a' ; 'b' ; 'c' ] (* Donne 3 *)

hd

List.hd : 'a list -> 'a

Donne le premier élément de la liste (hd signifie « head », « tête »). Cette fonction plantera si la liste est vide, donc vérifiez toujours sa taille avant.

List.hd [ 42 ; 3 ; 87 ] (* Donne 42 *)

tl

List.tl : 'a list -> 'a list

Donne la fin de la liste (tl signifie « tail », « queue »), autrement dit la liste sans son premier élément. Cette fonction aussi plante avec la liste vide.

List.tl [ 42 ; 3 ; 87 ] (* Donne [ 3 ; 87 ] *)

nth

List.nth : 'a list -> int -> 'a

Donne le n-ième élément d’une liste. Si n est plus grand que la taille de la liste, on aura une erreur au moment de l’exécution.

List.nth [ 1 ; 2 ; 10 ; 0 ] 2 (* Donne 10 *)

sort

List.sort : ('a -> 'a -> int) -> 'a list -> 'a list

Trie une liste suivant une fonction de comparaison donnée en premier argument. En général, on utilisera la fonction prédéfinie compare. Attention, il se peut que cette fonction soit interdite en examen.

List.sort compare [ 12 ; 7 ; 33 ] (* Donne [ 7 ; 12 ; 33 ] *)

init

List.init : int -> (int -> 'a) -> 'a list

Un peu plus complexe cette fois. Cette fonction initialise une liste avec les valeurs données par une fonction. Le premier argument est le nombre d’élément de la liste. Le second est une fonction qui à un indice associe une valeur. Et la valeur retournée est la liste générée.

Voici un exemple :

let carre x = x * x
let ma_liste = List.init 5 carre
(* ma_liste vaut [ 0 ; 1 ; 4 ; 9 ; 16 ] *)

(* On peut aussi utiliser le mot-clé fun pour faire la même chose en une seule ligne *)
List.init 5 (fun x -> x * x)

Fonctions d’itérations

map

List.map : ('a -> 'b) -> 'a list -> 'b list

Associe à tout élément d’une liste de type 'a un élément de type 'b. Le premier paramètre est la fonction qui fait cette association, le second est la liste de départ, et la valeur retournée est la liste obtenue.

Un exemple, avec une fonction qui transforme une liste de caractères en la liste de leurs codes ASCII :

List.map int_of_char [ 'a' ; 'b' ; 'Z' ]
(* Donne [ 97 ; 98 ; 90 ] *)

Ou encore, une transformation qui double tous les nombres d’une liste :

let double x = x *. 2.0

List.map double [ 3.0 ; 7.2 ; 1.8 ]
(* Donne [ 6.0 ; 14.4 ; 3.6 ] *)

(* Là aussi, on peut utiliser fun si on veut : *)
List.map (fun x -> x *. 2.0) [ 3.0 ; 7.2 ; 1.8 ]

filter

List.filter ('a -> bool) -> 'a list -> 'a list

Filtre une liste en fonction d’une condition. La première fonction va être appliquée à tous les élements de la liste, et si elle renvoie true l’élément en question sera gardé. Voici un exemple pour ne garder que les éléments pairs d’une liste :

let est_pair x = (x mod 2) = 0
List.filter est_pair [ 1 ; 2 ; 3 ; 10 ; 11 ; 19 ; 38 ]
(* Donne [ 2 ; 10 ; 38 ] *)

(* Ou, avec fun : *)
List.filter (fun x -> (x mod 2) = 0) [ 1 ; 2 ; 3 ; 10 ; 11 ; 19 ; 38 ]

find

List.find ('a -> bool) -> 'a list -> 'a

Donne le premier élément correspondant à une condition. Si on veut trouver le premier élément d’une liste supérieur à 10, on peut par exemple faire :

let sup_a_dix x = x > 10
List.find sup_a_dix [ 1 ; 5 ; 15 ; 12 ; 5 ] (* Donne 15 *)

(* La même chose avec fun : *)
List.find (fun x -> x > 10) [ 1 ; 5 ; 15 ; 12 ; 5 ]

(* ou, si on veut laisser le choix de la liste, on peut faire une application partielle : *)
let plus_que_dix = List.find (fun x -> x > 10) (* Il « manque » un argument à List.find *)

plus_que_dix [ 5; 12 ] (* Donne 12 *)
plus_que_dix [ 87 ; 50 ; 1 ] (* Donne 87 *)

fold_left

List.fold_left : ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a

Celle-ci est sans doute la plus complexe à comprendre. Observons ses paramètres :

Cette fonction fait ce qu’on appelle un « pli » (d’où le nom « fold »). Elle prend tous les éléments de la liste et les compacte en un seul. Elle utilise pour cela la fonction qu’on lui donne.

Elle commence par appeler cette fonction avec le second argument (de type 'a), et le premier élément de la liste (de type 'b). Cette fonction renvoie un 'a, qui va venir remplacer l’original. Notre fonction est alors de nouveau appelée, mais avec le nouveau 'a, et l’élément suivant dans la liste. Le 'a est à nouveau remplacé. Et ça recommence, jusqu’à atteindre la fin de la liste.

Au final, cette fonction permet de faire des choses équivalentes aux accumulateurs en Python. Par exemple, le code suivant :

def somme(liste):
  somme = 0 # La valeur par défaut
  for x in liste:
    somme += x
  return somme

Peut être écrit de cette façon en OCaml :

let addition x y = x + y
let somme liste = List.fold_left addition 0 liste

(* La même chose avec fun : *)
let somme liste = List.fold_left (fun somme x -> somme + x) 0 liste

(* On peut même se passer de « liste », si on utilise la magie de l’application partielle : *)
let somme = List.fold_left (fun somme x -> somme + x) 0

(* Et si on se rappelle que + est aussi une fonction, on peut même écrire directement : *)
let somme = List.fold_left (+) 0

somme [ 3 ; 4 ; 10 ] (* Donne 3 + 4 + 10 = 17 *)

Il existe aussi un équivalent à fold_left mais qui parcourt la liste de la fin vers le début, et qui s’appelle sans trop de surprise fold_right.

Il existe d’autres fonctions, je n’ai listé que les plus importantes. Vous pouvez toutes les retrouver dans la documentation officielle.