Tout d'abord, les listes sont une sorte d'arbres. Si nous représentons une liste sous forme de liste liée , il s'agit simplement d'un arbre dont chaque nœud a 1 ou 0 descendants.
Les arbres d'analyse ne sont qu'une utilisation des arbres comme structure de données. Les arbres ont de nombreuses applications en informatique, notamment le tri, la mise en œuvre de cartes, les tableaux associatifs, etc.
En général, la liste, les arbres, etc. sont des structures de données récursives: chaque nœud contient des informations et une autre instance de la même structure de données. Le pliage est une opération sur toutes ces structures qui transforme récursivement les nœuds en valeurs "ascendantes". Le dépliage est le processus inverse, il convertit les valeurs en nœuds "de haut en bas".
Pour une structure de données donnée, on peut construire mécaniquement leurs fonctions de pliage et de dépliage.
Par exemple, prenons des listes. (Je vais utiliser Haskell pour les exemples car il est tapé et sa syntaxe est très propre.) La liste est soit une fin, soit une valeur et une "queue".
data List a = Nil | Cons a (List a)
Imaginons maintenant que nous plions une liste. À chaque étape, nous avons le nœud actuel à plier et nous avons déjà plié ses sous-nœuds récursifs. Nous pouvons représenter cet état comme
data ListF a r = NilF | ConsF a r
où r
est la valeur intermédiaire construite en repliant la sous-liste. Cela nous permet d'exprimer une fonction de pliage sur des listes:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Nous convertissons List
en ListF
repliant récursivement sa sous-liste, puis utilisons une fonction définie sur ListF
. Si vous y réfléchissez, ce n'est qu'une autre représentation de la norme 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
On peut construire unfoldList
de la même manière:
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')
Encore une fois, c'est juste une autre représentation de unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Notez que Maybe (a, r)
c'est isomorphe à ListF a r
.)
Et nous pouvons aussi construire une fonction de déforestation:
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)
Il supprime simplement l'intermédiaire List
et fusionne les fonctions de pliage et de dépliage.
La même procédure peut être appliquée à toute structure de données récursive. Par exemple, un arbre dont les nœuds peuvent avoir 0, 1, 2 ou descendants avec des valeurs sur des nœuds à 1 ou 0 branches:
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)
Bien sûr, nous pouvons créer deforestTree
aussi mécaniquement qu'auparavant.
(Habituellement, nous exprimons treeFold
plus facilement:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Je vais laisser de côté les détails, j'espère que le motif est évident.
Voir également: