【问题标题】:Fold Tree Function折叠树函数
【发布时间】:2017-01-03 23:20:21
【问题描述】:

我正在尝试为树编写折叠函数:

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

这段代码几乎可以工作。然而并非总是如此。例如,它不会重建与原始树完全相同的树。

【问题讨论】:

  • 你怎么打电话给foldTree
  • 我没有在下面看到的一条评论:而列表上的右折叠使用(a -> b -> b)类型的组合函数;列表上的 left 折叠使用 (a -> b -> a) 类型的折叠。

标签: haskell


【解决方案1】:

我在 Allen & Moronuki 的 Haskell Programming from first principle 的练习中苦苦挣扎后来到这里。这个问题听起来像是同一个练习,所以,对于它的价值,从一个初学者到另一个,这就是我想出的。

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

执行顺序折叠的另一种方法是首先在 (1) 应用于列表头部的给定函数的返回值和 (2) 列表尾部递归调用 fold。剩下一个折叠。

但是在二叉树中,每个值可以有两个“后续”值,而不是像列表中的一个。所以必须有两个递归调用折叠。因此,对传递函数的调用既可以在两个递归调用之外,也可以在两个递归调用内部,或者在它们之间。如果我将它们分别称为左、右和中心折叠,我会得到这些:

-- 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 是在几周前,所以我可能对所有事情都完全错了,但在我看来就是这样。

【讨论】:

  • foldTreel的右侧切换leftright对应foldTreer。在foldTreer的右侧切换leftright对应foldTreelfoldTreec 保持不变。
【解决方案2】:

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

【讨论】:

  • 太好了。但是我想知道如何自己编码。就像一个学习练习。
  • 请参阅the linked Documentation page 以获取示例。 (另外,我在答案中添加了一个部分。)
  • foldl (\l x -> l ++ [x]) [] 不是列表中的身份吗?如果是这样,它会保留列表结构:)
  • @V.Semeria 是的,但树不是列表。它们包含额外的信息,foldl/foldr 丢弃。 Foldable 的世界观将所有内容简化为一个列表。
  • 而是foldr f init t == foldr f init (toList t)。因此,折叠不能使用更多关于 t 的信息,而不是其列表折叠中包含的信息。
【解决方案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 生成的内容。

上面是顺序折叠,先折叠左边部分,再折叠中间元素,再折叠右边。

一个等效但效率较低的变体是将树转换为具有 in-visit 的列表,然后在结果上应用 foldr fn base。可以在所有列表生成中“传播”foldr,恢复上面的代码。

【讨论】:

  • 假设我创建了一个定义为 data TriTree a = EmptyNode | TriNode a (TriTree a) (TriTree a) (TriTree a) deriving (Show) 的三元(Ternary?)树,我该如何为这样的树创建预购折叠?
  • @Lombe 这不是评论,这是一个问题。您可能应该这样发布它,并说明为什么您无法使上述代码适应您的树类型。
  • @chi 我能弄明白。但是,我有一个不同的与 haskell 相关的问题。如果你有时间,我想知道你是否能帮我弄清楚。我刚刚把它贴在这里stackoverflow.com/questions/52638198/…
【解决方案4】:

感谢您的回答。我想我会保持原样,原因如下:

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

【讨论】:

    【解决方案5】:

    我认为你需要在去右子树之前合并中间点:

    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
    

    【讨论】:

    • 不幸的是,这也没有提供正确的答案。
    • 请举一个失败的例子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-02
    • 1970-01-01
    • 1970-01-01
    • 2017-01-30
    相关资源
    最近更新 更多