2011-12-07 13 views
16

quisiera representar un "árbol" de la siguiente forma en Haskell:cómo representar árbol con el intercambio en Haskell

/\        
    /\/\ 
/\/\/\ 
/\/\/\/\ 
` ` ` ` ` 

/y \ son las ramas y las hojas `. Puede ver que a partir de cualquier nodo, siguiendo la ruta de la izquierda, la derecha lo lleva al mismo nodo que sigue el camino correcto y luego a la izquierda. Debería poder etiquetar las hojas, aplicar una función de las dos descendencias en cada nodo y propagar esta información a la raíz en O (n^2) tiempo. Mis esfuerzos ingenuos me están dando un tiempo de ejecución exponencial. ¿Algún consejo?

+0

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á? –

+1

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. –

Respuesta

20

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') 
+3

Gracias Stefan! Tu primer formulario fue el que tuve inicialmente, y cuando afirmas que es difícil mantener el intercambio. Esperaba tener una versión que no requiriera etiquetas explícitas (Ints en su caso) pero quizás eso sea imposible. –

+6

En el Día de FP holandés de este año, he hablado sobre cómo obtener lo mejor de ambos mundos: todavía escribir sus funciones como si estuviera recorriendo árboles (usando catamorfismos y similares), mientras tiene los beneficios del intercambio observable. Las diapositivas para esa charla se pueden encontrar en mi sitio web: http://www.holdermans.nl/talks/assets/nlfp11.pdf. Se está preparando un documento sobre este tema. –

+0

@dblhelix - un par de años después, ¿se materializó alguna vez ese documento? – ajp

2

Tal vez puede representar simplemente como una lista de hojas y aplicar el nivel de funciones de nivel hasta que esté abajo a un valor, es decir algo como esto:

type Tree a = [a] 

propagate :: (a -> a -> a) -> Tree a -> a 
propagate f xs = 
    case zipWith f xs (tail xs) of 
    [x] -> x 
    xs' -> propagate f xs' 
+0

Ciertamente, pero también me gustaría mantener los nodos intermedios disponibles para la inspección y navegar de manera eficiente de un nodo a sus descendientes. –

Cuestiones relacionadas