【问题标题】:Haskell, Foldr, and foldlHaskell、Foldr 和 foldl
【发布时间】:2015-01-08 17:02:42
【问题描述】:

我一直试图围绕 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。

为什么会这样?我知道当您将 [1,2,3,4] 传递给定义为的函数时

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

它被扩展为 ((([] ++ [1]) ++ [2]) ++ [3]) ++ [4]

同样,上述函数 a、b、c 和 d 扩展为什么?

【问题讨论】:

  • 您是否搜索过其他与 foldr/foldl 相关的问题?他们中的许多人展示了扩展并且有很好的解释。您也可以尝试自己进行替换,用它们的定义替换对 fold 的递归调用,以便对正在发生的事情有一个很好的直觉。
  • 我确实搜索了一些解释,但我仍然无法理解它们。他们都没有成功地向我展示如何将 foldl 和 foldr 调用替换为上述表达式。我不确定如何自己进行替换,这就是我问这个问题的原因。或者你的意思是我看看 foldr 和 foldl 是如何递归定义的,并分别用 a、b、c 和 d 遍历它们。听起来我在寻找捷径很麻烦,但我想如果没有其他方法......
  • 我会确保在我弄清楚答案后发表评论
  • 你见过foldl.comfoldr.com吗?
  • 我还没有,我去看看:)

标签: haskell fold


【解决方案1】:

我认为Haskell Wiki's fold page 上的两张图片很好地解释了这一点。

由于您的操作不是可交换的,foldrfoldl 的结果将不同,而在可交换操作中它们会:

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

使用scanlscanr 获取包含中间结果的列表是查看发生了什么的好方法:

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

所以在第一种情况下我们有 (((1 * 1) * 2) * 3),而在第二种情况下是 (1 * (2 * (1 * 3)))。

【讨论】:

    【解决方案2】:

    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 函数的参数中的位置不同)

    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
    

    【讨论】:

      【解决方案3】:

      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
      

      我们看到该函数首先从列表中获取一个元素,然后是累加器,并返回下一个累加器将是什么。有了这个,我们就可以编写自己的foldr函数了:

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

      你来了;您应该对 foldr 的工作原理有更好的了解,以便将其应用于上述问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-09-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多