Во-первых, списки - это своего рода деревья. Если мы представляем список в виде связанного списка , это просто дерево , каждый узел которого имеет 1 или 0 потомков.
Деревья разбора - это просто использование деревьев в качестве структуры данных. Деревья имеют множество приложений в информатике, включая сортировку, реализацию карт, ассоциативные массивы и т. Д.
В общем, список, деревья и т. Д. Являются рекурсивными структурами данных: каждый узел содержит некоторую информацию и другой экземпляр той же структуры данных. Складывание - это операция над всеми такими структурами, которая рекурсивно преобразует узлы в значения «снизу вверх». Развертывание - это обратный процесс, он преобразует значения в узлы «сверху вниз».
Для заданной структуры данных мы можем механически построить их функции свертывания и разворачивания.
В качестве примера возьмем списки. (Я буду использовать Haskell для примеров, так как он напечатан, и его синтаксис очень чистый.) Список - это либо конец, либо значение, либо «хвост».
data List a = Nil | Cons a (List a)
Теперь давайте представим, что мы свернули список. На каждом шаге у нас есть текущий узел, который нужно свернуть, и мы уже свернули его рекурсивные подузлы. Мы можем представить это состояние как
data ListF a r = NilF | ConsF a r
где r
промежуточное значение, построенное путем складывания подсписка. Это позволяет нам выразить функцию сворачивания над списками:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Мы конвертируем List
в ListF
, рекурсивно сворачивая его подсписок, а затем используем функцию, определенную в ListF
. Если вы думаете об этом, это просто еще одно представление стандарта 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
Мы можем построить unfoldList
таким же образом:
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')
Опять же, это просто еще одно представление unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Обратите внимание, что Maybe (a, r)
это изоморфно ListF a r
.)
И мы можем построить функцию обезлесения тоже:
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)
Он просто исключает промежуточное звено List
и объединяет функции складывания и раскладывания.
Та же процедура может быть применена к любой рекурсивной структуре данных. Например, дерево, узлы которого могут иметь 0, 1, 2 или потомков со значениями на 1- или 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)
Конечно, мы можем создавать deforestTree
так же механически, как и раньше.
(Обычно мы выражаемся treeFold
более удобно как:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Я опущу детали, я надеюсь, что картина очевидна.
Смотрите также: