reduceLeft、reduceRight 还是 foldLeft、foldRight

什么时候我应该使用reduceLeftreduceRightfoldLeftfoldRightscanLeftscanRight?

我想要对它们的区别有一个直观/概述——可能用一些简单的例子。

80861 次浏览

一般来说,所有6重函数都对集合的每个元素应用二进制操作符。每一步的结果都传递给下一步(作为二进制操作符两个参数之一的输入)。这样我们就可以累积一个结果。

reduceLeftreduceRight累积一个结果。

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

scanLeftscanRight使用起始值累积中间累积结果的集合。

积累

从左到前……

通过元素集合abc和二进制操作符add,我们可以探索不同的fold函数在从集合的左元素(从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
< p > < br >

如果我们从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)

De-cumulate

从左到前……

相反,如果我们要通过从集合的LEFT元素开始减法来de-cumulate某个结果,我们将通过二进制操作符minus的第一个参数res来累积结果:

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

但是现在请注意xRight的变化!记住,xRight变量中的(去)累计值被传递给二进制操作符minus第二个参数res:

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)

最后一个List(- 2,3,1,4,0)可能不是你直观上所期望的!

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

底线

  • reduceLeftreduceRight累积一个结果。
  • 如果有起始值,则用foldLeftfoldRight累积结果。
  • scanLeftscanRight累积中间结果的集合。

  • 如果你想在集合中向前,则使用xLeft变体。

  • 如果你想在集合中遍历向后,则使用xRight变体。

通常情况下,REDUCE,FOLD,SCAN方法通过在左侧积累数据并不断改变右侧变量来工作。它们之间的主要区别是REDUCE,FOLD是:-

Fold将始终以seed值开始,即用户定义的起始值。 如果集合为空,则Reduce将抛出异常,而fold则返回种子值。将始终产生单个值。 < / p >

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

  • LEFT_REDUCE方法的工作原理类似于REDUCE方法。
  • RIGHT_REDUCE与reduclefefone相反,即它在右边积累值,并不断改变左边的变量。

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

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

在数字列表上使用scan操作(使用seed0) List(-2,-1,0,1,2)

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

  • {0, 2} = > 2 {2, 1} = > 3 {3 0} = > 3 {3 1} = > 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,} = > 0 scanLeft (b + a)列表(0,2、3、3、2 0)

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

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

使用reducefold操作对字符串列表List("A","B","C","D","E")

  • {A、B} = > AB {AB, C} = > ABC {ABC, D} = > ABCD {ABCD E} = >中的减少(A + B)中的
  • {A,B}=>AB {AB,C}=>ABC {ABC,D}=>ABCD {ABCD,E}=>ABCDE reducleft (A + B) ABCDE
  • {A,B}=>BA {BA,C}=>CBA {CBA,D}=>DCBA {DCBA,E}=>EDCBA reducelefleft (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}))
}

对于包含元素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)
    • 李< / ul > < / >
    • 不是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
      • 李< / ul > < / >