Innanzitutto, le liste sono una specie di alberi. Se rappresentiamo un elenco come un elenco collegato , è solo un albero il cui ogni nodo ha 1 o 0 discendenti.
Gli alberi di analisi sono solo un utilizzo degli alberi come struttura di dati. Gli alberi hanno molte applicazioni nell'informatica, inclusi smistamento, implementazione di mappe, array associativi, ecc.
In generale, elenco, alberi, ecc. Sono strutture di dati ricorsive: ogni nodo contiene alcune informazioni e un'altra istanza della stessa struttura di dati. La piegatura è un'operazione su tutte queste strutture che trasforma ricorsivamente i nodi in valori "dal basso verso l'alto". Il dispiegamento è il processo inverso, converte i valori in nodi "top down".
Per una data struttura di dati, possiamo costruire meccanicamente le loro funzioni di piegatura e spiegamento.
Ad esempio, prendiamo le liste. (Userò Haskell per gli esempi mentre viene digitato e la sua sintassi è molto pulita.) L'elenco è una fine o un valore e una "coda".
data List a = Nil | Cons a (List a)
Ora immaginiamo di piegare un elenco. Ad ogni passo, abbiamo il nodo corrente da piegare e abbiamo già piegato i suoi sotto-nodi ricorsivi. Possiamo rappresentare questo stato come
data ListF a r = NilF | ConsF a r
dove r
è il valore intermedio costruito piegando la lista secondaria. Questo ci consente di esprimere una funzione di piegatura su liste:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Convertiamo List
in ListF
ricorsivamente ripiegando sua sottolista e quindi utilizzare una funzione definita ListF
. Se ci pensate, questa è solo un'altra rappresentazione dello standard 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
Possiamo costruire unfoldList
allo stesso modo:
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')
Ancora una volta, è solo un'altra rappresentazione di unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Si noti che Maybe (a, r)
è isomorfo a ListF a r
.)
E possiamo anche costruire una funzione di deforestazione:
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)
Lascia semplicemente fuori l'intermedio List
e fonde insieme le funzioni di piegatura e spiegamento.
La stessa procedura può essere applicata a qualsiasi struttura di dati ricorsiva. Ad esempio, un albero i cui nodi possono avere 0, 1, 2 o discendenti con valori su nodi con ramificazione 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)
Certo, possiamo creare deforestTree
meccanicamente come prima.
(Di solito, esprimiamo treeFold
più convenientemente come:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Lascerò fuori i dettagli, spero che lo schema sia evidente.
Guarda anche: