Есть (по крайней мере?) Два важные обобщения понятия складной список. Первое, более мощное, понятие - это катамаморфизм . Ответ anyOldOrder
Даниэля Вагнера следует этой схеме.
Но для вашей конкретной проблемы понятие катаморфизма - это немного больше энергии, чем вам нужно. Второе, более слабое, понятие относится к контейнеру Foldable
. Foldable
выражает идею контейнера, элементы которого могут быть выровнены вместе, используя операцию произвольного Monoid
. Вот такая уловка:
{-# LANGUAGE DeriveFoldable #-}
-- Note that for this trick only I've
-- switched the order of the Node fields.
data BinaryTree a =
Node (BinaryTree a) a (BinaryTree a)
| Leaf
deriving (Show, Foldable)
index :: [a] -> Int -> Maybe a
[] `index` _ = Nothing
(x : _) `index` 0 = Just x
(_ : xs) `index` i = xs `index` (i - 1)
(!?) :: Foldable f => Int -> f a -> Maybe a
xs !? i = toList xs `index` i
Тогда вы можете просто использовать !?
индексировать в дереве!
Этот трюк мило, и на самом деле, вытекающих Foldable
это огромное удобство, но это не поможет вам понять что-либо. Начну с того, что вы можете точно и эффективно определить treeToList
, не используя Foldable
.
treeToList :: BinaryTree a -> [a]
treeToList t = treeToListThen t []
Магия находится в функции treeToListThen
. treeToListThen t more
преобразует t
в список и добавляет список more
в конец результата. Это небольшое обобщение оказывается тем, что требуется для эффективного преобразования в список.
treeToListThen :: BinaryTree a -> [a] -> [a]
treeToListThen Leaf more = more
treeToListThen (Node v l r) more =
treeToListThen l $ v : treeToListThen r more
Вместо получения заказовМои обход левого поддерева, а затем добавляя все остальное, мы говорим левый обход какой наклеить на конце, когда это сделано! Это позволяет избежать потенциально серьезной неэффективности повторной конкатенации списков, которая может превратить вещи O (n^2) в плохих случаях.
Возвращаясь к Foldable
понятия, превращая вещи в списки является частным случаем foldr
:
toList = foldr (:) []
Так как мы можем реализовать foldr
для деревьев? Он заканчивает тем, что несколько похож на то, что мы сделали с toList
:
foldrTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldrTree _ n Leaf = n
foldrTree c n (Node v l r) = foldrTree c rest l
where
rest = v `c` foldrTree c n r
То есть, когда мы идем вниз по левой стороне, мы говорим, что когда это будет сделано, он должен иметь дело с текущим узлом и его правом ребенка ,
В настоящее время foldr
не является наиболее фундаментальной операцией Foldable
; что на самом деле
foldMap :: (Foldable f, Monoid m)
=> (a -> m) -> f a -> m
Это можно реализовать с помощью foldr
foldMap
, в некоторой степени сложной манере, используя своеобразный Monoid
.Я не хочу перегружать вас данными об этом прямо сейчас, если вы не спросите (но вы должны посмотреть определение по умолчанию foldr
в Data.Foldable
). Вместо этого, я покажу, как foldMap
можно определить с помощью Daniel Вагнер anyOldOrder
:
instance Foldable BinaryTree where
foldMap f = anyOldOrder bin mempty where
bin lres v rres = lres <> f v <> rres
'возьмем к (Симметричного е [] дерево)', где 'f' это функция, которая создает список, содержащий Симметричный обход дерева. – chepner
@chepner Я думаю, что 'inorder', как написано, будет всегда пересекать все дерево. Очевидно, было бы желательно написать «лучшую» функцию 'inorder', которая может избежать этого, когда ее' f' достаточно ленив; вопрос здесь, как я вижу, о том, как. –
@chepner, который не сломается раньше. Я думал, что это тоже сработает, и это будет оцениваться лениво, но, похоже, это не так, когда я «взял 1 (inorder (:) [] testTree)». – m0meni