Paradigmas de Programación Práctica 3 Árboles estrictamente binarios En el módulo Fb_tree, escrito en ocaml, está definido el tipo de dato 'a fb_tree que sirve para representar “árboles binarios llenos” (full binary trees) con nodos etiquetados con valores de tipo 'a. Los árboles “binarios llenos” o “estrictamente binarios” o “propiamente binarios” son árboles binarios en los que de todo nodo, que no sea hoja, “cuelgan dos ramas”. No hay “árboles estrictamente binarios” vacíos; los más sencillos tienen un sólo nodo que es raíz y hoja a la vez. El módulo Fb_tree tiene la siguiente interfaz: type 'a fb_tree val string_of_tree : ('a -> string) -> 'a fb_tree -> string exception Branches val single : 'a -> 'a fb_tree val comp : 'a -> 'a fb_tree * 'a fb_tree -> 'a fb_tree val root : 'a fb_tree -> 'a val branches : 'a fb_tree -> 'a fb_tree * 'a fb_tree La función string_of_tree sirve para “visualizar como string” el contenido de un árbol. Hay que pasarle como parámetro una función que permita convertir a strings las etiquetas de los nodos. Esta función se ha incluido en el módulo sólo a efectos de facilitar la “visualización” de árboles para la realización de pruebas y no debe utilizarse en los desarrollos que se pedirán a continuación. La función single construye un árbol-hoja con una etiqueta dada. La función comp contruye un árbol a partir de una etiqueta para la raíz y de un par de árboles que serán sus ramas. La función root devuelve el valor (etiqueta) de la raíz de un árbol. La función branches devuelve las ramas de un árbol. Si se aplica a un árbol que no tiene rama (un árbolhoja) se activa la excepción Branches. Se pide definir en ocaml las siguientes funciones: val is_single : 'a Fb_tree.fb_tree -> bool (* identifica árboles-hoja *) val l_branch : 'a Fb_tree.fb_tree -> 'a Fb_tree.fb_tree (* rama izquierda *) val r_branch : 'a Fb_tree.fb_tree -> 'a Fb_tree.fb_tree (* rama derecha *) val size : 'a Fb_tree.fb_tree -> int (* número de nodos *) val height : 'a Fb_tree.fb_tree -> int (* altura *) val preorder : 'a Fb_tree.fb_tree -> 'a list val postorder : 'a Fb_tree.fb_tree -> 'a list val inorder : 'a Fb_tree.fb_tree -> 'a list val leafs : 'a Fb_tree.fb_tree -> 'a list (* lista de hojas *) val mirror : 'a Fb_tree.fb_tree -> 'a Fb_tree.fb_tree (* imagen especular *) val treemap : ('a -> 'b) -> 'a Fb_tree.fb_tree -> 'b Fb_tree.fb_tree (* aplica una función a todos los nodos *) val is_perfect : 'a Fb_tree.fb_tree -> bool (* un árbol lleno es perfecto si todas sus hojas están en el mismo nivel *) val is_complet : 'a Fb_tree.fb_tree -> bool (* un árbol es completo si todo nivel, excepto quizás el último, está lleno y todos los nodos del último nivel están lo más a la izquierda posible *) Para utilizar el módulo Fb_tree puede cargarse el archivo fb_tree.cmo desde el compilador interactivo ocaml, con la directiva #load “fb.tree.cmo”;; El archivo “fb_tree.cmi” debe estar presente en el mismo directorio. Puede evitarse el tener que calificar los nombre de los valores con el nombre del módulo si se ejecuta la sentencia open Fb_tree;; Para definir la función is_single puede interceptarse la excepción “Branches” utilizando una construcción “try..with...” Por ejemplo: let is_single t = try let _ = branches t in false with Branches -> true;; que también puede escribirse: let is_single t = try branches t ; false with Branches -> true;; pues <exp1>; <exp2> es una expresión que se evalúa en ocaml evaluando primero la expresión <exp1> y a continuación la expresión <exp2> y cuyo valor es el de <exp2>. A continuación se transcriben unos ejemplos de uso de las funciones que hay que definir con los resultados que deberían dar si las definiciones son correctas. # let h x = single x;; val h : 'a -> 'a Fb_tree.fb_tree = <fun> # let h1 = h 1 and h2 = h 2 and h3 = h 3;; val h1 : int Fb_tree.fb_tree = <abstr> val h2 : int Fb_tree.fb_tree = <abstr> val h3 : int Fb_tree.fb_tree = <abstr> # let ta = comp 1 (h 2, h 3);; val ta : int Fb_tree.fb_tree = <abstr> # let tb = comp 4 (ta, h 5);; val tb : int Fb_tree.fb_tree = <abstr> # let tc = comp 4 (ta, ta);; val tc : int Fb_tree.fb_tree = <abstr> # let td = comp 4 (h 3, ta);; val td : int Fb_tree.fb_tree = <abstr> # let te = comp 6 (tb, h 0);; val te : int Fb_tree.fb_tree = <abstr> # let print = string_of_tree string_of_int;; val print : int Fb_tree.fb_tree -> string = <fun> # print te;; - : string = "(6 (4 (1 (2) (3)) (5)) (0))" # l_branch h1;; Exception: Fb_tree.Branches. # r_branch h2;; Exception: Fb_tree.Branches. # is_single h3;; - : bool = true # is_single ta;; - : bool = false # is_single (l_branch ta);; - : bool = true # let l = [h1; h2; h3; ta; tb; tc; td; te];; val l : int Fb_tree.fb_tree list = [<abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>] # List.map root l;; - : int list = [1; 2; 3; 1; 4; 4; 4; 6] # List.map size l;; - : int list = [1; 1; 1; 3; 5; 7; 5; 7] # List.map height l;; - : int list = [1; 1; 1; 2; 3; 3; 3; 4] # preorder tb;; - : int list = [4; 1; 2; 3; 5] # preorder te;; - : int list = [6; 4; 1; 2; 3; 5; 0] # inorder tb;; - : int list = [2; 1; 3; 4; 5] # postorder tb;; - : int list = [2; 3; 1; 5; 4] # let et= mirror te;; val et : int Fb_tree.fb_tree = <abstr> # preorder et;; - : int list = [6; 0; 4; 5; 1; 3; 2] # print et;; - : string = "(6 (0) (4 (5) (1 (3) (2))))" # leafs h1;; - : int list = [1] # leafs tb;; - : int list = [2; 3; 5] # leafs te;; - : int list = [2; 3; 5; 0] # print (treemap (function x -> x * x) td);; - : string = "(16 (9) (1 (4) (9)))" # List.map is_perfect l;; - : bool list = [true; true; true; true; false; true; false; false] # List.map is_complet l;; - : bool list = [true; true; true; true; true; true; false; false] Para hacer estas pruebas con mayor comodidad, podrían meterse todos estos ejemplos de uso en un archivo “test.ml” y todas las definiciones del ejercicio en un archivo “fb_tree_plus.ml” y compilarlo como un módulo con ocamlc -c fb_tree_plus.ml Después se puede arrancar el ocaml, cargar el módulo, abrirlo, y evaluar el archivo de pruebas “test.ml” con la directiva #use “test.ml”;; La salida debería coincidir exactamente con: val h : 'a -> 'a Fb_tree.fb_tree = <fun> val h1 : int Fb_tree.fb_tree = <abstr> val h2 : int Fb_tree.fb_tree = <abstr> val h3 : int Fb_tree.fb_tree = <abstr> val ta : int Fb_tree.fb_tree = <abstr> val tb : int Fb_tree.fb_tree = <abstr> val tc : int Fb_tree.fb_tree = <abstr> val td : int Fb_tree.fb_tree = <abstr> val te : int Fb_tree.fb_tree = <abstr> val print : int Fb_tree.fb_tree -> string = <fun> - : string = "(6 (4 (1 (2) (3)) (5)) (0))" - : bool = true - : bool = false - : bool = true val l : int Fb_tree.fb_tree list = [<abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>; <abstr>] - : int list = [1; 2; 3; 1; 4; 4; 4; 6] - : int list = [1; 1; 1; 3; 5; 7; 5; 7] - : int list = [1; 1; 1; 2; 3; 3; 3; 4] - : int list = [4; 1; 2; 3; 5] - : int list = [6; 4; 1; 2; 3; 5; 0] - : int list = [2; 1; 3; 4; 5] - : val - : - : - : - : - : - : - : - : int list = [2; 3; 1; 5; 4] et : int Fb_tree.fb_tree = <abstr> int list = [6; 0; 4; 5; 1; 3; 2] string = "(6 (0) (4 (5) (1 (3) (2))))" int list = [1] int list = [2; 3; 5] int list = [2; 3; 5; 0] string = "(16 (9) (1 (4) (9)))" bool list = [true; true; true; true; false; true; false; false] bool list = [true; true; true; true; true; true; false; false]