你如何在Haskell中使用foldr定义地图和过滤器?

时间:2011-04-20 06:40:44

标签: haskell map functional-programming filter fold

我正在对函数式语言进行一些自学(目前正在使用Haskell)。我遇到了一个基于Haskell的任务,需要根据foldr定义地图和过滤器。对于我的生活,我并不完全明白如何解决这个问题。

例如,当我定义地图函数时:

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] xs

我不知道为什么列表的第一个元素总是被忽略。意思是:

map' (*2) [1,2,3,4]

导致[4,6,8]而不是[2,4,6,8]

同样,我的过滤器'功能:

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] xs

运行时:

filter' even [2,3,4,5,6]

导致[4,6]而不是[2,4,6]

为什么会出现这种情况?我应该如何定义这些函数以获得预期的结果?我假设我的lambda表达式有问题......

8 个答案:

答案 0 :(得分:43)

我希望我能发表评论,但唉,我没有足够的业力。

其他答案都很好,但我认为最大的困惑似乎源于你使用x和xs。

如果你把它重写为

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\y ys -> (f y):ys) [] xs

你会清楚地看到右侧没有提到x,所以它无法在解决方案中出现。

干杯

答案 1 :(得分:20)

对于您的第一个问题,foldr已经有空列表的案例,因此您不需要也不应该在自己的地图中提供案例。

map' f = foldr (\x xs -> f x : xs) []

同样适用于filter'

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

您的lambda表达式没有任何问题,但您对filter'map'的定义有问题。在缺点情况下(x:xs)你吃头(x)然后将尾巴传递给foldrfoldr函数永远看不到您已经吃过的第一个元素。 :)

另请注意:

filter' p = foldr (\x xs -> if p x then x : xs else xs) []

等同于(η-equivalent):

filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs

答案 2 :(得分:4)

我会使用foldr和function composition定义地图,如下所示:

map :: (a -> b) -> [a] -> [b]
map f = foldr ((:).f) []

对于过滤器的情况:

filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr (\x xs -> if p x then x:xs else xs) []

请注意,使用foldr或foldl在列表上定义函数时,没有必要传递列表本身。 您的解决方案的问题是您放弃列表的头部,然后将地图应用于列表和 这就是显示结果时缺少列表头部的原因。

答案 3 :(得分:3)

在您的定义中,您正在为x:xs进行模式匹配,这意味着,当您的参数为[1,2,3,4]时,x绑定到1和{{1} }绑定到列表的其余部分:xs

你不应该做的只是扔掉[2,3,4]部分。然后,您的x:将在整个列表中工作。

所以你的定义应该如下:

foldr

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f xs       = foldr (\x xs -> (f x):xs) [] xs

答案 4 :(得分:2)

我是Haskell的新手(实际上我发现此页面提出了同样的问题),但这是我对列表和折叠器的理解:

  • 列表是使用cons (:)运算符链接到下一个元素的元素。它们以空列表[]终止。 (将其视为二元运算符,就像添加(+) 1+2+3+4 = 101:2:3:4:[] = [1,2,3,4]
  • 一样
  • foldr函数采用一个带两个参数的函数。这将取代cons运算符,它将定义每个项目如何链接到下一个项目。
  • 它还获取操作的终值,可以将其作为将分配给空列表的初始值。因为它是空列表[]。如果将空列表链接到任何列表,结果就是列表本身。因此对于sum函数,它是0。对于乘法函数,它是1等。
  • 并且它自己获取列表

所以我的解决方案如下:

filter' p = foldr (\x n -> if p x then x : n else n) []

lambda表达式是我们的链接函数,它将用于代替cons (:)运算符。空列表是空列表的默认值。如果谓词满意,我们会照常使用(:)链接到下一个项目,否则我们根本就没有链接。

map' f = foldr (\x n -> f x : n) []

此处我们将f x链接到下一个项目,而不仅仅是x,这只会复制列表。

另外,请注意,您不需要使用模式匹配,因为我们已经告诉foldr在列表空的情况下该怎么做。

我知道这个问题真的很老但我还是想回答它。我希望这不违反规则。

答案 5 :(得分:2)

考虑它的另一种方式 - 存在foldr,因为经常使用以下递归模式:

-- Example 1: Sum up numbers
summa :: Num a => [a] -> a
summa []     = 0
summa (x:xs) = x + suma xs

获取数字的乘积甚至反转列表看起来在结构上与之前的递归函数非常相似:

-- Example 2: Reverse numbers
reverso :: [a] -> [a]
reverso []      = []
reverso (x:xs)  = x `op` reverso xs
  where
    op = (\curr acc -> acc ++ [curr])

上述示例中的结构仅在初始值(对于汇总0和对于反转的[])以及第一个值和递归调用之间的运算符(+ for summa和(\q qs -> qs ++ [q]) for reverso)。因此,上述示例的功能结构通常可以视为

-- Generic function structure
foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
foo op init_val []      = init_val
foo op init_val (x:xs)  = x `op` foo op init_val xs

要看到这个"泛型" foo有效,我们现在可以通过使用foo并将其传递给运算符,初始值和列表本身来重写reverso:

-- Test: reverso using foo
foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]

让我们给foo一个更通用的类型签名,以便它也适用于其他问题:

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

现在,回到你的问题 - 我们可以像这样编写过滤器:

-- Example 3: filter
filtero :: (a -> Bool) -> [a] -> [a]
filtero p []     = []
filtero p (x:xs) = x `filterLogic` (filtero p xs)
  where
     filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)

这又有一个非常相似的结构来总结和反转。因此,我们应该能够使用foo来重写它。让我们说我们想要从列表[1,2,3,4]中过滤偶数。然后我们再次传递foo运算符(在本例中为filterLogic),初始值和列表本身。此示例中的filterLogic采用p函数,称为谓词,我们必须为调用定义:

let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 
Haskell中的 foo称为 foldr 。所以,我们使用foldr重写了过滤器。

let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 

因此,过滤器可以用 foldr 编写,如我们所见:

-- Solution 1: filter using foldr
filtero' :: (a -> Bool) -> [a] -> [a]
filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs 

对于地图,我们也可以将其写为

-- Example 4: map
mapo :: (a -> b) -> [a] -> [b]
mapo f []   = []
mapo f (x:xs) = x `op` (mapo f xs)
  where
    op = (\curr acc -> (f curr) : acc)

因此可以使用 foldr 重写。例如,要将列表中的每个数字乘以2:

let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]

因此,地图可以用 foldr 编写,如我们所见:

-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs

答案 6 :(得分:1)

您的解决方案几乎可行。) 问题是你在两个函数中都有两个不同的x绑定(在模式匹配内部和lambda表达式内部),因此你会松开第一个元素的跟踪。

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\x xs -> (f x):xs) [] (x:xs)

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p (x:xs)    = foldr (\x xs -> if p x then x:xs else xs ) [] (x:xs)

这应该是诀窍:)。另外:你可以轻松地编写无功能的函数。

答案 7 :(得分:0)

*Main> :{
*Main| map' :: (a -> b) -> [a] -> [b]
*Main| map' = \f -> \ys -> (foldr (\x -> \acc -> f x:acc) [] ys)
*Main| :}
*Main> map' (^2) [1..10]
[1,4,9,16,25,36,49,64,81,100]

*Main> :{
*Main| filter' :: (a -> Bool) -> [a] -> [a]
*Main| filter' = \p -> \ys -> (foldr (\x -> \acc -> if p x then x:acc else acc) [] ys)
*Main| :}
*Main> filter' (>10) [1..100]

在上面的代码片段中, acc 指累加器, x 指最后一个元素。