Sin duda, es posible construir una árbol con nodos compartidos. Por ejemplo, podríamos definir:
data Tree a = Leaf a | Node (Tree a) (Tree a)
y luego cuidadosamente construir un valor de este tipo como en
tree :: Tree Int
tree = Node t1 t2
where
t1 = Node t3 t4
t2 = Node t4 t5
t3 = Leaf 2
t4 = Leaf 3
t5 = Leaf 5
para lograr el intercambio de subárboles (en este caso t4
).
Sin embargo, ya que esta forma de compartir no es observable en Haskell, es muy difícil de mantener: por ejemplo, si se recorre un árbol de volver a etiquetar sus hojas
relabel :: (a -> b) -> Tree a -> Tree b
relabel f (Leaf x) = Leaf (f x)
relabel f (Node l r) = Node (relabel f l) (relabel f r)
que compartir suelto. Además, cuando se hace un cálculo de abajo hacia arriba, como
sum :: Num a => Tree a -> a
sum (Leaf n) = n
sum (Node l r) = sum l + sum r
al final no tomar ventaja de compartir y posiblemente duplicar el trabajo.
Para superar estos problemas, puede hacer que compartir explícito (y por lo tanto observable) mediante la codificación de los árboles de una manera gráfica como:
type Ptr = Int
data Tree' a = Leaf a | Node Ptr Ptr
data Tree a = Tree {root :: Ptr, env :: Map Ptr (Tree' a)}
El árbol del ejemplo anterior ahora se puede escribir como
tree :: Tree Int
tree = Tree {root = 0, env = fromList ts}
where
ts = [(0, Node 1 2), (1, Node 3 4), (2, Node 4 5),
(3, Leaf 2), (4, Leaf 3), (5, Leaf 5)]
El precio a pagar es que las funciones que atraviesan estas estructuras son algo engorroso para escribir, pero ahora se puede definir, por ejemplo, una función que conserva el reetiquetado compartir
relabel :: (a -> b) -> Tree a -> Tree b
relabel f (Tree root env) = Tree root (fmap g env)
where
g (Leaf x) = Leaf (f x)
g (Node l r) = Node l r
y una función sum
que no duplique el trabajo cuando el árbol ha compartido nodos:
sum :: Num a => Tree a -> a
sum (Tree root env) = fromJust (lookup root env')
where
env' = fmap f env
f (Leaf n) = n
f (Node l r) = fromJust (lookup l env') + fromJust (lookup r env')
No acabo de conseguir el propósito del árbol. ¿Sería posible usar una lista también? Si sus hojas están etiquetadas de izquierda a derecha con los valores v1 a v5, ¿podría representar su árbol por una lista [v1, ..., v5]? Por ejemplo, para buscar un valor, solo debe contar la cantidad de pasos correctos en su ruta para identificar el valor correcto en la lista. En otras palabras, si etiqueta una hoja, ¿desea mantener la estructura de uso compartido? Es decir, si etiquetamos la hoja a la izquierda, izquierda, izquierda, derecha, ¿se supone que la hoja a la izquierda, izquierda, derecha e izquierda también cambiará? –
Ene, también quiero etiquetar los nodos interiores, en función de los valores en las hojas, y luego buscar esta información de manera eficiente en un punto futuro del programa. –