将值向上传播到Haskell中的Data.Tree中

将值向上传播到Haskell中的Data.Tree中,haskell,tree,functional-programming,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 | | | +-

使用
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
。也许有更好的方法可以使用
可折叠的
可遍历的

对每个节点进行操作,包括所有子树(节点标签),然后可以使用一个有用的函数

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
达到了预期的效果

正如Daniel所说,这个
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
不需要根分支和以前的分支来计算某个分支值(例如求和问题)时,now才是最优的。但是(例如)如果在每个节点上存储了一些密钥,那么这个sum实现也将是低效的


(使用前面的折叠,求和问题可以写成
foldTree(λx xs)→ ∑(x:map rootLabel xs))

对于每个节点,使用所有子树(包括节点标签),可以使用一个有用的函数

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
达到了预期的效果

正如Daniel所说,这个
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
不需要根分支和以前的分支来计算某个分支值(例如求和问题)时,now才是最优的。但是(例如)如果在每个节点上存储了一些密钥,那么这个sum实现也将是低效的


(使用前面的折叠,求和问题可以写成
foldTree(λx xs)→ ∑(x:map rootLabel xs))

我不认为
可折叠的
暴露了足够多的
树的结构来做你想做的事情<代码>可遍历的
可能会,但要正确处理似乎相对比较棘手;我想我更喜欢实现这样的递归模式:

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)

或者甚至用
sconcat
(:)
代替
sum
(:)

Num
推广到
半群
,我认为
可折叠
暴露的
树的结构不足以满足您的需要<代码>可遍历的
可能会,但要正确处理似乎相对比较棘手;我想我更喜欢实现这样的递归模式:

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)

或者甚至用
sconcat
(:)
来代替
sum
(:)

,将
Num
推广到
半群
。但这效率极低,因为它经常会重新计算深子树的和。@DanielWagner我认为你关于
f
的假设是错误的,“问题是我无法访问节点的子节点”,求和问题是一个示例,而不是一般情况。您可以优化尾部计算(求和情况)但是忽略preffix计算。我只解决了建议的一般问题……但这是非常低效的,因为它会经常重新计算深子树的和。@DanielWagner我认为你关于
f
的假设是错误的,“问题是我无法访问节点的子节点,求和问题只是一个例子,而不是一般情况。您可以优化尾部计算(总和情况),但可以忽略预修复计算。我只解决了建议的一般问题。您可能会喜欢(使用幺半群作为向上移动的注释)。您可能会喜欢(使用幺半群作为向上移动的注释)。
Traversable
绝对不够。我想在uniplate中有这样的东西,但我从来没有用过。你的方法看起来很明智。你介意写信给我吗libraries@haskell.org建议将其添加到
数据树
?它明显地丢失了。@dfeur似乎
容器
不是由库维护的;至少它没有被提及,而且Hackage上列出的维护人员在几个主要版本之前已经从库中更换了。也就是说,几周前,我与雷恩·罗马诺共同维护了
容器。由于其在软件包层次结构中的特殊地位,以及大量的反向依赖关系,以前的软件包维护者已经建立了一种传统,即就API更改咨询库列表。一些特别适合API其余部分的添加可能会通过,但这是一个例外,而不是规则。
Traversable
显然是不够的。我想在uniplate中有这样的东西,但我从来没有用过。你的方法看起来很明智。你介意写信给我吗libraries@haskell.org建议将其添加到
数据树
?它明显地丢失了。@dfeur似乎
容器
不是由库维护的;至少它没有被提及,而且Hackage上列出的维护人员在几个主要版本之前已经从库中更换了。也就是说,几周前,我与雷恩·罗马诺共同维护了
容器。由于其在软件包层次结构中的特殊地位,以及大量的反向依赖关系,以前的软件包维护者已经建立了一种传统,即就API更改咨询库列表。一些特别适合API其余部分的添加可能会获得通过,但这是一个例外,而不是