Chapitre 2. Les listes

publicité
Lycée Victor Hugo MPSI 2016-2017
Option informatique
Chapitre 2. Les listes
I Introduction
En informatique, une structure de données est la description d'une structure logique destinée à organiser et à agir sur des données indépendamment de la mise en ÷uvre eective de ces structures. Nous
allons en étudier plusieurs, en commençant par les listes simplement chainées (ou plus simplement dans
la suite de ce cours, les listes).
Une liste est une collection séquentielle et de taille arbitraire de données de même type : chaque
élément possède, en plus de la donnée, d'un pointeur vers l'élément suivant de la liste. On peut donc
représenter une liste par la gure suivante :
a0
a1
a2
···
an
···
Néanmoins, un tel schéma est incomplet car taille arbitraire ne signie pas taille innie : il est nécessaire que notre liste se termine. Nous allons donc adjoindre à cette description un élément particulier
caractérisant la terminaison de la liste, et qu'on appelle le nil (abréviation du latin nihil, autrement dit,
rien).
a0
a1
a2
···
an
nil
Ainsi, le type de données abstrait dénissant une liste est le suivant :
Liste = nil + Élément × Liste,
ce qui signe qu'une liste est soit la liste vide, soit un couple constitué d'un élément et d'une liste.
On notera que cette dénitation de la liste est récursive !
I.1
Mise en ÷uvre en
Caml
Bien que cette structure de donnée soit déjà présente en Caml , nous allons commencer par le faire
nous-mêmes, pour bien comprendre ses avantages, mais aussi ses contraintes.
Commençons par dénir le type polymorphe et récursif 'a liste :
Code Caml 1
1 # type 'a liste = Nil | Cellule of 'a * ( ' a liste ) ;;
2 Le type liste est défini.
On a traduit le type abstrait sous la forme d'un type somme. Une des valeurs possibles est la constante
Nil (Noter la majuscule obligatoire). L'autre valeur possible est un couple (2-uplet) Cellule constitué d'une
valeur de type 'a et d'une liste de valeurs de type 'a .
Nous pouvons maintenant dénir notre première liste d'entiers, par exemple celle représentée par le
schéma suivant :
3
2
1
nil
Code Caml 2
1 # let lst = Cellule (3 , Cellule (2 , Cellule (1 , Nil ) ) ) ;;
2 lst : int liste = Cellule (3 , Cellule (2 , Cellule (1 , Nil ) ) )
1
Lycée Victor Hugo MPSI 2016-2017
Option informatique
Nous allons adjoindre à notre type une fonction permettant d'insérer un élément en tête de liste :
Code Caml 3
1 # let construire t q = Cellule (t , q ) ;;
2 construire : 'a -> 'a liste -> 'a liste = <fun >
Pour dénir la liste représentée ci-dessous, il sura donc d'écrire :
4
3
2
1
nil
Code Caml 4
1 # let lst2 = construire 4 lst ;;
2 lst2 : int liste = Cellule (4 , Cellule (3 , Cellule (2 , Cellule (1 , Nil))))
Inversement, nous allons avoir besoin de récupérer les éléments d'une liste. Ceci nous amène à dénir
deux fonctions supplémentaires : une fonction tete qui retourne le premier élément de la liste, et
queue qui renvoie la liste pointée par la tête de liste :
Code Caml 5
1
2
3
4
5
6
7
8
9
10
11
12
# let tete l = match l with
| Nil -> failwith " liste vide "
| Cellule (t , q ) -> t ;;
tete : 'a liste -> 'a = <fun>
# let queue l = match l with
| Nil -> failwith " liste vide "
| Cellule (t , q ) -> q ;;
queue : 'a liste -> 'a liste = <fun>
# tete lst2 ;;
- : int = 4
# queue lst2 ;;
- : int liste = Cellule (3 , Cellule (2 , Cellule (1 , Nil)))
Nous n'allons pas pousser plus loin la mise en ÷uvre de notre propre type de liste puisque le type 'a list
existe d'ores et déjà en Caml , mais on retiendra principalement de cette construction les observations
suivantes, qui en font un type très diérents des listes python :
les listes peuvent grossir dynamiquement, mais les éléments sont toujours rajoutés en tête de liste ;
on ne peut accéder directement qu'à la tête de la liste.
tous les éléments d'une liste sont du même type (listes homogènes)
II Description des listes en Caml
II.1
Construction d'une liste
Les listes sont délimitées par des crochets [ et ] , les éléments (qui doivent être de même type) étant
séparés par un point-virgule. Par exemple :
Code Caml 6
1
2
3
4
5
6
7
8
9
10
# [4; 3; 2; 1] ;;
- : int list = [4; 3; 2; 1]
# [ 'a '; 'b '; 'c '] ;;
- : char list = [ 'a'; 'b'; 'c']
# [4; 'a '; 3; 2; 1] ;;
Entrée interactive :
>[4; 'a '; 3; 2; 1] ;;
> Cette expression est de type int list,
mais est utilisée avec le type char list.
2
Lycée Victor Hugo MPSI 2016-2017
Option informatique
Bien entendu, l'élément nil sera représenté par la liste vide [] et aura pour type 'a list .
L'ajout d'un élément en tête de liste est représenté par l'opérateur inxe : : qu'on appelle conse
(pour constructeur de liste). Ce constructeur est associatif à droite, ce qui permet d'éviter l'écriture de
nombeuses parenthèses inutiles.
Code Caml 7
1 # 5 :: [4; 3; 2; 1] ;;
2 -: int list = [5; 4; 3; 2; 1]
3 # 'a ' :: ( 'b ' :: ( 'c ' :: []) ) ;; (* version avec parenth è ses (
inutiles ) *)
4 -: char list = ['a'; 'b'; 'c']
5 # 'a ' :: 'b ' :: 'c ' :: [] ;; (* version sans parenth è ses , é
criture conseill é e *)
6 -: char list = ['a'; 'b'; 'c']
À l'inverse, les fonctions hd (head) et tl (tail) permettent d'obtenir la tête (le premier élément de
la liste) et la queue (la liste privée de son premier élément) d'une liste :
Code Caml 8
1
2
3
4
#
#
-
hd [4; 3; 2; 1] ;;
: int = 4
tl [4; 3; 2; 1] ;;
: int list = [3; 2; 1]
Notons que ces deux fonctions déclenchent une exception lorsqu'on essaye de les appliquer à la liste
vide :
Code Caml 9
1 # hd [] ;;
2 Exception non rattrapée : Failure "hd "
II.2
Filtrage et récursivité
Le motif tete :: queue est reconnu par toute liste non vide, et dans la suite de l'évaluation
tete prendra la valeur de la tête et queue celle de la queue. Il nous est donc facile de redénir à
titre d'exemple les fonctions hd et tl :
Code Caml 10
1
2
3
4
5
6
7
8
9
10
# let head liste =
match liste with
| [] -> failwith " liste vide "
| tete :: queue -> tete ;;
head : 'a list -> 'a = <fun>
# let tail liste =
match liste with
| [] -> failwith " liste vide "
| tete :: queue -> queue ;;
tail : 'a list -> 'a list = <fun>
Associé à la récursivité, nous avons là le mode principal de dénition d'une fonction agissant sur les
listes. À titre d'exemple, on peut proposer les instructions suivantes.
3
Lycée Victor Hugo MPSI 2016-2017
Le calcul de la longueur d'une liste
Option informatique
:
Code Caml 11
1 # let rec longueur liste =
2
match liste with
3
| [] -> 0
4
| tete :: queue -> 1 + longueur queue ;;
Caml dispose déjà de l'instruction correspondante list_length.
L'obtention du dernier élément d'une liste
Code Caml 12
1 # let rec dernier liste =
2
match liste with
3
| [] -> failwith " liste vide "
4
| [ a ] -> a
5
| _ :: queue -> dernier queue ;;
Notons que le motif [a] est équivalent au motif a :: [] .
Notons également que nous avons ici deux cas de base ! Si la liste est vide il faut lever une exception.
Si la liste n'a qu'un seul élément, c'est forcément le dernier.
Le test d'appartenance à une liste
Code Caml 13
1 # let rec membre x liste =
2
match liste with
3
| [] -> false
4
| tete :: _ when tete = x -> true
5
| _ :: queue -> membre x queue ;;
Caml dispose déjà de l'instruction correspondante mem
.
Notons que le principe de l'évaluation paresseuse permet d'écrire de manière équivalente :
Code Caml 14
1 # let rec membre x liste =
2
match liste with
3
| [] -> false
4
| tete :: queue -> ( tete = x ) || membre x queue ;;
Je rappelle que si vous décidez de programmer en utilisant
vous avez tout intérêt à le signaler au correcteur...
L'obtention du ne élément d'une liste
en conscience
l'évaluation paresseuse,
La programmation est un peu plus subtile... et fait appel a
un ltrage sur les deux paramètres d'entrée. Rappelons qu'on n'a pas accès à la taille de la liste, et qu'on
n'a pas non plus de boucle for en programmation fonctionnelle ! L'idée de la récurrence est que le n-ième
élément d'une liste est le (n - 1)-ième élément de la queue de cette liste. Les cas de base sont les suivants :
si on demande un élément d'une liste vide il y a bien sûr erreur ; si on demande le premier élément il est
facile de le rendre avec la fonction hd (Par homogénéité avec les us informatiques habituels on numérote
à partir de 0, le premier élément de la liste est le 0-ième)
4
Lycée Victor Hugo MPSI 2016-2017
Option informatique
Code Caml 15
1 # let rec nieme n liste =
2
match n , liste with
3
| _ , [] -> failwith " Liste trop courte "
4
| 0 , x :: _ -> x
5
| _ , _ :: queue -> nieme ( n - 1) queue ;;
La concaténation de deux listes
Code Caml 16
1 # let rec concat lst1 lst2 =
2
match lst1 with
3
| [] -> lst2
4
| tete :: queue -> tete :: ( concat queue lst2 ) ;;
On notera l'expressivité de cette programmation récursive...
Caml dispose de l'instruction @ qui est la forme inxe (i.e. que l'opérateur se met
opérandes) de cette fonction :
entre les deux
Code Caml 17
1 # [1; 2; 3] @ [4; 5; 6] ;;
2 - : int list = [1 ;2; 3; 4; 5; 6]
Attention, mis à part les fonctions hd et tl , aucune de ces fonctions ci-dessus n'a un coût temporel
constant : si n désigne la longueur de la liste, la fonction list_length et la fonction mem ont une
complexité en O(n).
Quand à la concaténation lst1 @ lst2 , son coût est proportionnel à la longueur de la liste lst1 .
III Fonctionnelles agissant sur les listes
III.1
Les fonctions map et do_list
Étant données une fonction f de type 'a -> 'b et une liste [a0 ; · · · ; an−1 ] de type 'a list , la
fonctionnelle map a
pour objet de créer la liste [f (a0 ); · · · ; f (an−1 )] . Sa dénition est la suivante :
Code Caml 18
1 # let rec map f liste =
2
match liste with
3
| [] -> []
4
| tete :: queue -> ( f tete ) :: ( map f queue ) ;;
Son type est ('a -> 'b) -> 'a list -> 'b list ; notons que cette fonction est prédénie en
Caml .
Code Caml 19
1 # map string_length [ " alpha " ; " beta " ; " gamma " ; " delta " ] ;;
2 -: int list = [5; 4; 5; 5]
Étant données une fonction f de type 'a -> unit et une liste [a0 ; · · · ; an−1 ] de type 'a list , la
fonctionnelle do_list a pour objet d'eectuer la séquence (notion que nous reverrons plus tard, plutôt
dans le contexte de la programme impérative) f (a0 ); f (a1 ); · · · ; f (an−1 ) (ce qui n'a d'intérêt que si f a
un eet sur l'environnement). Sa dénition est la suivante :
5
Lycée Victor Hugo MPSI 2016-2017
Option informatique
Code Caml 20
1 # let rec do_list f liste = function
2
| [] -> ()
3
| tete :: queue -> f tete ; do_list f queue ;;
Son type est ('a -> 'b) -> 'a list -> unit ; cette fonction est elle aussi prédénie en
Caml .
Code Caml 21
1 # do_list print_string [ " alpha " ; " beta " ; " gamma " ; " delta " ] ;;
2 alphabetagammadelta-: unit = ()
6
Téléchargement