Programmation fonctionnelle

publicité
Programmation fonctionnelle
Notes de cours
Cours 5
31 Octobre 2012
Sylvain Conchon
[email protected]
1/21
Le programme de cette semaine
Le mode T9 des téléphones portables
2/21
Les notions abordées avec ce programme
I
Exceptions
I
Dictionnaires
I
Arbres de préfixes
3/21
Les exceptions
# 1 / 0 ;;
Exception: Division by zero.
# let x = [| 1 |];;
val x : int array = [| 1 |] ;;
# x.(1);;
Exception: Invalid argument "index out of bounds".
# int of string "bonjour" ;;
Exception: Failure "int of string".
I
Le mécanisme d’exception permet de gérer le problème des
fonctions partielles : une exception est levée si aucune valeur
ne peut être renvoyée par la fonction.
I
Si l’exception n’est pas rattrapée (voir ci-après), le programme
se termine.
4/21
Les exceptions : déclaration et typage
# exception Fin ;;
exception Fin
# raise Fin; print int 10 ;;
Exception: Fin.
# exception E of int ;;
exception E of int
# let f x = if x = 0 then raise (E(x)) else 10 / x ;;
val f : int -> int = <fun>
# f 0 ;;
Exception: E(0)
I
Une exception est définie à l’aide du mot-clé exception.
I
Comme pour les constructeurs, elle commence par une
majuscule et elle peut attendre des arguments.
I
On lève une exception à l’aide de la fonction raise.
I
Ce mécanisme est transparent au typage.
5/21
Les exceptions : rattrapage
# let f x = if x = 10 then raise (E(10)) else 10 / x ;;
# try f 0 with
| Division by zero -> 0
| E x -> x ;;
- : int = 0
I
Une exception peut être rattrapée à l’aide de la construction
try-with qui permet de définir des gestionnaires
d’exceptions.
I
Comme pour un pattern-matching, un gestionnaire permet de
récupérer les arguments associés aux exceptions.
6/21
Dictionnaires
Un dictionnaire est une structure de données qui permet d’associer
des clefs (de type ’a) à des valeurs (de type ’b).
La manière la plus simple de réaliser un dictionnaire est d’utiliser
une liste de paires (’a * ’b) list.
La bibliothèque OCaml fournit un certain nombre de fonctions
permettant de manipuler de tels dictionnaires. En particulier,
I
la fonction List.assoc renvoie la valeur associée à une clef
(elle lève l’exception Not found si aucune valeur n’est
associée)
’a -> (’a * ’b) list -> ’b
I
la fonction List.remove assoc supprime la première
association liée à une clef
’a -> (’a * ’b) list -> (’a * ’b) list
7/21
Dictionnaires : changement d’association
Pour changer l’association liée à une clef dans un dictionnaire, on
définit la fonction change assoc de type
’a -> ’b -> (’a * ’b) list -> (’a * ’b) list
de sorte que change assoc i v l change l’association liée à i
dans la liste l par le couple (i, v)
let rec change assoc i v l =
match l with
| [] -> [ (i, v) ]
| (x, )::l when x=i -> (i, v)::l
| z::l -> z::(change assoc i v l)
8/21
Les arbres de préfixes
Structure de données pour représenter des ensembles de
mots .
Par mots, il faut comprendre toute valeur ocaml pouvant être
décomposée comme une suite de lettres :
I
les valeurs de type string sont des mots dont les lettres sont
de type char
I
les entiers de type int sont également des mots dont les
lettres peuvent être simplement les chiffres 0 et 1
I
etc.
9/21
Décomposition
On utilise cette décomposition en lettres pour représenter des
ensembles de mots de la manière suivante :
I
chaque branche est étiquetée par une lettre
I
chaque nœud contient un booléen qui indique si la séquence
de lettres menant de la racine de l’arbre à ce nœud est un mot
appartenant à l’ensemble
10/21
Exemple
L’arbre de préfixes correspondant au dictionnaire {if, in, do, done}
false
i
d
false
false
n
true
f
true
o
true
n
false
e
true
11/21
Pourquoi une telle structure ?
Le temps de recherche d’un élément dans un arbre de préfixes
est borné à la longueur du mot le plus long de cet ensemble,
quelque soit le nombre de mots qu’il contient.
I
Cette propriété est garantie si toutes les feuilles d’un arbre de
préfixes représentent bien un mot de l’ensemble, c’est-à-dire si
elles contiennent toutes une valeur booléenne à vrai.
12/21
L’interface du module Trie
type ’a t
type ’a word = ’a list
val empty : ’a t
val is empty : ’a t -> bool
val mem : ’a word -> ’a t -> bool
val add : ’a word -> ’a t -> ’a t
val remove : ’a word -> ’a t -> ’a t
val inter : ’a t -> ’a t -> ’a t
val union : ’a t -> ’a t -> ’a t
val cardinal : ’a t -> int
I
’a t est le type (abstrait) des arbres de préfixe dont les
lettres sont de type ’a
I
les mots sont représentés par des listes de lettres ’a word
13/21
Implémentation du module Trie
type ’a t =
{ is a word : bool ;
branches : (’a * ’a t) list }
Les valeurs de ce type sont les nœuds des arbres.
I
Le champ is a word contient la valeur booléenne indiquant
la présence d’un mot dans l’arbre.
I
Le champ branches contient les fils d’un nœud. Il s’agit d’une
liste d’associations qui associe des sous-arbres à des lettres.
14/21
Implémentation du module Trie
I
L’arbre de préfixes vide empty est représenté par un arbre
réduit à un unique nœud où le champ is a word vaut false
et branches est une liste vide :
let empty = { is a word = false; branches = [] }
I
La fonction is empty pour tester qu’un arbre de préfixes est
vide est alors définie de la manière suivante :
let is empty t =
not t.is a word && t.branches = []
15/21
Recherche d’un élément
let rec mem x t =
match x with
| [] -> t.is a word
| i::l ->
try
mem l (List.assoc i t.branches)
with Not found -> false
La recherche d’un élément x procède récursivement de la façon
suivante :
I
si le mot x est la liste vide, la recherche se termine en
renvoyant la valeur booléenne associée à la racine de l’arbre t
I
Sinon, la recherche se poursuit récursivement dans le
sous-arbre associé à la branche étiquetée par la première lettre
i du mot x. Dans le cas où cette branche n’existe pas, la
recherche se termine immédiatement sur un échec.
16/21
Insertion d’un élément
let rec add x t =
match x with
| [] -> { t with is a word = true }
| i::l ->
let b =
try List.assoc i t.branches
with Not found -> empty
in
{ t with
branches = change assoc i (add l b) t.branches }
17/21
Insertion d’un élément
L’insertion d’un mot x dans un arbre de préfixes t consiste à
descendre le long de la branche étiquetée par les lettres de x.
I
Si x est vide, on renvoie un arbre t avec un champ is a word
à vrai afin d’indiquer que x appartient désormais à cet
ensemble.
I
Si x est de la forme i::l, on ajoute récursivement l dans le
sous-arbre b associé à lettre i dans t. Si b n’existe pas, on
réalise l’ajout à partir d’un arbre vide. L’insertion se termine
en associant l’arbre ainsi obtenu à la lettre i.
18/21
Suppression d’un élément
let rec remove x t =
match x with
| [] ->
{ t with is a word = false }
| i::l ->
try
let s = remove l (List.assoc i t.branches) in
let new branches =
if is empty s then remove assoc i t.branches
else change assoc i s t.branches
in
{ t with branches = new branches }
with Not found -> t
19/21
Suppression d’un élément
La suppression d’un élément x dans un arbre t procède encore une
fois avec le même parcours récursif que pour l’insertion.
I
La principale subtilité de cette opération est de maintenir la
bonne formation de l’arbre renvoyé. Si x est la liste vide, il est
supprimé de t simplement en passant le champ is a word à
faux.
I
Sinon, x est de la forme i::l on procède ainsi :
I
I
I
On commence par supprimer récursivement le reste du mot l
dans le sous-arbre associé à i. Si ce sous-arbre n’existe pas, la
fonction se termine en renvoyant directement t.
Puis, selon que l’arbre s ainsi obtenu est vide ou non, on
supprime la branche associée à i dans t ou on lui associe s.
Cela permet de garantir qu’aucune branche de t ne pointe vers
un arbre vide.
Enfin, la fonction se termine en associant ces nouvelles
branches à t.
20/21
Cardinal
let cardinal t =
let n = ref 0 in
let rec count t =
if t.is a word then incr n;
List.iter (fun ( , t) -> count t) t.branches
in
count t;
!n
I
La fonction cardinal renvoie le nombre de mot contenu dans
un arbre de préfixe.
I
Il s’agit simplement d’un parcours en profondeur d’un arbre
pendant lequel on compte le nombre de nœuds ayant un
champ is a word à true.
21/21
Téléchargement