【问题标题】:Tree Fold operation?树折叠操作?
【发布时间】:2019-01-26 23:50:08
【问题描述】:

我正在学习 Haskell 的课程,我们需要为定义的树定义折叠操作:

data Tree a = Lf a | Br (Tree a) (Tree a)

我似乎找不到任何关于“tfold”操作或它应该做什么的信息。任何帮助将不胜感激。

【问题讨论】:

    标签: haskell tree fold


    【解决方案1】:

    我一直认为折叠是一种用其他函数系统地替换构造函数的方法。因此,例如,如果您有一个自己动手的List 类型(定义为data List a = Nil | Cons a (List a)),则相应的折叠可以写成:

    listfold nil cons Nil = nil
    listfold nil cons (Cons a b) = cons a (listfold nil cons b)
    

    或者,也许更简洁,如:

    listfold nil cons = go where
        go Nil = nil
        go (Cons a b) = cons a (go b)
    

    listfold 的类型是b -> (a -> b -> b) -> List a -> b。也就是说,它需要两个“替换构造函数”;一个告诉Nil 值应如何转换为bCons 构造函数的另一个替换构造函数,告诉Cons 构造函数(a 类型)的第一个值应如何与b 类型的值(为什么是b?因为折叠已经被递归应用了!)产生一个新的b,最后是一个List a,将整个she-bang应用到 - 结果为@ 987654336@.

    在你的情况下,tfold 的类型应该是(a -> b) -> (b -> b -> b) -> Tree a -> b 类似的推理;希望你能从那里拿走它!

    【讨论】:

    • 这完全正确。但是,我认为将这一点添加到您的答案中很有用:如果正确定义了 tfold,那么 tfold Lf Br 应该是 Tree a 上的标识函数——一个接受一棵树并返回相同树的函数。 (同样对于您的示例,listfold Nil ConsList 的身份。)
    【解决方案2】:

    假设你定义一棵树应该以下面的方式显示,

    <1 # <<2#3> # <4#5>>>
    

    折叠这样的树意味着用实际提供的操作替换每个分支节点,该操作要对数据类型的组成部分(这里是节点的两个子节点,它们是他们自己,每个人,一棵树),例如+,产生

    (1 + ((2+3) + (4+5)))
    

    因此,对于叶子,您应该只取其中的值,对于分支,递归地为两个子节点中的每一个应用折叠,然后将两个结果与提供的函数组合,一个用来折叠树的。 (编辑:)当从叶子中“获取”值时,您可以另外转换它们,应用一元函数。所以一般来说,你的折叠需要两个用户提供的函数,一个用于leavesLf,另一个用于组合r r递归折叠分支节点的树状成分(即分支)的结果,Br

    您的树数据类型可能有不同的定义,例如可能是空的叶子,并且内部节点也带有值。然后你必须提供一个默认值来代替空的叶子节点,以及一个三向组合操作。仍然会有 two 函数定义的折叠,对应于数据类型定义的 两种情况

    这里要实现的另一个区别是,什么你折叠,以及如何你折叠它。 IE。你可以用 linear 方式折叠你的树,(1+(2+(3+(4+5)))) == ((1+) . (2+) . (3+) . (4+) . (5+)) 0,或者你可以用 tree-like 方式折叠一个线性列表,((1+2)+((3+4)+5)) == (((1+2)+(3+4))+5)。这完全取决于您如何为生成的“表达式”加上括号。当然,在经典的折叠中,表达式的结构遵循被折叠的数据结构;但是variations do exist。另请注意,组合操作可能并不严格,它使用/产生的“result”类型可能表示 compound(列表等),以及原子(数字等),值。

    (更新 2019-01-26) 如果组合操作是关联的,则可以重新加括号,例如 +: (a<sub>1</sub>+a<sub>2</sub>)+a<sub>3</sub> == a<sub>1</sub>+(a<sub>2</sub>+a<sub>3</sub>)。一种数据类型连同这种关联操作和一个“零”元素 (a+0 == 0+a == a) 称为“Monoid”,而将“折叠”成一个 Monoid 的概念由 Foldable 类型类捕获。

    【讨论】:

    • @sacundim 啊,我明白你对身份转换的看法。你是对的,这里应该解释一个单独的一元函数对叶子值的应用。我的回答模棱两可。
    • (很明显,这个答案中的疏忽早已得到修复。)
    【解决方案3】:

    列表上的折叠是将列表缩减为单个元素。它接受一个函数,然后将该函数应用于元素,一次两个,直到它只有一个元素。例如:

    Prelude> foldl1 (+) [3,5,6,7]
    21
    

    ...是通过一个一个操作找到的:

    3 + 5 == 8
    8 + 6 == 14
    14 + 7 == 21
    

    可以写折叠

    ourFold :: (a -> a -> a) -> [a] -> a
    ourFold _         [a]        = a -- pattern-match for a single-element list. Our work is done.
    ourFold aFunction (x0:x1:xs) = ourFold aFunction ((aFunction x0 x1):xs)
    

    树折叠可以做到这一点,但会向上或向下移动树的树枝。为此,它首先需要进行模式匹配以查看您是在叶子上还是在分支上进行操作。

    treeFold _ (Lf a)   = Lf a -- You can't do much to a one-leaf tree
    treeFold f (Br a b) = -- ...
    

    剩下的就交给你了,因为这是家庭作业。如果您遇到困难,请先尝试考虑类型应该是什么。

    【讨论】:

    • 现在问题已经够老了,很高兴看到完整的答案,对于那些想要学习但没有作业的人。
    【解决方案4】:

    折叠是一种使用操作将数据结构“压缩”为单个值的操作。取决于您是否有起始值和执行顺序(例如,对于您有foldlfoldrfoldl1foldr1 的列表),会有一些变化,因此正确的实现取决于您的分配。

    我猜你的tfold 应该简单地将所有叶子替换为其值,并将所有分支替换为给定操作的应用程序。画一个带有一些数字的示例树,给定一个像(+) 这样的操作“折叠”他。在此之后,应该很容易编写一个函数来做同样的事情。

    【讨论】:

    • 我对你投了反对票,原因与我在对 Will Ness 回答的评论中解释的原因相同。对于海报显示的 Tree 数据类型,tfold 不应该像你说的那样“用它的值替换所有叶子”。
    • 这取决于您如何定义“折叠”,因为 TO 没有提供有关预期功能的足够信息。我所描述的只是行为,例如foldr1foldl1 用于交换操作,因为这是概念上最简单的版本。如果您非常确定自己是“正确的”,我建议您实际回答这个问题,而不是对您稍微不同意的每个人投反对票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-03
    • 1970-01-01
    • 2019-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多