繁体   English   中英

折叠树 Function

[英]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,所以我对所有事情都完全错了,但这就像我看来的那样。

谢谢你的回答。 我想我会因为以下原因而保留原样:

  1. 类型签名在问题中给出,所以我认为这是他们想要的。
  2. 问题提到“任何遍历顺序都很好”。
  3. 它可以工作,只是不按订单方式,例如。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM