使用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
。也许有更好的方法可以使用Foldable
或Traversable
?
答案 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)
甚至使用Num
和Semigroup
代替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))
)