【问题标题】:FoldLeft using FoldRight in scala在scala中使用FoldRight的FoldLeft
【发布时间】:2013-06-12 18:34:17
【问题描述】:

在浏览Functional Programming in Scala时,我遇到了这个问题:

你能用 foldRight 来修正 foldLeft 吗?其他方式怎么样 周围?

在作者提供的解决方案中,他们提供了如下实现:

def foldRightViaFoldLeft_1[A,B](l: List[A], z: B)(f: (A,B) => B): B = 
    foldLeft(l, (b:B) => b)((g,a) => b => g(f(a,b)))(z)

  def foldLeftViaFoldRight[A,B](l: List[A], z: B)(f: (B,A) => B): B = 
    foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)

有人可以帮我追踪这个解决方案并让我了解这实际上是如何在 foldr 方面实现 foldl 的,反之亦然?

谢谢

【问题讨论】:

  • 这是来自 RWH(Real World Haskell)书的an exercise。黄色块内有一些非常有用的 cmets,标题为“Understanding foldl in terms of foldr”。
  • Haskell 的定义确实在 WTF/s 上击败了 Scala:myFoldl f z xs = foldr step id xs z where step x g a = g (f a x)
  • 是否有资源提供练习的解决方案?

标签: scala functional-programming currying fold higher-order-functions


【解决方案1】:

我们来看看

def foldLeftViaFoldRight[A,B](l: List[A], z: B)(f: (B,A) => B): B = 
  foldRight(l, (b:B) => b)((a,g) => b => g(f(b,a)))(z)

(其他折叠类似)。诀窍在于,在正确的折叠操作期间,我们不会构建B 类型的最终值。相反,我们构建了一个从BB 的函数。 fold 步骤采用类型为 a: A 的值和函数 g: B => B 并产生一个新函数 (b => g(f(b,a))): B => B。这个函数可以表示为gf(_, a)的组合:

  l.foldRight(identity _)((a,g) => g compose (b => f(b,a)))(z);

我们可以查看过程如下:对于l中的每个元素a,我们取部分应用b => f(b,a),这是一个函数B => B。然后,我们compose 所有这些函数,使得对应于最右边元素(我们开始遍历)的函数位于组合链的最左边。最后,我们在z 上应用大组合函数。这会产生一系列操作,从最左边的元素(在组合链中的最右边)开始,到最右边的元素结束。

更新:作为一个例子,让我们看看这个定义是如何在一个二元素列表上工作的。首先,我们将函数重写为

def foldLeftViaFoldRight[A,B](l: List[A], z: B)
                             (f: (B,A) => B): B =
{
  def h(a: A, g: B => B): (B => B) =
    g compose ((x: B) => f(x,a));
  l.foldRight(identity[B] _)(h _)(z);
}

现在让我们计算一下当我们传递List(1,2) 时会发生什么:

List(1,2).foldRight(identity[B] _)(h _)
  = // by the definition of the right fold
h(1, h(2, identity([B])))
  = // expand the inner `h`
h(1, identity[B] compose ((x: B) => f(x, 2)))
  =
h(1, ((x: B) => f(x, 2)))
  = // expand the other `h`
((x: B) => f(x, 2)) compose ((x: B) => f(x, 1))
  = // by the definition of function composition
(y: B) => f(f(y, 1), 2)

将此函数应用于z 产生

f(f(z, 1), 2)

根据需要。

【讨论】:

  • Pudlak - 谢谢。我知道这是一种将函数的​​执行推迟到 List 的最后一个元素的方法,但我仍然很难想象执行的踪迹。
  • 我还没有完全理解它,但我想我最终会在考虑你的答案时得到要点。感谢您的回复。
  • @sc_ray 我用一个显示表达式如何计算的示例更新了答案。
  • 我不明白为什么 (a,g) => b => g(f(b,a)) 可以重写为 (a,g) => g compose (b => f(b,a))?在第一种情况下,b 参数位于最左侧,而在第二种情况下,它已移动到函数 g 的参数内部。你能解释一下为什么这是有效的吗?
  • 我参加聚会有点晚了。作者指出这是一个难题。但是既然他们问了,他们一定认为有人跟随找到解决方案是可能的。如果我自己没有做到,我应该感到难过吗:-)?
【解决方案2】:

我刚刚做了这个练习,并想分享我是如何得出答案的(与问题中的基本相同,只是字母不同),希望它可能对某人有用。

作为背景,让我们从foldLeftfoldRight 的作用开始。例如,列表 [1, 2, 3] 上的 foldLeft 的结果与操作 * 和起始值 z 是值 ((z * 1) * 2) * 3

我们可以将 foldLeft 视为从左到右递增地使用列表的值。换句话说,我们最初从值z 开始(如果列表为空,结果会是这样),然后我们向foldLeft 揭示我们的列表从1 开始,值变为z * 1,然后foldLeft 看到我们的列表下一个有2 并且值变成了(z * 1) * 2,最后在作用于3 之后变成了值((z * 1) * 2) * 3

                             1    2    3
Initially:               z
After consuming 1:      (z * 1)
After consuming 2:     ((z * 1) * 2
After consuming 3:    (((z * 1) * 2) * 3

这个最终值是我们想要达到的值,除了(如练习要求我们)使用foldRight 代替。现在请注意,就像foldLeft 从左到右使用列表的值一样,foldRight 从右到左使用列表的值。所以在列表[1,2,3]上,

  • 此 foldRight 将作用于 3 和 [something],给出 [result]
  • 然后它将作用于 2 和 [result],给出 [result2]
  • 最后它将作用于 1 和 [result2] 给出最终表达式
  • 我们希望我们的最终表达式是(((z * 1) * 2) * 3

换句话说:使用foldRight,我们首先得到列表为空时的结果,然后是列表仅包含[3]的结果,然后是列表为[2, 3]的结果],最后列表的结果是 [1, 2, 3]。

也就是说,这些是我们想要达到的值,使用foldRight

                             1    2    3
Initially:                             z
After consuming 3:                 z * 3
After consuming 2:           (z * 2) * 3
After consuming 1:     ((z * 1) * 2) * 3

所以我们需要从z(z * 3)(z * 2) * 3((z * 1) * 2) * 3

作为,我们不能这样做:对于任意操作*,没有自然的方法可以从值(z * 3) 到值(z * 2) * 3。 (有乘法,因为它是可交换的和关联的,但我们只使用* 来代表任意操作。)

但作为函数,我们或许可以做到这一点!我们需要一个带有“占位符”或“洞”的函数:将z 放在适当的位置。

  • 例如在第一步之后(在作用于 3 之后)我们有占位符函数z => (z * 3)。或者更确切地说,作为一个函数必须采用任意值,而我们一直使用z 作为一个特定值,让我们把它写成t => (t * 3)。 (此函数应用于输入 z 给出值 (z * 3)。)
  • 在第二步之后(在作用于 2 和结果之后)我们有占位符函数 t => (t * 2) * 3 也许?

我们可以从第一个占位符函数转到下一个吗?让

      f1(t) = t * 3
and   f2(t) = (t * 2) * 3

f2 中的f1 是什么?

f2(t) = f1(t * 2)

是的,我们可以!所以我们想要的函数接受2f1 并给出f2。我们称之为g。我们有g(2, f1) = f2 f2(t) = f1(t * 2) 或者换句话说

g(2, f1) = 
    t => f1(t * 2)

让我们看看如果我们继续这样做是否可行:下一步将是g(1, f2) = (t => f2(t * 1)),RHS 与t => f1((t * 1) * 2))t => (((t * 1) * 2) * 3) 相同。

看起来有效!最后我们将z 应用于这个结果。

第一步应该是什么?我们在3f0 上应用g 以得到f1,其中f1(t) = t * 3 如上所述,但f1(t) = f0(t * 3) 也来自g 的定义。所以看起来我们需要f0 作为身份函数。


让我们重新开始。

Our foldLeft(List(1, 2, 3), z)(*) is ((z * 1) * 2) * 3
Types here: List(1, 2, 3) is type List[A]
             z is of type B
             * is of type (B, A) -> B
             Result is of type B
We want to express that in terms of foldRight
As above:
 f0 = identity. f0(t) = t.
 f1 = g(3, f0). So f1(t) = f0(t * 3) = t * 3
 f2 = g(2, f1). So f2(t) = f1(t * 2) = (t * 2) * 3
 f3 = g(1, f2). So f3(t) = f2(t * 1) = ((t * 1) * 2) * 3

最后我们在 z 上应用 f3 并得到我们想要的表达式。一切正常。所以

f3 = g(1, g(2, g(3, f0)))

表示 f3 = foldRight(xs, f0)(g)

让我们定义g,这次用任意函数s(x, y)代替x * y

  • g 的第一个参数是 A 类型
  • 第二个参数到g 是这些f 的类型,即B => B
  • 所以g 的类型是(A, (B=>B)) => (B=>B)
  • 所以g 是:

    def g(a: A, f: B=>B): B=>B = 
        (t: B) => f(s(t, a))
    

把所有这些放在一起

def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B = {
    val f0 = (b: B) => b

    def g(a: A, f: B=>B): B=>B =
        t => f(s(t, a))

    foldRight(xs, f0)(g)(z)
}

在阅读本书的这个级别上,我实际上更喜欢这种形式,因为它更明确且更容易理解。但是为了更接近解决方案的形式,我们可以内联f0g 的定义(我们不再需要声明g 的类型,因为它是foldRight 的输入并且编译器会推断它) ,给予:

def foldLeft[A, B](xs: List[A], z: B)(s: (B, A) => B): B =
  foldRight(xs, (b: B) => b)((a, f) => t => f(s(t, a)))(z)

这正是问题所在,只是使用了不同的符号。就 foldLeft 而言,对于 foldRight 也是如此。

【讨论】:

  • 哇!你很棒! :) 我试图理解这一点很长一段时间,我认为这是正确的解释。谢谢!!
【解决方案3】:

该代码将多个函数对象链接在一起,列表中的每个元素一个函数。这是一个更清楚地表明这一点的示例。

val f = (a: Int, b: Int) => a+b
val list = List(2,3,4)
println(list.foldLeft(1)(f))

val f1 = (b: Int) => f(b, 2)
val f2 = (b: Int) => f(b, 3)
val f3 = (b: Int) => f(b, 4)
val f4 = (b: Int) => b

val ftotal = f1 andThen f2 andThen f3 andThen f4
println(ftotal(1))

你可以把它想象成一个函数对象的链表。当您传入一个值时,它会“流动”通过所有函数。这有点像数据流编程。

【讨论】:

    【解决方案4】:

    本书的作者在他们的github/fpinscala 页面上提供了很好的解释。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 1970-01-01
      • 2012-03-25
      • 1970-01-01
      • 2017-03-25
      • 2011-09-09
      相关资源
      最近更新 更多