简体   繁体   English

在scala中使用FoldRight的FoldLeft

[英]FoldLeft using FoldRight in scala

While going through Functional Programming in Scala , I came across this question: 在Scala中进行功能编程时 ,我遇到了这个问题:

Can you right foldLeft in terms of foldRight? 你可以在foldRight方面正确折叠左边吗? How about the other way around? 反过来怎么样?

In solution provided by the authors they have provided an implementation as follows: 在作者提供的解决方案中,他们提供了如下实现:

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)

Can somebody help me trace through this solution and make me understand how this actually gets the foldl implemented in terms of foldr and vice-versa? 有人可以帮助我追踪这个解决方案并让我理解这实际上是如何实现foldl实现的折叠,反之亦然?

Thanks 谢谢

Let's have a look at 我们来看看吧

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)

(the other fold is similar). (另一个折叠类似)。 The trick is that during the right fold operation, we don't build the final value of type B . 诀窍是在右折叠操作期间,我们不构建类型B的最终值。 Instead, we build a function from B to B . 相反,我们构建了一个从BB的函数。 The fold step takes a value of type a: A and a function g: B => B and produces a new function (b => g(f(b,a))): B => B . 折叠步骤采用类型a: A的值a: A和函数g: B => B并产生新函数(b => g(f(b,a))): B => B This function can be expressed as a composition of g with f(_, a) : 此函数可表示为gf(_, a)

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

We can view the process as follows: For each element a of l we take the partial application b => f(b,a) , which is a function B => B . 我们可以按如下方式查看该过程:对于l每个元素a ,我们采用部分应用b => f(b,a) ,这是函数B => B Then, we compose all these functions in such a way that the function corresponding to the rightmost element (with which we start the traversal) is at far left in the composition chain. 然后,我们组合所有这些函数,使得对应于最右边元素(我们开始遍历)的函数在组合链中最左边。 Finally, we apply the big composed function on z . 最后,我们在z上应用了大组合函数。 This results in a sequence of operations that starts with the leftmost element (which is at far right in the composition chain) and finishes with the right most one. 这导致一系列操作以最左边的元素(在组合链中最右边)开始,并以最右边的元素结束。

Update: As an example, let's examine how this definition works on a two-element list. 更新:举个例子,我们来看看这个定义在两元素列表上是如何工作的。 First, we'll rewrite the function as 首先,我们将函数重写为

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

Now let's compute what happens when we pass it List(1,2) : 现在让我们计算一下当我们传递它时会发生什么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)

Applying this function to z yields 将此函数应用于z产生

f(f(z, 1), 2)

as required. 按要求。

I just did this exercise, and would like to share how I arrived at an answer (basically the same as what's in the question, only the letters are different), in the hope that it may be useful to someone. 我刚刚做了这个练习,想分享我是如何得出答案的(基本上与问题中的内容相同,只是字母不同),希望对某人有用。

As background, let's start with what foldLeft and foldRight do. 作为背景,让我们从foldLeftfoldRight开始。 For example, the result of foldLeft on the list [1, 2, 3] with the operation * and starting value z is the value ((z * 1) * 2) * 3 例如,带有操作*和起始值z的列表[1,2,3]上的foldLeft的结果是值((z * 1) * 2) * 3

We can think of foldLeft as consuming values of the list incrementally, left to right. 我们可以将foldLeft视为从左到右递增地消耗列表的值。 In other words, we initially start with the value z (which is what the result would be if the list were empty), then we reveal to foldLeft that our list starts with 1 and the value becomes z * 1 , then foldLeft sees our list next has 2 and the value becomes (z * 1) * 2 , and finally, after acting on 3, it becomes the value ((z * 1) * 2) * 3 . 换句话说,我们最初从值z (这是列表为空的结果)开始,然后我们向foldLeft显示我们的列表以1开头并且值变为z * 1 ,然后foldLeft看到我们的列表next为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

This final value is the value we want to achieve, except (as the exercise asks us) using foldRight instead. 这个最终值是我们想要实现的值,除了(正如练习要求我们)使用foldRight Now note that, just as foldLeft consumes values of the list left to right, foldRight consumes values of the list right to left. 现在请注意,就像foldLeft使用列表的值一样, foldRight从右到左使用列表的值。 So on the list [1, 2, 3], 所以在列表[1,2,3],

  • This foldRight will act on 3 and [something], giving a [result] 这个foldRight将作用于3和[某事],给出[结果]
  • Then it will act on 2 and the [result], giving [result2] 然后它将作用于2和[结果],给出[result2]
  • Finally it will act on 1 and [result2] giving the final expression 最后它将作用于1和[result2]给出最终表达式
  • We want our final expression to be (((z * 1) * 2) * 3 我们希望我们的最终表达式为(((z * 1) * 2) * 3

In other words: using foldRight , we first arrive at what the result would be if the list were empty, then the result if the list contained only [3], then the result if the list were [2, 3], and finally the result for the list being [1, 2, 3]. 换句话说:使用foldRight ,我们首先得到的结果是列表为空时的结果,然后结果如果列表只包含[3],那么结果如果列表是[2,3],最后是列表的结果为[1,2,3]。

That is, these are the values we would like to arrive at, using 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

So we need to go from z to (z * 3) to (z * 2) * 3 to ((z * 1) * 2) * 3 . 所以我们需要从z(z * 3)(z * 2) * 3((z * 1) * 2) * 3

As values , we cannot do this: there's no natural way to go from the value (z * 3) to the value (z * 2) * 3 , for an arbitrary operation * . 作为 ,我们不能这样做:对于任意操作* ,没有自然的方法从值(z * 3)到值(z * 2) * 3 (There is for multiplication as it's commutative and associative, but we're only using * to stand for an arbitrary operation.) (存在乘法,因为它是可交换的和关联的,但我们只使用*代表任意操作。)

But as functions we may be able to do this! 但作为功能,我们可以做到这一点! We need to have a function with a "placeholder" or "hole": something that will take z and put it in the proper place. 我们需要一个带有“占位符”或“洞”的功能:将z带到适当位置的东西。

  • Eg after the first step (after acting on 3) we have the placeholder function z => (z * 3) . 例如,在第一步之后(在作用于3之后),我们有占位符函数z => (z * 3) Or rather, as a function must take arbitrary values and we've been using z for a specific value, let's write this as t => (t * 3) . 或者更确切地说,由于函数必须采用任意值,并且我们一直使用z作为特定值,让我们将其写为t => (t * 3) (This function applied on input z gives the value (z * 3) .) (应用于输入z此函数给出值(z * 3) 。)
  • After the second step (after acting on 2 and the result) we have the placeholder function t => (t * 2) * 3 maybe? 在第二步之后(在执行2和结果之后)我们有占位符函数t => (t * 2) * 3可能吗?

Can we go from the first placeholder function to the next? 我们可以从第一个占位符函数转到下一个吗? Let

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

What is f2 in terms of f1 ? f1 f2是多少?

f2(t) = f1(t * 2)

Yes we can! 我们可以! So the function we want takes 2 and f1 and gives f2 . 所以我们想要的函数取2f1并给出f2 Let's call this g . 我们称之为g We have g(2, f1) = f2 where f2(t) = f1(t * 2) or in other words 我们有g(2, f1) = f2其中f2(t) = f1(t * 2)或换句话说

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

Let's see if this would work if we carried it forward: the next step would be g(1, f2) = (t => f2(t * 1)) and the RHS is same as t => f1((t * 1) * 2)) or t => (((t * 1) * 2) * 3) . 让我们看看如果我们继续前进这是否会有效:下一步是g(1, f2) = (t => f2(t * 1)) ,RHS与t => f1((t * 1) * 2))相同t => f1((t * 1) * 2))t => (((t * 1) * 2) * 3)

Looks like it works! 看起来很有效! And finally we apply z to this result. 最后我们将z应用于此结果。

What should the initial step be? 初步步骤应该是什么? We apply g on 3 and f0 to get f1 , where f1(t) = t * 3 as defined above but also f1(t) = f0(t * 3) from the definition of g . 我们将g on 3f0应用于f1 ,其中f1(t) = t * 3如上所定义,但f1(t) = f0(t * 3)来自g的定义。 So looks like we need f0 to be the identity function. 所以看起来我们需要f0作为身份函数。


Let's start afresh. 让我们重新开始吧。

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

And finally we apply f3 on z and get the expression we want. 最后我们在z上应用f3并得到我们想要的表达式。 Everything works out. 一切顺利。 So 所以

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

which means f3 = foldRight(xs, f0)(g) 这意味着f3 = foldRight(xs, f0)(g)

Let's define g , this time instead of x * y using an arbitrary function s(x, y) : 让我们使用任意函数s(x, y)定义g ,这次代替x * y

  • first arg to g is of type A 第一个arg到gA类型
  • second arg to g is of the type of these f 's, which is B => B 第二个arg到g是这些f的类型,即B => B
  • So type of g is (A, (B=>B)) => (B=>B) 所以g类型是(A, (B=>B)) => (B=>B)
  • So g is: 所以g是:

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

Putting all this together 将所有这些放在一起

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

At this level of working through the book, I actually prefer this form as it's more explicit and easier to understand. 在本书的这个层面上,我实际上更喜欢这种形式,因为它更明确,更容易理解。 But to get closer to the form of the solution, we can inline the definitions of f0 and g (we no longer need to declare the type of g as it's input to foldRight and the compiler infers it), giving: 但是为了更接近解决方案的形式,我们可以内联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)

which is exactly what is in the question, just with different symbols. 这正是问题所在,只是用不同的符号。 Similarly for foldRight in terms of foldLeft. 类似地,对于foldRft而言,foldRight。

That code is chaining several function objects together, one function for each element in the list. 该代码将多个函数对象链接在一起,列表中的每个元素都有一个函数。 Here is an example that shows that more clearly. 这是一个更清楚地显示的例子。

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

You can imagine that as a linked list of function objects. 您可以将其想象为功能对象的链接列表。 When you pass in a value it "flows" through all the functions. 传入一个值时,它会“流过”所有函数。 It's a little like dataflow programming. 这有点像数据流编程。

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

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

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