1/21 Programmation fonctionnelle Notes de cours Cours 5 31 Octobre 2012 Sylvain Conchon sylvain.conchon@lri.fr
2/21 Le programme de cette semaine Le mode T9 des téléphones portables
3/21 Les notions abordées avec ce programme Exceptions Dictionnaires Arbres de préfixes
4/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". 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. Si l exception n est pas rattrapée (voir ci-après), le programme se termine.
5/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) Une exception est définie à l aide du mot-clé exception. Comme pour les constructeurs, elle commence par une majuscule et elle peut attendre des arguments. On lève une exception à l aide de la fonction raise. Ce mécanisme est transparent au typage.
6/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 Une exception peut être rattrapée à l aide de la construction try-with qui permet de définir des gestionnaires d exceptions. Comme pour un pattern-matching, un gestionnaire permet de récupérer les arguments associés aux exceptions.
7/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, 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 la fonction List.remove assoc supprime la première association liée à une clef a -> ( a * b) list -> ( a * b) list
8/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)
9/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 : les valeurs de type string sont des mots dont les lettres sont de type char les entiers de type int sont également des mots dont les lettres peuvent être simplement les chiffres 0 et 1 etc.
10/21 Décomposition On utilise cette décomposition en lettres pour représenter des ensembles de mots de la manière suivante : chaque branche est étiquetée par une lettre 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
11/21 Exemple L arbre de préfixes correspondant au dictionnaire {if, in, do, done} i false d n false f false o true true true n false e true
12/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. 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.
13/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 a t est le type (abstrait) des arbres de préfixe dont les lettres sont de type a les mots sont représentés par des listes de lettres a word
14/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. Le champ is a word contient la valeur booléenne indiquant la présence d un mot dans l arbre. 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.
15/21 Implémentation du module Trie 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 = [] } 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 = []
16/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 : 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 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.
17/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 }
18/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. 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. 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.
19/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
20/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. 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. Sinon, x est de la forme i::l on procède ainsi : 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.
21/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 La fonction cardinal renvoie le nombre de mot contenu dans un arbre de préfixe. 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.