По-перше, списки - це різновид дерев. Якщо ми представляємо список як пов'язаний список , це просто дерево , кожен вузол якого має 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
)
Я залишу без деталей, сподіваюся, що шаблон очевидний.
Дивіться також: