haskell 如何写一个类型为a->B ->b ->b的函数来折叠一棵树

cl25kdpy  于 2022-11-14  发布在  其他
关注(0)|答案(4)|浏览(181)

一些背景:我在Haskell中有一个foldT函数(类似于foldr,但用于树),它的类型如下。

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b

这个foldT只接受类型(a -〉B -〉b -〉b)作为输入函数。
我试图找到一种方法将我的树转换成列表,但无法找到一种方法使我的append函数采用(a -〉B -〉b -〉b)的形式。
以下是无效的,因为它不是正确的类型:

append x y z = append x:y:z

任何帮助都将不胜感激。
谢谢你,谢谢你

pnwntuvh

pnwntuvh1#

因为你还没有发布它,我会假设你的树是...

data Tree a = Leaf | Node a (Tree a) (Tree a)

...并且foldTa -> b -> b -> b参数以声明Node构造函数的字段的相同顺序获取这些字段。
我试图找到一种方法将我的树转换为列表
让我们从以下类型开始解决这个问题:

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b

您希望将其扁平化为列表,因此函数的结果类型必须为[a]

treeToList :: Tree a -> [a]

这给我们提供了一个好主意,让我们知道如何继续:

treeToList t = foldT f z t

具有:

f :: a -> [a] -> [a] -> [a]
z :: [a]
t :: Tree a

现在,继续讨论参数. z,它将代替无值的Leaf s被插入,因此它必须是[]。至于f,它必须将从左分支和右分支创建的子表与直接在Node中的值结合起来。假设是按序遍历,我们有:

treeToList t = foldT (\x l r -> l ++ x : r) [] t

或者,不提t

treeToList = foldT (\x l r -> l ++ x : r) []

就是这样。需要注意的是,重复使用左嵌套的(++)(在本例中会发生,因为foldT会递归地遍历分支)可能会非常低效。在您关心性能的情况下,值得考虑实现级联函数的替代方法,例如difference lists
附言:关于术语的一个注意事项。说一个函数“像foldr,但是用于树”是不明确的,因为有两种众所周知的函数类似于foldr。首先,你有Foldable类的方法(参见Benjamin Hodgson's answer),无论你做什么,它们都会在折叠时将树扁平化为一个列表。例如您正在使用的foldT,它们能够利用树结构。

ruyhziif

ruyhziif2#

GHC可以自动填充Foldable类的一个示例,其中包含了您要查找的toList

{-# LANGUAGE DeriveFoldable #-}
import Data.Foldable

data Tree a = Leaf | Node a (Tree a) (Tree a) deriving Foldable

treeToList :: Tree a -> [a]
treeToList = toList

由于Node构造函数的字段的顺序,treeToList的此定义将执行预排序遍历。

2vuwiymt

2vuwiymt3#

如果你想把树转换成一个列表,那么这个函数相对容易,所以基本上如果你有树结构,比如:

data Tree a = Leaf | Node (Tree a) a (Tree a) deriving (Eq, Ord, Show)

然后你必须从树中提取值,并将第一个值添加到一个空列表中,然后在列表(++)操作符的帮助下,递归地将相同的方法应用于树的其他分支,以将所有结果追加到一个列表中,即:

toList :: Tree a -> [a]
toList Leaf                    =    []
toList (Node left value right) =    [value] ++ toList left ++ toList right

然后折叠它,首先:

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b

函数签名缺少一个参数,下面让我们看看签名:a(第一个参数)-〉B(第二个)-〉b(第三个)-〉b(返回类型)该函数接受3个参数/参数,但您提供的签名只有1个b类型**(我猜是您的累加器),所以基本上这就是您要查找的函数签名:

foldT :: (a -> b -> b) -> b -> Tree a -> b

现在我们可以处理这个签名了,接下来让我们来看看函数:

foldT :: (a -> b -> b) -> b -> Tree a -> b
foldT f acc Leaf                    =   acc
foldT f acc (Node left value right) =   foldT f (foldT f (f value acc) left) right

虽然我不认为我应该给你答案,你应该多练习一下递归数据结构,这样你就可以更好地理解如何遍历它们。

jjjwad0x

jjjwad0x4#

你每次开始的第一件事就是开始匹配模式:)在这一点上,它几乎是机械的!
假设您有:

data Tree a = Leaf | Node (Tree a) a (Tree a)

让我们开始匹配模式吧!:)

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b
foldT f z ...

第一个Tree构造函数是什么?是Leaf

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b
foldT f z Leaf = ...

我们需要b类型的东西......我们只有一种方法可以得到它。通过使用z:)

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b
foldT f z Leaf = z

因此,从机械Angular 来看,我们可以进入下一个可能的模式:

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b
foldT f z Leaf           = z
foldT f z (Node t1 x t2) =

我们可以使用t1xt2,我们可以使用f,它需要a类型的东西......我们有x :: a:)

foldT :: (a -> b -> b -> b) -> b -> Tree a -> b
foldT f z Leaf           = z
foldT f z (Node t1 x t2) = f x ??? ???

f还有两个参数,类型为b,那么你认为我们可以在那里放什么呢?我们可以放z两次,但是这有点无聊。我们如何从t1t2得到b的?

相关问题