简体   繁体   English

Haskell,Foldr和foldl

[英]Haskell, Foldr, and foldl

I've been trying to wrap my head around foldr and foldl for quite some time, and I've decided the following question should settle it for me. 我已经尝试将我的头缠在折叠器和折叠器上一段时间了,并且我决定以下问题应该为我解决。 Suppose you pass the following list [1,2,3] into the following four functions: 假设您将以下列表[1,2,3]传递给以下四个函数:

a = foldl (\xs y -> 10*xs -y) 0
b = foldl (\xs y -> y - 10 * xs) 0
c = foldr (\y xs -> y - 10 * xs) 0
d = foldr (\y xs -> 10 * xs -y) 0

The results will be -123, 83, 281, and -321 respectively. 结果将分别为-123、83、281和-321。

Why is this the case? 为什么会这样呢? I know that when you pass [1,2,3,4] into a function defined as 我知道当您将[1,2,3,4]传递给定义为

f = foldl (xs x -> xs ++ [f x]) []

it gets expanded to ((([] ++ [1]) ++ [2]) ++ [3]) ++ [4] 它被扩展为(([[] ++ [1])++ [2])++ [3])++ [4]

In the same vein, What do the above functions a, b, c, and d get expanded to? 同样,上面的函数a,b,c和d被扩展为什么?

I think the two images on Haskell Wiki's fold page explain it quite nicely. 我认为Haskell Wiki的折叠页上的两张图片很好地解释了它。

Since your operations are not commutative, the results of foldr and foldl will not be the same, whereas in a commutative operation they would: 由于您的操作不是可交换的,因此foldrfoldl的结果将不同,而在可交换操作中,它们将:

Prelude> foldl1 (*) [1..3]
6
Prelude> foldr1 (*) [1..3]
6

Using scanl and scanr to get a list including the intermediate results is a good way to see what happens: 使用scanlscanr得到包括中间结果列表是为了看看会发生什么的好方法:

Prelude> scanl1 (*) [1..3]
[1,2,6]
Prelude> scanr1 (*) [1..3]
[6,6,3]

So in the first case we have (((1 * 1) * 2) * 3), whereas in the second case it's (1 * (2 * (1 * 3))). 因此,在第一种情况下我们有((((1 * 1)* 2)* 3),而在第二种情况下是(1 *(2 *(1 * 3)))。

foldr is a really simple function idea: get a function which combines two arguments, get a starting point, a list, and compute the result of calling the function on the list in that way. foldr是一个非常简单的函数思想:获取一个将两个参数组合在一起的函数,获取一个起点,一个列表,然后以这种方式计算在列表上调用该函数的结果。

Here's a nice little hint about how to imagine what happens during a foldr call: 这是关于如何想象在文件foldr调用期间会发生什么的一个很好的小提示:

foldr (+) 0 [1,2,3,4,5]
=> 1 + (2 + (3 + (4 + (5 + 0))))

We all know that [1,2,3,4,5] = 1:2:3:4:5:[] . 我们都知道[1,2,3,4,5] = 1:2:3:4:5:[] All you need to do is replace [] with the starting point and : with whatever function we use. 您需要做的只是将[]替换为起点,并将:替换为我们使用的任何函数。 Of course, we can also reconstruct a list in the same way: 当然,我们也可以用相同的方式重建列表:

foldr (:) [] [1,2,3]
=> 1 : (2 : (3 : []))

We can get more of an understanding of what happens within the function if we look at the signature: 如果我们看一下签名,我们将对函数内部发生的事情有更多的了解:

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

We see that the function first gets an element from the list, then the accumulator, and returns what the next accumulator will be. 我们看到函数首先从列表中获取一个元素,然后从累加器中获取,然后返回下一个累加器。 With this, we can write our own foldr function: 这样,我们可以编写自己的文件foldr功能:

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

And there you are; 你在那里; you should have a better idea as to how foldr works, so you can apply that to your problems above. 您应该对文件foldr工作方式有一个更好的了解,因此可以将其应用于上面的问题。

The fold* functions can be seen as looping over the list passed to it, starting from either the end of the list ( foldr ), or the start of the list ( foldl ). fold*函数可以看作是遍历传递给它的列表,它从列表的末尾( foldr )或列表的开头( foldl )开始。 For each of the elements it finds, it passes this element and the current value of the accumulator to what you have written as a lambda function. 对于找到的每个元素,它将将此元素和累加器的当前值传递给您作为lambda函数编写的内容。 Whatever this function returns is used as the value of the accumulator in the next iteration. 该函数返回的值将用作下一次迭代中累加器的值。

Slightly changing your notation ( acc instead of xs ) to show a clearer meaning, for the first left fold 稍微更改您的表示法( acc代替xs )以显示更清晰的含义(左一折)

a = foldl (\acc y -> 10*acc - y) 0              [1, 2, 3]
  = foldl (\acc y -> 10*acc - y) (0*1 - 1)      [2, 3]
  = foldl (\acc y -> 10*acc - y) -1             [2, 3]
  = foldl (\acc y -> 10*acc - y) (10*(-1) - 2)  [3]
  = foldl (\acc y -> 10*acc - y) (-12)          [3]
  = foldl (\acc y -> 10*acc - y) (10*(-12) - 3) []
  = foldl (\acc y -> 10*acc - y) (-123)         []
  = (-123)

And for your first right fold (note the accumulator takes a different position in the arguments to the lambda function) 对于您的第一个右折(请注意,累加器在lambda函数的参数中占据不同的位置)

c = foldr (\y acc -> y - 10*acc) 0              [1, 2, 3]
  = foldr (\y acc -> y - 10*acc) (3 - 10*0)     [1, 2]
  = foldr (\y acc -> y - 10*acc) 3              [1, 2]
  = foldr (\y acc -> y - 10*acc) (2 - 10*3)     [1]
  = foldr (\y acc -> y - 10*acc) (-28)          [1]
  = foldr (\y acc -> y - 10*acc) (1 - 10*(-28)) []
  = foldr (\y acc -> y - 10*acc) 281            []
  = 281

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM