缩小,折叠或扫描(左/右)?

时间:2013-07-01 16:04:30

标签: scala scala-collections reduce fold

我应该何时使用reduceLeftreduceRightfoldLeftfoldRightscanLeftscanRight

我想要直觉/概述他们的差异 - 可能还有一些简单的例子。

3 个答案:

答案 0 :(得分:343)

通常,所有6个折叠函数都将二元运算符应用于集合的每个元素。每个步骤的结果都传递给下一步(作为二元运算符的两个参数之一的输入)。这样我们就可以累积结果。

reduceLeftreduceRight累积了一个结果。

foldLeftfoldRight使用起始值累计单个结果。

scanLeftscanRight使用起始值累积一组中间累积结果。

累计

从左和右前进......

使用元素abc和二元运算符add的集合,我们可以探索从集合的LEFT元素(从A到C)前进时不同折叠函数的作用:

val abc = List("A", "B", "C")

def add(res: String, x: String) = { 
  println(s"op: $res + $x = ${res + x}")
  res + x
}

abc.reduceLeft(add)
// op: A + B = AB
// op: AB + C = ABC    // accumulates value AB in *first* operator arg `res`
// res: String = ABC

abc.foldLeft("z")(add) // with start value "z"
// op: z + A = zA      // initial extra operation
// op: zA + B = zAB
// op: zAB + C = zABC
// res: String = zABC

abc.scanLeft("z")(add)
// op: z + A = zA      // same operations as foldLeft above...
// op: zA + B = zAB
// op: zAB + C = zABC
// res: List[String] = List(z, zA, zAB, zABC) // maps intermediate results


从正确和向后......

如果我们从RIGHT元素开始并向后(从C到A),我们会注意到现在我们的二元运算符的第二个参数会累积结果(运算符是相同的,我们只是切换了参数名称以使其角色清晰明了):

def add(x: String, res: String) = {
  println(s"op: $x + $res = ${x + res}")
  x + res
}

abc.reduceRight(add)
// op: B + C = BC
// op: A + BC = ABC  // accumulates value BC in *second* operator arg `res`
// res: String = ABC

abc.foldRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: String = ABCz

abc.scanRight("z")(add)
// op: C + z = Cz
// op: B + Cz = BCz
// op: A + BCz = ABCz
// res: List[String] = List(ABCz, BCz, Cz, z)

德累积

从左和右前进......

如果我们通过从集合的LEFT元素开始的减法来去累积某些结果,我们将通过我们的二元运算符的第一个参数res累积结果{ {1}}:

minus


从正确和向后......

但是现在请注意xRight的变化!请记住,xRight变体中的(de-)累积值将传递给二元运算符val xs = List(1, 2, 3, 4) def minus(res: Int, x: Int) = { println(s"op: $res - $x = ${res - x}") res - x } xs.reduceLeft(minus) // op: 1 - 2 = -1 // op: -1 - 3 = -4 // de-cumulates value -1 in *first* operator arg `res` // op: -4 - 4 = -8 // res: Int = -8 xs.foldLeft(0)(minus) // op: 0 - 1 = -1 // op: -1 - 2 = -3 // op: -3 - 3 = -6 // op: -6 - 4 = -10 // res: Int = -10 xs.scanLeft(0)(minus) // op: 0 - 1 = -1 // op: -1 - 2 = -3 // op: -3 - 3 = -6 // op: -6 - 4 = -10 // res: List[Int] = List(0, -1, -3, -6, -10) second 参数res

minus

最后一个List(-2,3,-1,4,0​​)可能不是你想象的那样!

如您所见,您可以通过简单地运行scanX来检查您的foldX正在做什么,并在每一步调试累积结果。

底线

  • 使用def minus(x: Int, res: Int) = { println(s"op: $x - $res = ${x - res}") x - res } xs.reduceRight(minus) // op: 3 - 4 = -1 // op: 2 - -1 = 3 // de-cumulates value -1 in *second* operator arg `res` // op: 1 - 3 = -2 // res: Int = -2 xs.foldRight(0)(minus) // op: 4 - 0 = 4 // op: 3 - 4 = -1 // op: 2 - -1 = 3 // op: 1 - 3 = -2 // res: Int = -2 xs.scanRight(0)(minus) // op: 4 - 0 = 4 // op: 3 - 4 = -1 // op: 2 - -1 = 3 // op: 1 - 3 = -2 // res: List[Int] = List(-2, 3, -1, 4, 0) reduceLeft累积结果。
  • 如果您有起始值,请使用reduceRightfoldLeft累积结果。
  • 使用foldRightscanLeft累积一系列中间结果。

  • 如果您想通过集合转发,请使用xLeft变体。

  • 如果您想通过集合向后,请使用xRight变体。

答案 1 :(得分:9)

通常REDUCE,FOLD,SCAN方法通过在LEFT上累积数据并继续改变RIGHT变量来工作。它们之间的主要区别是REDUCE,FOLD是: -

折叠始终以seed值开始,即用户定义的起始值。 如果集合为空,则Reduce将抛出异常,其中fold会返回种子值。 始终会产生单一价值。

扫描用于左侧或右侧的项目的某些处理顺序,然后我们可以在后续计算中使用先前的结果。这意味着我们可以扫描物品。 将永远产生一个集合。

  • LEFT_REDUCE方法与REDUCE方法类似。
  • RIGHT_REDUCE与reduceLeft 1相反,即它在RIGHT中累积值并继续改变左变量。

  • reduceLeftOption和reduceRightOption类似于left_reduce和right_reduce只是区别在于它们在OPTION对象中返回结果。

下面提到的代码的输出部分是: -

对数字列表使用scan操作(使用seed0List(-2,-1,0,1,2)

  • {0,-2} => -2 {-2,-1} => -3 {-3,0} => -3 {-3,1} => - 2 {-2,2} => 0扫描列表(0,-2,-3,-3,-2,0)

  • {0,-2} => -2 {-2,-1} => -3 {-3,0} => -3 {-3,1} => - 2 {-2,2} => 0 scanLeft(a + b)列表(0,-2,-3,-3,-2,0)

  • {0,-2} => -2 {-2,-1} => -3 {-3,0} => -3 {-3,1} => - 2 {-2,2} => 0 scanLeft(b + a)列表(0,-2,-3,-3,-2,0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} = > 0 scanRight(a + b)列表(0,2,3,3,2,0)

  • {2,0} => 2 {1,2} => 3 {0,3} => 3 {-1,3} => 2 {-2,2} = > 0 scanRight(b + a)列表(0,2,3,3,2,0)

对字符串列表reduce

使用foldList("A","B","C","D","E")次操作
  • {A,B} => AB {AB,C} => ABC {ABC,D} => ABCD {ABCD,E} => ABCDE减少(a + b)ABCDE
  • {A,B} => AB {AB,C} => ABC {ABC,D} => ABCD {ABCD,E} => ABCDE reduceLeft(a + b)ABCDE
  • {A,B} => BA {BA,C} => CBA {CBA,D} => DCBA {DCBA,E} => EDCBA reduceLeft(b + a)EDCB
  • {D,E} => DE {C,DE} => CDE {B,CDE} => BCDE {A,BCDE} => ABCDE reduceRight(a + b)ABCDE
  • {D,E} => ED {C,ED} => EDC {B,EDC} => EDCB {A,EDCB} => EDCBA reduceRight(b + a)EDCBA

代码:

object ScanFoldReduce extends App {

    val list = List("A","B","C","D","E")
            println("reduce (a+b) "+list.reduce((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (a+b) "+list.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (b+a) "+list.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("reduceRight (a+b) "+list.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("reduceRight (b+a) "+list.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  ")
                b+a
            }))

            println("scan            "+list.scan("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanLeft (a+b)  "+list.scanLeft("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanLeft (b+a)  "+list.scanLeft("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))
            println("scanRight (a+b) "+list.scanRight("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))
            println("scanRight (b+a) "+list.scanRight("[")((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))
//Using numbers
     val list1 = List(-2,-1,0,1,2)

            println("reduce (a+b) "+list1.reduce((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (a+b) "+list1.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  ")
                a+b
            }))

            println("reduceLeft (b+a) "+list1.reduceLeft((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("      reduceRight (a+b) "+list1.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("      reduceRight (b+a) "+list1.reduceRight((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  ")
                b+a
            }))

            println("scan            "+list1.scan(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("scanLeft (a+b)  "+list1.scanLeft(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b
            }))

            println("scanLeft (b+a)  "+list1.scanLeft(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (b+a)+"  " )
                b+a
            }))

            println("scanRight (a+b)         "+list1.scanRight(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                a+b}))

            println("scanRight (b+a)         "+list1.scanRight(0)((a,b)=>{
                print("{"+a+","+b+"}=>"+ (a+b)+"  " )
                b+a}))
}

答案 2 :(得分:4)

对于元素x0,x1,x2,x3和任意函数f的集合x,您具有以下内容:

1. x.reduceLeft    (f) is f(f(f(x0,x1),x2),x3) - notice 3 function calls
2. x.reduceRight   (f) is f(f(f(x3,x2),x1),x0) - notice 3 function calls
3. x.foldLeft (init,f) is f(f(f(f(init,x0),x1),x2),x3) - notice 4 function calls
4. x.foldRight(init,f) is f(f(f(f(init,x3),x2),x1),x0) - notice 4 function calls
5. x.scanLeft (init,f) is f(init,x0)=g0
                          f(f(init,x0),x1) = f(g0,x1) = g1
                          f(f(f(init,x0),x1),x2) = f(g1,x2) = g2
                          f(f(f(f(init,x0),x1),x2),x3) = f(g2,x3) = g3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldLeft
6. x.scanRight (init,f) is f(init,x3)=h0
                          f(f(init,x3),x2) = f(h0,x2) = h1
                          f(f(f(init,x3),x2),x1) = f(h1,x1) = h2
                          f(f(f(f(init,x3),x2),x1),x0) = f(h2,x0) = h3
                          - notice 4 function calls but also 4 emitted values
                          - last element is identical with foldRight

总结

  • scan类似于fold,但还会发出所有中间值
  • reduce不需要初始值,有时候很难找到
  • fold需要一个很难找到的初始值:
    • 总和为0
    • 1个产品
    • 第一个min元素(可能会建议使用Integer.MAX_VALUE)
  • 不确定100%,但是看起来有这些等效的实现:
    • x.reduceLeft(f) === x.drop(1).foldLeft(x.head,f)
    • x.foldRight(init,f) === x.reverse.foldLeft(init,f)
    • x.foldLeft(init,f) === x.scanLeft(init,f).last