19

我正在对函数式语言(目前使用 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 表达式有问题...

4

8 回答 8

51

我希望我可以发表评论,但是,我没有足够的业力。

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

如果你把它改写为

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

您会清楚地看到x右侧甚至没有提到它,因此它不可能出现在解决方案中。

干杯

于 2011-04-20T08:24:51.823 回答
21

对于您的第一个问题,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'and的定义有问题map'。在 cons 情况下 (x:xs) 你吃掉头部 ( x) 然后将尾部传递给foldr. 该foldr函数永远无法看到您已经吃过的第一个元素。:)

另请注意:

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

等效于(η-等效):

filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs
于 2011-04-20T07:11:05.237 回答
5

我将使用 foldr 和函数组合定义 map ,如下所示:

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 在列表上定义函数时,不必传递列表本身。您的解决方案的问题是您删除了列表的头部,然后将地图应用到列表上,这就是显示结果时缺少列表头部的原因。

于 2012-09-03T17:52:36.790 回答
3

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

你不应该做的只是扔掉x:一部分。然后你foldr将处理整个列表。

因此,您的定义应如下所示:

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

filter'             :: (a -> Bool) -> [a] -> [a]
filter' p []        = []
filter' p xs        = foldr (\x xs -> if p x then x:xs else xs ) [] xs
于 2011-04-20T07:10:10.197 回答
3

我是 Haskell 的新手(事实上我发现这个页面问了同样的问题)但这是我迄今为止对列表和 foldr 的理解:

  • 列表是使用 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到下一个项目而不是 just x,这将简单地复制列表。

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

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

于 2016-03-28T20:07:20.617 回答
3

一种不同的思考方式 - 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对于 summa 和[]对于 reverso)以及第一个值和递归调用之间的运算符(+对于 summa 和(\q qs -> qs ++ [q])对于 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 重写逆向,并将运算符、初始值和列表本身传递给它:

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

这再次具有与 summa 和 reverso 非常相似的结构。因此,我们应该能够使用 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 

至于map,我们也可以写成

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

因此,map可以用foldr编写,正如我们所见:

-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs
于 2016-07-24T17:42:29.473 回答
1

您的解决方案几乎可以工作。)问题是您的两个函数(在模式匹配内部和 lambda 表达式内部)都有两个不同的 x 绑定,因此您失去了对第一个元素的跟踪。

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)

这应该是诀窍:)。另外:您可以轻松编写函数无点样式。

于 2011-04-20T07:21:43.413 回答
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指的是最后一个元素。

于 2020-02-01T13:27:37.807 回答