如何将函数应用于foldr?

时间:2013-02-28 22:38:40

标签: haskell binary-search-tree fold

这是一个相对简单的haskell问题,但我遇到了很多麻烦。 我正在尝试应用此函数'add'将字符串列表转换为BST。 (add函数只是插入一个术语)我的问题是如何定义折叠以便它将add函数应用于[String],以便它基本上逐个插入每个函数?

我最初的想法是,我将应用于xs的函数将是(add _ basetree),其中_将是xs的每个元素,而基本树将是具有一个元素x的树。然后,foldr将该函数应用于xs的每个元素。我不确定有什么问题,但这给了我一个错误。

*** Expression     : foldr1 (add x (add x Empty)) xs
*** Term           : add x (add x Empty)
*** Type           : BST
*** Does not match : [Char] -> [Char] -> [Char]

data BST = MakeNode BST String BST
           | Empty

add :: String -> BST -> BST

listToTree :: [String] -> BST
listToTree (x:xs) = foldr (add x (add x Empty)) xs -- Here***

如果有人可以帮助我,那就太好了。我花了差不多3个小时试图找出这个折叠器..

3 个答案:

答案 0 :(得分:8)

正如Gabriel在评论中所说,您正在以非常不寻常的方式将手动递归(模式匹配(x:xs))与foldr结合起来。通常你想使用 手动递归,或者在递归遵循模式的情况下使用foldr“重复将函数应用于列表的元素,直到你用完列表为止”

我认为你的add函数看起来像这样:

add :: String -> BST -> BST
add string Empty            = MakeNode Empty string Empty
add string (MakeNode l s r) =
    if string < s
        then MakeNode (add string l) s r
        else MakeNode l s (add string r)

通过这种方式,函数listToTree通常会以两种方式之一编写。第一种是使用模式匹配和递归:

listToTree []     = Empty
listToTree (x:xs) = add x (listToTree xs)

也就是说,要么你有一个空列表,在这种情况下你返回空树,或者你有一个头后跟一个尾,在这种情况下你将列表的头添加到由尾巴返回的树中清单。

另一种方法是通过折叠列表来编写listToTree。这会为你抽象出递归,这样你就可以写

listToTree = foldr add Empty

这可行,因为foldr的类型为

foldr :: (a -> b -> b) -> b -> [a] -> b

addEmpty的类型为

add   :: String -> BST -> BST
Empty :: BST

专门处理ab类型,您将获得

foldr :: (String -> BST -> BST) -> BST -> [String] -> BST

这意味着

foldr add Empty :: [String] -> BST 

您更喜欢哪一种?也许第一个更容易让初学者阅读和理解。但是,随着您获得Haskell的更多经验,您会发现第二个版本变得更容易理解。它也更简洁,而且它以折叠方式编写,这样可以更频繁地触发列表融合规则,从而可以提高代码效率。

了解foldr

在我看来,理解折叠的关键是要认识到折叠用你给它的任何函数和常量替换列表构造函数。在Haskell中,列表有两种可能的构造函数:

[]  :: [a]
(:) :: a -> [a] -> [a]

当你贬低所有语法时,列表实际上看起来像这样(这是有效的Haskell - 尝试一下!)

xs = 1 : 2 : 3 : []

当您致电foldr op x0 xs时,折叠有效地替换(:)xs的所有op构造函数,[]构造函数x0 {1}}:

foldr op x0 xs = 1 `op` 2 `op` 3 `op` x0

当然,这里存在歧义,因为我们不知道op是左侧还是右侧。为了使类型能够解决,你必须提供一个与右边相关联的函数(这就是为什么它被称为正确折叠),如下所示:

foldr op x0 xs = 1 `op` (2 `op (3 `op` x0))

左侧折叠是相同的,除了它与左侧相关联(并将初始值放在列表的开头而不是结尾),所以最终得到

foldl op x0 xs = ((x0 `op` 1) `op` 2) `op` 3

答案 1 :(得分:2)

折叠器的类型是

(a -> b -> b) -> b -> [a] -> b,

即。它需要一个带有b的函数和一个[a]的列表,并返回一个b。 现在添加

(String -> BST -> BST),

所以它变成了

(String -> BST -> BST) -> BST -> [String] -> BST,

现在,我无法编译它,因为我没有添加功能,但我相信

listToTree (x:xs) = foldr add (add x (add x Empty)) xs

会做到这一点。它的类型签名至少是相同的。 现在这应该两次添加列表的开头,但是你的Add函数可能会忽略重复项?如果你可以把它包括在内,那就更好了,这样我们就可以进一步研究它。

答案 2 :(得分:2)

看起来你想要:

listToTree = foldr add Empty

foldr的类型为(a -> b -> b) -> b -> a

所以它需要和累加器功能和初始值。 Empty是您的初始树值,add构造一个给定元素和现有树的新树。