折叠树功能

时间:2016-08-27 11:30:20

标签: haskell

我试图为树编写折叠函数:

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

此代码几乎可以使用。但并非总是如此。例如,它不会重建与原始树完全相同的树。

5 个答案:

答案 0 :(得分:10)

GHC擅长折叠东西。您的类型的结构包含足够的信息,以便您所需的有序遍历策略对于机器来说是显而易见的。要调用魔法咒语,请说出deriving Foldable!”这两个词,GHC会为你编写你的功能。

{-# LANGUAGE DeriveFoldable #-}
data BinaryTree a = Leaf
                  | Node (BinaryTree a) a (BinaryTree a)
                  deriving Foldable

现在我们有了

foldTree = foldr

这里有一个有趣的推论是你可以通过改变类型的形状来改变遍历顺序。

我们在这里时,请注意您的要求。您希望使用foldr实现一个函数,该函数将树分开并将其重新组合在一起,相当于id这是不可能的foldr提供对<{1}}结构元素的顺序访问,删除信息,例如树中元素的精确位置。充其量,您可以构建一个列表形状的树,其中的元素出现在右侧脊柱上:

Foldable

你想要的是 catamorphism

toListShapedTree = foldr (Node Leaf) Leaf

注意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 ,那么一种策略就是随身携带位置信息。首先是label each element with its position,然后在折叠中使用该数据来重建树。对我来说似乎很辛苦。

答案 1 :(得分:3)

我认为你正在寻找这种折叠:

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。人们可以&#34;传播&#34;所有列表生成的foldr,恢复上面的代码。

答案 2 :(得分:1)

我认为你需要在转到正确的子树之前结合中间点:

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

答案 3 :(得分:0)

感谢您的回答。我想我会因为这些原因而保留原样:

  1. 类型签名在问题中给出,所以我认为这就是他们想要的。
  2. 问题提到“任何遍历顺序都很好”。
  3. 它起作用,只是不是以订单方式,例如。 foldTree (:) [] tree将创建节点列表但不按顺序创建。但是,foldTree (+) 0 tree会创建订单无关的总计。

答案 4 :(得分:0)

我在努力学习Allen&Moronuki的 Haskell编程中的基本原理之后,来到这里。这个问题听起来像是相同的练习,所以,对于一个初学者到另一个初学者来说,这是我想出来的。

我看到三种“折叠”(或目录变形)二叉树的方法。折叠序列时,有两种方法:左折叠和右折叠。一种方法是通过将给定函数应用于(1)列表的开头和(2)应用于列表尾部的递归调用的返回值开始。那是对折。

另一种进行顺序折叠的方法是从以下位置开始递归折叠:(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是在几周前,所以我可能在所有方面都完全错了,但这就是我的样子。