Scala中reduceLeft和reduceRight之间的区别是什么?

时间:2015-09-23 01:49:35

标签: scala collections

Scala中reduceLeft和reduceRight之间的区别是什么?

  val list = List(1, 0, 0, 1, 1, 1)

  val sum1 = list reduceLeft  {_ + _}   
  val sum2 = list reduceRight {_ + _}

  println { sum2 == sum2 }

在我的代码段sum1 = sum2 = 4中,订单无关紧要。

3 个答案:

答案 0 :(得分:19)

它们何时产生相同的结果

正如莱昂内尔已经指出的那样,reduceLeftreduceRight只会产生相同的结果,如果你用来组合元素的函数是关联的(这并非总是如此,请参阅我的注释)底部)。例如,当使用函数reduceLeftreduceRight上运行Seq(1,2,3)(a: Int, b: Int) => a - b时,您会得到不同的结果。

scala> Seq(1,2,3)
res0: Seq[Int] = List(1, 2, 3) 

scala> res0.reduceLeft(_ - _)
res5: Int = -4

scala> res0.reduceRight(_ - _)
res6: Int = 2

如果我们查看如何在列表中应用每个函数,为什么会发生这种情况。

对于reduceRight,如果要解开它们,这就是调用的样子。

(1 - (2 - 3))
(1 - (-1))
2

对于reduceLeft,序列是从开始构建的,

((1 - 2) - 3)
((-1) - 3)
(-4)

尾递归

此外,因为reduceLeft是使用Tail Recursion实现的,所以当在非常大的集合(甚至可能是无限的集合)上运行时,它不会堆栈溢出。 reduceRight不是尾递归的,所以给定一个足够大的集合,它会产生堆栈溢出。

例如,在我的机器上,如果我运行以下内容,则会出现Out of Memory错误,

scala> (0 to 100000000).reduceRight(_ - _)
java.lang.OutOfMemoryError: GC overhead limit exceeded
  at java.lang.Integer.valueOf(Integer.java:832)
  at scala.runtime.BoxesRunTime.boxToInteger(BoxesRunTime.java:65)
  at scala.collection.immutable.Range.apply(Range.scala:61)
  at scala.collection.IndexedSeqLike$Elements.next(IndexedSeqLike.scala:65)
  at scala.collection.Iterator$class.foreach(Iterator.scala:742)
  at scala.collection.AbstractIterator.foreach(Iterator.scala:1194)
  at scala.collection.TraversableOnce$class.reversed(TraversableOnce.scala:99)
  at scala.collection.AbstractIterator.reversed(Iterator.scala:1194)
  at scala.collection.TraversableOnce$class.reduceRight(TraversableOnce.scala:197)
  at scala.collection.AbstractIterator.reduceRight(Iterator.scala:1194)
  at scala.collection.IterableLike$class.reduceRight(IterableLike.scala:85)
  at scala.collection.AbstractIterable.reduceRight(Iterable.scala:54)
  ... 20 elided

但如果我用reduceLeft计算,我就不会得到OOM,

scala> (0 to 100000000).reduceLeft(_ - _)
res16: Int = -987459712

根据您的JVM默认内存设置,您的系统可能会略有不同的结果。

首选左侧版本

因此,由于尾递归,如果您知道reduceLeftreduceRight将产生相同的值,您应该更喜欢reduceLeft变体。这通常适用于其他左/右功能,例如foldRightfoldLeft(这些只是reduceRightreduceLeft的更通用版本。)

他们什么时候确实总是产生相同的结果

关于您正在使用的函数的reduceLeftreduceRight以及关联属性的小注释。我说如果运算符是关联的,reduceRightreduceLeft只产生相同的结果。对于所有集合类型,并非总是如此。这虽然是另一个主题,所以请参考ScalaDoc,但简而言之,您正在减少的功能需要两者可交换和关联,以便为 all 集合类型。

答案 1 :(得分:1)

减少左侧并不总是等于与右侧相同的结果。考虑阵列上的非对称函数。

假设相同的结果,性能是一个明显的差异

请参阅performance-characteristics

构建数据结构,具有持续的头尾访问时间。向后迭代对大型列表的影响会更差。

答案 2 :(得分:-3)

了解差异的最佳方法是阅读library / scala / collection / LinearSeqOptimized.scala中的源代码:

  def reduceLeft[B >: A](f: (B, A) => B): B =
    ......
    tail.foldLeft[B](head)(f)

  def reduceRight[B >: A](op: (A, B) => B): B =
    ...... 
    op(head, tail.reduceRight(op))

  def foldLeft[B](z: B)(f: (B, A) => B): B = {
    var acc = z 
    var these = this              
    while (!these.isEmpty) {      
      acc = f(acc, these.head)    
      these = these.tail          
    }                             
    acc
  } 

以上是代码的一些关键部分,您可以看到reduceLeft基于foldLeft,而reduceRight是通过递归实现的。

我猜reduceLeft有更好的表现。