Haskell、Foldr和foldl
很长一段时间以来,我一直在想foldr和foldl,我决定下面的问题应该为我解决。假设您将以下列表[1,2,3]传递到以下四个函数中:Haskell、Foldr和foldl,haskell,fold,Haskell,Fold,很长一段时间以来,我一直在想foldr和foldl,我决定下面的问题应该为我解决。假设您将以下列表[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 结果将分别为-123、83、281和-321 为什么会这样?我知道
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
结果将分别为-123、83、281和-321
为什么会这样?我知道当你把[1,2,3,4]传递到一个定义为
f = foldl (xs x -> xs ++ [f x]) []
它被扩展为(([]++[1])++[2])++[3])++[4]
同样,上面的函数a、b、c和d扩展到了什么?我认为上面的两幅图很好地解释了这一点
由于您的操作不是可交换的,因此foldr
和foldl
的结果将不同,而在可交换操作中,它们将:
Prelude> foldl1 (*) [1..3]
6
Prelude> foldr1 (*) [1..3]
6
使用scanl
和scanr
获取包含中间结果的列表是查看发生了什么的好方法:
Prelude> scanl1 (*) [1..3]
[1,2,6]
Prelude> scanr1 (*) [1..3]
[6,6,3]
因此,在第一种情况下,我们有((1*1)*2)*3),而在第二种情况下,它是(1*(2*(1*3))。我认为上面的两个图像很好地解释了这一点
由于您的操作不是可交换的,因此foldr
和foldl
的结果将不同,而在可交换操作中,它们将:
Prelude> foldl1 (*) [1..3]
6
Prelude> foldr1 (*) [1..3]
6
使用scanl
和scanr
获取包含中间结果的列表是查看发生了什么的好方法:
Prelude> scanl1 (*) [1..3]
[1,2,6]
Prelude> scanr1 (*) [1..3]
[6,6,3]
因此,在第一种情况下,我们有((1*1)*2)*3),而在第二种情况下,它是(1*(2*(1*3))。我认为上面的两个图像很好地解释了这一点
由于您的操作不是可交换的,因此foldr
和foldl
的结果将不同,而在可交换操作中,它们将:
Prelude> foldl1 (*) [1..3]
6
Prelude> foldr1 (*) [1..3]
6
使用scanl
和scanr
获取包含中间结果的列表是查看发生了什么的好方法:
Prelude> scanl1 (*) [1..3]
[1,2,6]
Prelude> scanr1 (*) [1..3]
[6,6,3]
因此,在第一种情况下,我们有((1*1)*2)*3),而在第二种情况下,它是(1*(2*(1*3))。我认为上面的两个图像很好地解释了这一点
由于您的操作不是可交换的,因此foldr
和foldl
的结果将不同,而在可交换操作中,它们将:
Prelude> foldl1 (*) [1..3]
6
Prelude> foldr1 (*) [1..3]
6
使用scanl
和scanr
获取包含中间结果的列表是查看发生了什么的好方法:
Prelude> scanl1 (*) [1..3]
[1,2,6]
Prelude> scanr1 (*) [1..3]
[6,6,3]
因此,在第一种情况下,我们有(((1*1)*2)*3),而在第二种情况下,它是(1*(2*(1*3))。可以将
折叠*
函数视为在传递给它的列表上循环,从列表的末尾(foldr
)或列表的开头(foldl
)。对于它找到的每个元素,它都将该元素和累加器的当前值传递给您编写的lambda函数。此函数返回的任何值都将在下一次迭代中用作累加器的值
稍微更改您的符号(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)
对于第一次右折叠(注意累加器在lambda函数的参数中的位置不同)
fold*
函数可以看作是在传递给它的列表上循环,从列表的末尾(foldr
)或列表的开头(foldl
)开始。对于它找到的每个元素,它都将该元素和累加器的当前值传递给您编写的lambda函数。此函数返回的任何值都将在下一次迭代中用作累加器的值
稍微更改您的符号(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)
对于第一次右折叠(注意累加器在lambda函数的参数中的位置不同)
fold*
函数可以看作是在传递给它的列表上循环,从列表的末尾(foldr
)或列表的开头(foldl
)开始。对于它找到的每个元素,它都将该元素和累加器的当前值传递给您编写的lambda函数。此函数返回的任何值都将在下一次迭代中用作累加器的值
稍微更改您的符号(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)
对于第一次右折叠(注意累加器在lambda函数的参数中的位置不同)
fold*
函数可以看作是在传递给它的列表上循环,从列表的末尾(foldr
)或列表的开头(foldl
)开始。对于它找到的每个元素,它都将该元素和累加器的当前值传递给您编写的lambda函数。此函数返回的任何值都将在下一次迭代中用作累加器的值
稍微更改您的符号(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)
对于第一次右折叠(注意累加器在lambda函数的参数中的位置不同)
foldr
是一个非常简单的函数思想:获取一个包含两个参数的函数,获取一个起点、一个列表,并以这种方式计算调用列表中函数的结果
这里有一个很好的小提示,告诉你如何想象在foldr
调用过程中会发生什么:
foldr (+) 0 [1,2,3,4,5]
=> 1 + (2 + (3 + (4 + (5 + 0))))
我们都知道[1,2,3,4,5]=1:2:3:4:5:[]
。您需要做的就是用起点替换[]
,用我们使用的任何函数替换:
。当然,我们也可以用同样的方法重建列表:
foldr (:) [] [1,2,3]
=> 1 : (2 : (3 : []))
如果查看签名,我们可以更好地了解函数中发生的情况:
foldr :: (a -> b -> b) -> b -> [a] -> b
我们首先看到函数