| Votre opinion sur le cours de ce matin : | Votre opinion sur le TD de la semaine dernière : |
L'objectif de ce TD est d'implémenter de façon efficace le parcours d'arbres binaires à l'aide d'itérateurs. L'implémentation sera réalisée en OCaml.
'a tree des arbres binaires dont
les nœuds internes portent un élément de type 'a et dont
les feuilles ne portent aucune information. (On
notera Node le constructeur des nœuds internes et Empty
le constructeur des feuilles.)
a1 ci-dessous :
elements : 'a tree -> 'a list
qui renvoie la liste des éléments rencontrés lors d'un parcours
infixe. L'assertion suivante devra être vérifiée :
let () =
assert (elements a1 = [2; 3; 5; 6; 8; 12; 15])
fold : ('a -> 'b -> 'a) -> 'a -> 'b tree ->
'a. Si la fonction f : 'a -> 'b -> 'a, appliquée
à un état et à un élément, renvoie un nouvel état,
alors l'appel fold f s t
doit appliquer la fonction f successivement à tous les
éléments de l'arbre t, dans l'ordre infixe,
à partir de l'état initial s,
et doit renvoyer l'état final obtenu à l'issue de l'itération.
fold pour définir une
fonction size : 'a tree -> int qui calcule le nombre de
nœuds internes d'un arbre. L'assertion suivante doit réussir :
let () =
assert (size a1 = 7)
fold pour redéfinir la
fonction elements. Tester cette nouvelle implémentation :
let () =
assert (elements a1 = [2; 3; 5; 6; 8; 12; 15])
Comparer la complexité de ces deux définitions de elements.
Déposez votre fichier :
Dans la suite, on supposera que nos arbres sont bien ordonnés,
c'est-à-dire que le résultat de elements est toujours une
liste triée de façon croissante, comme dans l'exemple a1
ci-dessus. En utilisant une variante de ces arbres, on peut par exemple
stocker efficacement les éléments d'un (multi)ensemble.
elements, définir une fonction equal : 'a tree -> 'a tree -> bool qui
détermine si deux arbres (bien ordonnés) contiennent les mêmes éléments
(lorsqu'on les parcourt dans l'ordre infixe). Par exemple, en
notant a2 et a3 les arbres
a2 = |
a3 = |
let () =
assert (equal a1 a1);
assert (equal a2 a3);
assert (not (equal a1 a2))
Notez que, bien qu'ayant des formes différentes, ces deux arbres sont
considérés comme égaux car ils représentent les mêmes ensembles
d'éléments.
Déposez votre fichier :
La fonction de comparaison ci-dessus n'est pas vraiment efficace. Elle construit toujours les deux listes de tous les éléments des deux arbres, alors que si (par exemple) les premiers éléments des deux arbres diffèrent, on pourrait espérer détecter cela en temps constant. Ainsi, si les deux arbres suivants ont un million d'éléments chacun,
type 'a iterator = ('a * 'a tree) list
iterator : 'a tree -> 'a iterator qui
crée un itérateur à partir d'un arbre.
Il reste tout l'arbre à parcourir et l'itérateur
est donc constitué de l'intégralité de la branche gauche de
l'arbre.
On pourra introduire une fonction intermédiaire de type
'a tree -> 'a iterator -> 'a iterator qui renvoie
un itérateur parcourant les éléments d'un arbre (premier
argument) puis tous les éléments d'un itérateur donné (second
argument).
next : 'a iterator -> ('a * 'a iterator)
option qui renvoie le prochain élément d'un itérateur ainsi
que l'itérateur correspondant à la suite du parcours. La fonction devra
renvoyer None lorsque le parcours est terminé.
elements : 'a tree -> 'a list en
utilisant un itérateur et la tester :
let () =
assert (elements Empty = []);
assert (elements a1 = [2; 3; 5; 6; 8; 12; 15]);
assert (elements a2 = [2; 3]);
assert (elements a3 = [2; 3])
elements ci-dessus) redéfinir la
fonction equal : 'a tree -> 'a tree -> bool. La fonction
devra renvoyer false dès que deux éléments distincts sont
trouvés (elle s'arrêtera donc à la première étape sur l'exemple des deux
arbres de taille un million ci-dessus). La tester :
let () =
assert (equal a1 a1);
assert (equal a2 a3);
assert (not (equal a1 a2))
Déposez votre fichier :
Nous avons mentionné que les arbres bien ordonnés (de type 'a
tree) sont souvent utilisés pour représenter des ensembles de
valeurs de type 'a, pour un type 'a
quelconque. En particulier, rien ne nous empêche de manipuler des
ensembles d'ensembles de valeurs, représentés par des valeurs de
type 'a tree tree. Cependant, pour comparer les éléments d'un
arbre, nous avons jusqu'à présent utilisé l'opération de comparaison
standard de OCaml (=), alors que l'on souhaiterait utiliser
la comparaison définie ci-dessus pour comparer les étiquettes d'un arbre
de type 'a tree tree.
Afin de régler ce problème, nous allons encapsuler les fonctions définies à la partie 3 dans un foncteur dont le paramètre va permettre de spécifier l'ordre que nous souhaitons considérer sur les éléments d'un arbre.
module type EqualityType =
sig
type t
val equal : t -> t -> bool
end
des modules contenant la définition d'un type (t) et d'une
fonction de comparaison sur ce type (equal).
module Tree(ET : EqualityType) =
struct
type element = ET.t
(* à compléter... *)
end
qui sera de type
module Tree :
functor (ET : EqualityType) ->
sig
type element = ET.t
type t = element tree
type iterator = (element * t) list
val iterator : t -> iterator
val next : iterator -> (element * iterator) option
val equal : t -> t -> bool
end
permettant de créer un module offrant des fonctions sur les arbres en
utilisant la fonction du module ET en paramètre pour
comparer ses éléments.
TT des arbres
d'arbres d'entiers et le tester :
let () =
let a23 = Node (Empty, a2, Node (Empty, a3, Empty)) in
let a32 = Node (Empty, a3, Node (Empty, a2, Empty)) in
let a32' = Node (Node (Empty, a3, Empty), a2, Empty) in
assert (TT.equal a23 a32);
assert (TT.equal a23 a32')
Déposez votre fichier :
La définition d'un itérateur, telle que nous l'avons considérée jusqu'à présent, nécessite de définir une structure de donnée spécialisée, qu'il n'est pas toujours aisé d'inventer, et qui est différente pour chaque façon de parcourir l'arbre. Nous allons maintenant considérer une autre façon d'implémenter l'itérateur pour le parcours infixe, en utilisant le mécanisme de clôtures d'OCaml. L'idée est de déclarer le type des itérateurs par
type 'a iterator = unit -> ('a * 'a iterator) option
et de considérer qu'un itérateur i est une fonction telle
que i () renvoie la valeur du prochain élément dans le
parcours ainsi que le nouvel itérateur (ou None si le parcours
est terminé). Pour des raisons techniques liées au typage, il n'est pas
possible de déclarer ce type directement de cette façon car le
type 'a iterator apparaît à droite de la définition :
# type 'a iterator = unit -> ('a * 'a iterator) option;;
Characters 4-52:
type 'a iterator = unit -> ('a * 'a iterator) option;;
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: The type abbreviation iterator is cyclic
Il est cependant aisé de contourner ce problème en utilisant un type
algébrique avec un unique constructeur (on peut définir récursivement un type
si l'appel récursif est dans un constructeur). On définira donc
type 'a iterator =
| I of (unit -> ('a * 'a iterator) option)
next : 'a iterator -> ('a * 'a iterator)
option.
iterator : 'a tree -> 'a iterator
correspondant au parcours infixe.
elements : 'a tree -> 'a list en
utilisant l'itérateur (comme en 3.3) et vérifier
let () =
assert (elements a1 = [2; 3; 5; 6; 8; 12; 15])
equal : 'a tree -> 'a tree ->
bool (comme en 3.4) et la tester.
'a iterator,
définir des fonctions
iterator_prefix : 'a tree -> 'a iteratoriterator_postfix : 'a tree -> 'a iteratorelements_prefix et
elements_postfix. (On pourra généraliser
elements pour qu'elle prenne iterator
en argument.) Tester avec
let () = assert (elements_prefix a1 = [5; 2; 3; 8; 6; 15; 12]); assert (elements_postfix a1 = [3; 2; 6; 12; 15; 8; 5])
Déposez votre fichier :
Une solution vous est proposée.