Primero, las listas son una especie de árboles. Si representamos una lista como una lista vinculada , es solo un árbol cuyo nodo tiene 1 o 0 descendientes.
Los árboles de análisis son solo una utilización de los árboles como estructura de datos. Los árboles tienen muchas aplicaciones en ciencias de la computación, incluida la clasificación, implementación de mapas, matrices asociativas, etc.
En general, la lista, los árboles, etc. son estructuras de datos recursivas: cada nodo contiene cierta información y otra instancia de la misma estructura de datos. El plegado es una operación sobre todas esas estructuras que transforma los nodos de forma recursiva a valores "de abajo hacia arriba". El despliegue es el proceso inverso, convierte los valores en nodos "de arriba abajo".
Para una estructura de datos dada, podemos construir mecánicamente sus funciones de plegado y desplegado.
Como ejemplo, tomemos listas. (Usaré Haskell para los ejemplos, ya que está escrito y su sintaxis es muy limpia). La lista es un final o un valor y una "cola".
data List a = Nil | Cons a (List a)
Ahora imaginemos que estamos doblando una lista. En cada paso, tenemos que plegar el nodo actual y ya hemos plegado sus subnodos recursivos. Podemos representar este estado como
data ListF a r = NilF | ConsF a r
donde r
es el valor intermedio construido al plegar la sublista. Esto nos permite expresar una función de plegado sobre listas:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Convertimos List
en ListF
plegando recursivamente sobre su sublista y luego usamos una función definida en ListF
. Si lo piensa, esta es solo otra representación del estándar foldr
:
foldr :: (a -> r -> r) -> r -> List a -> r
foldr f z = foldList g
where
g NilF = z
g (ConsF x r) = f x r
Podemos construir unfoldList
de la misma manera:
unfoldList :: (r -> ListF a r) -> r -> List a
unfoldList f r = case f r of
NilF -> Nil
ConsF x r' -> Cons x (unfoldList f r')
De nuevo, es solo otra representación de unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Tenga en cuenta que Maybe (a, r)
es isomorfo a ListF a r
).
Y también podemos construir una función de deforestación:
deforest :: (ListF a r -> r) -> (s -> ListF a s) -> s -> r
deforest f u s = f (map (deforest f u) (u s))
where
map h NilF = NilF
map h (ConsF x r) = ConsF x (h r)
Simplemente omite el intermedio List
y fusiona las funciones de plegado y desplegado.
El mismo procedimiento se puede aplicar a cualquier estructura de datos recursiva. Por ejemplo, un árbol cuyos nodos pueden tener 0, 1, 2 o descendientes con valores en nodos de ramificación 1 o 0:
data Tree a = Bin (Tree a) (Tree a) | Un a (Tree a) | Leaf a
data TreeF a r = BinF r r | UnF a r | LeafF a
treeFold :: (TreeF a r -> r) -> Tree a -> r
treeFold f (Leaf x) = f (LeafF x)
treeFold f (Un x r) = f (UnF x (treeFold f r))
treeFold f (Bin r1 r2) = f (BinF (treeFold f r1) (treeFold f r2))
treeUnfold :: (r -> TreeF a r) -> r -> Tree a
treeUnfold f r = case f r of
LeafF x -> Leaf x
UnF x r -> Un x (treeUnfold f r)
BinF r1 r2 -> Bin (treeUnfold f r1) (treeUnfold f r2)
Por supuesto, podemos crear deforestTree
tan mecánicamente como antes.
(Por lo general, nos expresamos treeFold
más convenientemente como:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Dejaré de lado los detalles, espero que el patrón sea obvio.
Ver también: