简体   繁体   English

折叠树 Function

[英]Fold Tree Function

I'm trying to write a fold function for a tree:我正在尝试为一棵树写一个折叠 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

This code nearly works.这段代码几乎可以工作。 However not always.然而并非总是如此。 For example it won't reconstruct the tree exactly the same as the original.例如,它不会重建与原始树完全相同的树。

GHC is good at folding things. GHC善于折叠东西。 The very structure of your type contains enough information for your desired in-order traversal strategy to be obvious to the machine. 您的类型的结构包含足够的信息,以便您所需的有序遍历策略对于机器来说是显而易见的。 To invoke the magic spell, utter the words " deriving Foldable !" 要调用魔法咒语,请说出deriving Foldable !” and GHC will write your function for you. GHC将为您编写您的功能。

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

Now we have 现在我们有

foldTree = foldr

An interesting corollary here is that you can vary the traversal order by varying the shape of the type. 这里有一个有趣的推论是,您可以通过改变类型的形状来改变遍历顺序。


While we're here, a note on your requirements. 我们在这里时,请注意您的要求。 You want to implement a function, using foldr , which takes a tree apart and puts it back together exactly the same, equivalent to id . 你想使用foldr实现一个函数,它将树分开并将它重新组合在一起,完全相同,相当于id This is not possible . 这是不可能的 foldr provides sequential access to the elements of the Foldable structure, erasing information like the precise position of the element within the tree. foldr提供对Foldable结构元素的顺序访问,删除信息,如树中元素的精确位置。 At best, you can build a list-shaped tree, with elements appearing along the right spine: 充其量,您可以构建一个列表形状的树,其中元素出现在右侧脊柱上:

toListShapedTree = foldr (Node Leaf) Leaf

What you want is a catamorphism : 你想要的是一个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)

Note the extra parameter to the node argument! 注意node参数的额外参数! This specification gives the folding function access to the arguments of the Node constructor. 此规范为折叠函数提供了对Node构造函数参数的访问。 Unlike Foldable , the type of a structure's catamorphism is specific to that structure. Foldable不同,结构的类似于该结构的类型。 We don't lose information by viewing everything as a list. 我们不会通过查看列表中的所有内容来丢失信息。 Now you can write: 现在你可以写:

cataId = cata Node Leaf

If you're dead set on using foldr for this, one strategy could be to take the positional information with you. 如果你已经开始使用foldr ,那么一种策略就是随身携带位置信息。 First label each element with its position , then in the fold use that data to reconstruct the tree. 首先用位置标记每个元素 ,然后在折叠中使用该数据重建树。 Seems like hard work to me. 对我来说似乎很辛苦。

I think you are looking for this kind of fold: 我想你正在寻找这种折叠:

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

This is, roughly, what is being generated by the automatic deriving Foldable . 这大致是自动deriving Foldable生成的内容。

The above is a sequential fold, which first folds over the left part, then over the middle element, then over the right. 以上是顺序折叠,首先在左侧部分折叠,然后在中间元素上折叠,然后在右侧折叠。

An equivalent but less efficient variant is to convert the tree to a list with an in-visit and then apply a foldr fn base over the result. 等效但效率较低的变体是将树转换为具有访问的列表,然后在结果上应用foldr fn base One can "spread" the foldr over all the list generation, recovering the code above. 可以在所有列表生成中“扩展” foldr ,恢复上面的代码。

I think you need to combine the middle point before going to the right subtree : 我认为你需要在转到正确的子树之前结合中间点:

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

I came here after struggling with an exercise in Allen & Moronuki's Haskell Programming from first principles . 我从Allen&Moronuki的Haskell编程中挣扎着从第一原理开始练习后来到这里。 This question sounds like the same exercise, so, for what it's worth, from one beginner to another, here's what I came up with. 这个问题听起来像是同样的练习,所以,从一个初学者到另一个,这是值得的,这就是我想出的。

I see three ways to "fold" (or catamorph) a binary tree. 我看到三种“折叠”(或catamorph)二叉树的方法。 When folding a sequence, there are two ways to do it: fold left and fold right. 折叠序列时,有两种方法:向左折叠和向右折叠。 One way is to start by applying the given function to (1) the head of the list and (2) the return value of the recursive call applied to the tail of the list. 一种方法是首先将给定函数应用于(1)列表的头部,以及(2)应用于列表尾部的递归调用的返回值。 That's a fold right. 这是一个正确的折叠。

The other way to do a sequential fold is to start by recursively calling fold on (1) the return value of the given function applied to the head of the list and (2) the tail of the list. 执行顺序折叠的另一种方法是首先递归调用fold on(1)应用于列表头部的给定函数的返回值,以及(2)列表的尾部。 That's a fold left. 这是一个折叠。

But in a binary tree each value can have two "subsequent" values, rather than one as in a list. 但是在二叉树中,每个值可以有两个 “后续”值,而不是列表中的一个值。 So there must be two recursive calls to the fold. 所以必须有两个递归调用折叠。 The call to the passed function thus can either be outside the two recursive calls, inside the two, or else between them. 因此,对传递函数的调用可以在两个递归调用之外,在两个之内,或者在它们之间。 If I call those each left, right and center folds, I get these: 如果我打电话给左,右和中间折叠,我得到这些:

-- 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

The first time I ever looked at Haskell was a couple weeks ago, so I could be totally wrong about everything, but that's the way it seems to me. 几周前我第一次看到Haskell,所以我对所有事情都完全错了,但这就像我看来的那样。

Thanks for the answers. 谢谢你的回答。 I think I will just leave it as is for these reasons: 我想我会因为以下原因而保留原样:

  1. The type signature is given in the question so I presume that's what they want. 类型签名在问题中给出,所以我认为这是他们想要的。
  2. The question mentions "any traversal order is fine". 问题提到“任何遍历顺序都很好”。
  3. It works, just not in an order fashion eg. 它可以工作,只是不按订单方式,例如。 foldTree (:) [] tree will create a list of nodes but not in order. foldTree (:) [] tree将创建节点列表但不按顺序。 However foldTree (+) 0 tree creates a total where order is irrelevant. 但是, foldTree (+) 0 tree会创建一个总数,其中订单无关紧要。

I think you are searching foldl one;我认为您正在搜索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

This will work for you这对你有用

Also if you need foldr ;另外,如果您需要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