从树

时间:2018-03-01 19:06:00

标签: haskell tree

我已经构建了一个树,我想从中收集所有Leaf类型:

Branch [] (Branch [0] (Leaf [0,1]) (Branch [0] (Leaf [0,2]) (Branch
[0] (Leaf [0,3]) (Leaf [0])))) (Branch [] (Branch [1] (Leaf [1,2])
(Branch [1] (Leaf [1,3]) (Leaf [1]))) (Branch [] (Branch [2] (Leaf
[2,3]) (Leaf [2])) (Branch [] (Leaf [3]) (Leaf []))))

我在上述变量的GHCI(:t)中获得的类型是:

Tree [Int]

数据结构如下:

data Tree a = Empty | Leaf a | Branch a (Tree a) (Tree a)

我正在尝试隔离 ONLY 叶子,以便获得:

[ [0,1], [0,2] .. [3], [] ]

我一直在尝试对结果运行filter,但这不起作用。我已经尝试使用函数Data.Foldable.toList,但是它也会拉入所有分支并导致一个包含多个重复项的大型列表列表,并且无法判断它是分支还是叶子。

3 个答案:

答案 0 :(得分:3)

虽然存在其他方法,但最简单的方法可能是使用递归。这里的基本案例是EmptyLeaf。如果是Empty,我们会返回一个空列表,如果是Leaf,我们可以返回一个带有一个元素的列表:包含在叶子中的列表,所以:

leave_elements :: Tree a -> [a]
leave_elements Empty = []
leave_elements (Leaf x) = [x]
leave_elements (Branch ...) = ...

我们仍然需要填写Branch个案,在这里我们看到构造函数中的三个元素:a,我们可以忽略它们,以及两个子树。我们可以在子树上递归地递归调用leave_elements,并附加子树叶子的数据列表。例如:

leave_elements :: Tree a -> [a]
leave_elements Empty = []
leave_elements (Leaf x) = [x]
leave_elements (Branch _ l r) = leave_elements l ++ leave_elements r

对于您给定的样本树,这会产生:

Prelude> leave_elements (Branch [] (Branch [0] (Leaf [0,1]) (Branch [0] (Leaf [0,2]) (Branch [0] (Leaf [0,3]) (Leaf [0])))) (Branch [] (Branch [1] (Leaf [1,2]) (Branch [1] (Leaf [1,3]) (Leaf [1]))) (Branch [] (Branch [2] (Leaf [2,3]) (Leaf [2])) (Branch [] (Leaf [3]) (Leaf [])))))
[[0,1],[0,2],[0,3],[0],[1,2],[1,3],[1],[2,3],[2],[3],[]]

我们还可以通过使用例如递归传递的尾部来提升性能:

leave_elements :: Tree a -> [a]
leave_elements = go []
    where go tl Empty = tl
          go tl (Leaf x) = (x:tl)
          go tl (Branch _ l r) = go (go tl r) l

或者我们可以使用Data.DList

import Data.DList

leave_elements :: Tree a -> [a]
leave_elements = toList . go
    where go Empty = empty
          go (Leaf x) = singleton x
          go (Branch _ l r) = append (go l) (go r)

答案 1 :(得分:1)

一种更高级的技术,可以节省您手动编写和维护递归函数的工作,就是使用通用编程库,例如lensPlated module

以下是Plated的工作原理:您描述了如何识别值 children - 与值本身具有相同类型的直接子结构 - 通过编写Plated class的实例,图书馆的各种高阶函数负责递归查找孩子的孩子等等。对于Tree数据类型,只有Branch构造函数具有子项(左子项和右项),因此这些是我们应用f的唯一位置。

instance Plated (Tree a) where
    plate f (Branch x l r) = Branch x <$> f l <*> f r
    plate f t = pure t

(如果您愿意派生Data,那么您甚至不必撰写plate。)

Plated的{​​{3}}函数现在可以递归搜索树的子项和子项的子项,依此类推,返回一个惰性列表,该列表生成树中的每个节点。它的工作方式大致如下:

universe :: Plated a => a -> [a]
universe t = t : [descendant | child <- toListOf plate t, descendant <- universe child]

因此,要查找所有叶子,您只需要筛选此列表以搜索Leaf构造函数。

leaves :: Tree a -> [a]
leaves t = [x | Leaf x <- universe t]

完成工作!

答案 2 :(得分:0)

正如@BenjaminHodgson在评论中指出的那样,以下解决方案是有效的,但不能在其他类型类中实现一致的实现。例如,Tree Traversable的实例会导致traverse函数的实现,该函数必然会触及Tree的所有元素。我将其留作学习目的,但不要使用此解决方案。

使用Foldable typeclass

import qualified Data.Foldable as F

instance F.Foldable Tree where
    foldMap f Empty = mempty
    foldMap f (Branch x l r) = foldMap f l `mappend`
                               foldMap f r
    foldMap f (Leaf x) = f x

对于tree

Prelude> foldr (\x acc -> x: acc) [] tree
[[0,1],[0,2],[0,3],[0],[1,2],[1,3],[1],[2,3],[2],[3],[]]