在scala中使用FoldRight的FoldLeft

时间:2013-06-16 19:14:56

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

在浏览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,反之亦然?

由于

4 个答案:

答案 0 :(得分:33)

我们来看看

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的函数。折叠步骤采用类型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);

我们可以按如下方式查看流程:对于a的每个元素l,我们采用部分应用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)

根据需要。

答案 1 :(得分:10)

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

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

我们可以将foldLeft视为从左到右递增地消耗列表的值。换句话说,我们最初从值z开始(如果列表为空,结果将是什么),然后我们向foldLeft显示我们的列表以1开头,值变为{ {1}},然后z * 1看到我们的列表接下来有foldLeft,值变为2,最后,在执行3后,它变为值(z * 1) * 2。< / p>

((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会从右到左使用列表的值。所以在列表[1,2,3],

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

换句话说:使用(((z * 1) * 2) * 3,我们首先得到的结果是列表为空时的结果,如果列表只包含[3]则得到结果,如果列表为[2]则为结果,3],最后列表的结果为[1,2,3]。

也就是说,这些是我们希望使用foldRight

得出的值
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 。 (因为它是可交换的和关联的,所以有乘法,但我们只使用*代表任意操作。)

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

  • E.g。在第一步之后(在执行3之后),我们有占位符函数z。或者更确切地说,由于函数必须采用任意值,并且我们一直使用z => (z * 3)作为特定值,我们将其写为z。 (此函数应用于输入t => (t * 3),提供值z。)
  • 在第二步之后(在执行2和结果之后),我们可能有占位符函数(z * 3)吗?

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

t =>  (t * 2) * 3

f1(t) = t * 3 and f2(t) = (t * 2) * 3 的{​​{1}}是什么?

f2

是的,我们可以!因此,我们想要的功能需要f1f2(t) = f1(t * 2) 并提供2。我们称之为f1。我们有f2其中g或换句话说

g(2, f1) = f2

如果我们继续前进,请看看这是否有效:下一步是f2(t) = f1(t * 2),RHS与g(2, f1) = t => f1(t * 2) g(1, f2) = (t => f2(t * 1))相同。

看起来很有效!最后,我们将t => f1((t * 1) * 2))应用于此结果。

最初的步骤应该是什么?我们在t => (((t * 1) * 2) * 3)z上应用g以获取3,其中f0如上所定义,但f1来自f1(t) = t * 3的定义}}。所以看起来我们需要f1(t) = f0(t * 3)作为身份函数。

让我们重新开始吧。

g

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

f0

表示f3 = 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

让我们使用任意函数f3 = g(1, g(2, g(3, f0))) 定义foldRight(xs, f0)(g),而不是g

  • 第一个arg到x * y的类型为s(x, y)
  • g的第二个arg属于这些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) } 的定义(我们不再需要声明f0的类型,因为它是{{1}的输入并且编译器推断它),给出:

g

这正是问题所在,只是用不同的符号。类似于foldRight的foldRight。

答案 2 :(得分:7)

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

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

您可以将其想象为功能对象的链接列表。传入一个值时,它会“流过”所有函数。这有点像数据流编程。

答案 3 :(得分:1)

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