[英]Fold Tree Function
我正在尝试为一棵树写一个折叠 function :
data BinaryTree a = Leaf
| Node (BinaryTree a) a (BinaryTree a)
deriving (Eq, Ord, Show)
foldTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTree _ base Leaf = base
foldTree fn base (Node left a right) = fn a (foldTree fn acc right)
where acc = foldTree fn base left
这段代码几乎可以工作。 然而并非总是如此。 例如,它不会重建与原始树完全相同的树。
GHC善于折叠东西。 您的类型的结构包含足够的信息,以便您所需的有序遍历策略对于机器来说是显而易见的。 要调用魔法咒语,请说出“ deriving Foldable
!” GHC将为您编写您的功能。
{-# LANGUAGE DeriveFoldable #-}
data BinaryTree a = Leaf
| Node (BinaryTree a) a (BinaryTree a)
deriving Foldable
现在我们有
foldTree = foldr
这里有一个有趣的推论是,您可以通过改变类型的形状来改变遍历顺序。
我们在这里时,请注意您的要求。 你想使用foldr
实现一个函数,它将树分开并将它重新组合在一起,完全相同,相当于id
。 这是不可能的 。 foldr
提供对Foldable
结构元素的顺序访问,删除信息,如树中元素的精确位置。 充其量,您可以构建一个列表形状的树,其中元素出现在右侧脊柱上:
toListShapedTree = foldr (Node Leaf) Leaf
你想要的是一个catamorphism :
cata :: (b -> a -> b -> b) -> b -> BinaryTree a -> b
cata node leaf Leaf = leaf
cata node leaf (Node l x r) = node (cata node leaf l) x (cata node leaf r)
注意node
参数的额外参数! 此规范为折叠函数提供了对Node
构造函数参数的访问。 与Foldable
不同,结构的类似于该结构的类型。 我们不会通过查看列表中的所有内容来丢失信息。 现在你可以写:
cataId = cata Node Leaf
如果你已经开始使用foldr
,那么一种策略就是随身携带位置信息。 首先用位置标记每个元素 ,然后在折叠中使用该数据重建树。 对我来说似乎很辛苦。
我想你正在寻找这种折叠:
foldTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTree _ base Leaf = base
foldTree fn base (Node left a right) = foldTree fn base' left
where
base' = fn a base''
base'' = foldTree fn base right
这大致是自动deriving Foldable
生成的内容。
以上是顺序折叠,首先在左侧部分折叠,然后在中间元素上折叠,然后在右侧折叠。
等效但效率较低的变体是将树转换为具有访问的列表,然后在结果上应用foldr fn base
。 可以在所有列表生成中“扩展” foldr
,恢复上面的代码。
我认为你需要在转到正确的子树之前结合中间点:
foldTree :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTree _ base Leaf = base
foldTree fn base (Node left a right) = foldTree fn middlePoint right
where leftFold = foldTree fn base left
middlePoint = fn a leftFold
我从Allen&Moronuki的Haskell编程中挣扎着从第一原理开始练习后来到这里。 这个问题听起来像是同样的练习,所以,从一个初学者到另一个,这是值得的,这就是我想出的。
我看到三种“折叠”(或catamorph)二叉树的方法。 折叠序列时,有两种方法:向左折叠和向右折叠。 一种方法是首先将给定函数应用于(1)列表的头部,以及(2)应用于列表尾部的递归调用的返回值。 这是一个正确的折叠。
执行顺序折叠的另一种方法是首先递归调用fold on(1)应用于列表头部的给定函数的返回值,以及(2)列表的尾部。 这是一个折叠。
但是在二叉树中,每个值可以有两个 “后续”值,而不是列表中的一个值。 所以必须有两个递归调用折叠。 因此,对传递函数的调用可以在两个递归调用之外,在两个之内,或者在它们之间。 如果我打电话给左,右和中间折叠,我得到这些:
-- Fold Right for BinaryTree
foldTreer :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTreer f z Leaf = z
foldTreer f z (Node left a right) =
f a (foldTreer f (foldTreer f z left) right)
-- Fold Left for Binary Tree
foldTreel :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTreel f z Leaf = z
foldTreel f z (Node left a right) =
foldTreel f (foldTreel f (f a z) left) right
-- Fold Center for Binary Tree
foldTreec :: (a -> b -> b) -> b -> BinaryTree a -> b
foldTreec f z Leaf = z
foldTreec f z (Node left a right) =
foldTreec f (f a (foldTreec f z left)) right
几周前我第一次看到Haskell,所以我对所有事情都完全错了,但这就像我看来的那样。
谢谢你的回答。 我想我会因为以下原因而保留原样:
foldTree (:) [] tree
将创建节点列表但不按顺序。 但是, foldTree (+) 0 tree
会创建一个总数,其中订单无关紧要。 我认为您正在搜索foldl one;
foldl :: (b -> a -> b) -> b -> Tree a -> b
foldl _ e Leaf = e
foldl f e (Node x l r) = foldl f (f (foldl f e x) l) r
这对你有用
另外,如果您需要foldr ;
foldr :: (a -> b -> b) -> b -> Tree a -> b
foldr _ base Leaf = base
foldr fn base (Node left a right) = fn a (foldr fn acc right)
where acc = foldr fn base left
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.