在Haskell中将值传播到Data.Tree

时间:2016-05-17 12:40:17

标签: haskell tree functional-programming

使用Data.Tree我可以像这样定义一棵树:

mkTree :: T.Tree Double
mkTree = T.Node 0 [ T.Node 4 []
                  , T.Node 0 [T.Node 5 [], T.Node 4 []]
                  , T.Node 0 [T.Node 2 [], T.Node 1 []]
                  ]

哪个转移到此:

0.0
|
+- 4.0
|
+- 0.0
|  |
|  +- 5.0
|  |
|  `- 4.0
|
`- 0.0
   |
   +- 2.0
   |
   `- 1.0

我现在想要转换树,以便每个T.Node现在包含其子项的总和(或其他一些函数):

16.0
|
+- 4.0
|
+- 9.0
|  |
|  +- 5.0
|  |
|  `- 4.0
|
`- 3.0
   |
   +- 2.0
   |
   `- 1.0

问题是我无法使用fmap访问节点的子节点。到目前为止我所拥有的功能是:

propagate :: Num a => T.Tree a -> T.Tree a
propagate (T.Node x []) = T.Node x []
propagate (T.Node _ ts) = T.Node (sum $ map gather ts) (map propagate ts)

gather :: Num a => T.Tree a -> a
gather (T.Node n []) = n
gather (T.Node _ ts) = sum $ map gather ts

但这似乎太复杂了,特别是如果我用另一个函数替换sum。也许有更好的方法可以使用FoldableTraversable

来完成此操作

2 个答案:

答案 0 :(得分:3)

我认为Foldable没有足够的Tree结构来做你想做的事情。 Traversable可能,但似乎相对棘手的做法;我想我更喜欢实现像这样的递归模式:

foldTree :: (a -> [b] -> b) -> Tree a -> b
foldTree f = go where
    go (Node value children) = f value (map go children)

然后你可以实现你的求和操作

sums :: Num a => Tree a -> Tree a
sums = foldTree (\val children -> Node (sum (val:map rootLabel children)) children)

甚至使用NumSemigroup代替sconcat(:|)sum推广到(:)

答案 1 :(得分:1)

您希望为每个节点操作所有子树(包括节点标签),然后,一个有用的功能可能

subtrees :: Tree a -> Tree (Tree a)
subtrees n@(Node _ xs) = Node n (map subtrees xs)

现在,您可以将任何树函数应用于任何树木树

sumSubtree :: Num a => Tree a -> Tree a
sumSubtree = fmap sum . subtrees

带有所需的结果。

正如丹尼尔所说,这个sumSubtree是低效率的,因为从叶子到根的总和具有最佳子结构。

但是,不存在独特的解决方案,请查看下一个折叠版本

foldTree :: (a → Forest b → b) → Tree a → Tree b
foldTree f (Node x xs) = Node (f x xs') xs'
                         where xs' = foldTree f ↥ xs
仅当f不需要根​​和前一个分支来计算某个分支值(例如总和问题)时,

现在才是最佳的。但是(例如)如果某个密钥存储在每个节点上,这个求和实现也会效率低下。

(使用前一个折叠,总和问题可能写成foldTree (λx xs → ∑(x: map rootLabel xs))