Scala中foldLeft和reduceLeft之间的区别


196

我了解了foldLeft和之间的基本区别reduceLeft

foldLeft:

  • 必须传递初始值

reduceLeft:

  • 将集合的第一个元素作为初始值
  • 如果collection为空,则引发异常

还有其他区别吗?

是否有两种特定功能具有相似功能的特定原因?



如果您将问题编辑为“ Scala中折叠与缩小之间的差异”,那就太好了。
pedram bashiri

Answers:


302

在给出实际答案之前,这里要提到的几件事:

  • 您的问题与无关left,而是关于缩小和折叠之间的区别
  • 区别根本不是实现,只看签名。
  • 这个问题与Scala无关,尤其是关于函数编程的两个概念。

回到您的问题:

这是foldLeft(可能是foldRight我要说的)的签名:

def foldLeft [B] (z: B)(f: (B, A) => B): B

这是reduceLeft(再次的方向在这里无关紧要)的签名

def reduceLeft [B >: A] (f: (B, A) => B): B

两者看起来非常相似,因此引起了混乱。reduceLeft是的一种特殊情况foldLeft(顺便说一句,您有时可以通过使用它们之一来表达相同的内容)。

当你打电话reduceLeft说在List[Int]它的字面减少整数的整个列表为一个值,这将是类型Int(或者的超类型Int,因此[B >: A])。

当您foldLeft在上说时,List[Int]它会将整个列表折叠(想象一下要卷一张纸)成一个值,但是该值甚至不必与Int(因此[B])相关。

这是一个例子:

def listWithSum(numbers: List[Int]) = numbers.foldLeft((List.empty[Int], 0)) {
   (resultingTuple, currentInteger) =>
      (currentInteger :: resultingTuple._1, currentInteger + resultingTuple._2)
}

此方法采用a List[Int]并返回Tuple2[List[Int], Int]or (List[Int], Int)。它计算总和,并返回带有整数列表和其总和的元组。顺便说一下,列表是向后返回的,因为我们使用foldLeft而不是foldRight

观看“ 一折”,将其全部统治,以进行更深入的说明。


你能解释为什么B是超型A吗?似乎B实际上应该是的子类型A,而不是父类型。例如,假设Banana <: Fruit <: Food,如果我们有一个Fruits 列表,则似乎其中可能包含一些Bananas,但是如果它包含任何Foods,则类型为Food,对吗?因此,在这种情况下,如果B是的超类型,A并且存在同时包含Bs和As的列表,则该列表的类型应该B不是A。您能解释这个差异吗?
socom1880 '16

我不确定我是否正确理解您的问题。我5岁的孩子对reduce函数的回答是,List[Banana]可以将a简化为单个Banana或单个Fruit或单个Food。因为Fruit :> Banana和`Food:> Banana'。
agilesteel

是的,这确实很有意义,谢谢。我最初将其解释为“类型列表Banana可能包含Fruit”,这没有任何意义。您的解释确实是有道理的- f传递给该函数的reduce()结果可能是a Fruit或a Food,这意味着B签名中的应该是超类,而不是子类。
socom1880 '16

193

reduceLeft只是一种方便的方法。相当于

list.tail.foldLeft(list.head)(_)

11
好,简洁的答案:)可能要纠正拼写reducelft虽然
寒雪

10
好答案。这也突出说明了为什么fold在空白列表上工作而在空白列表reduce上不工作。
Mansoor Siddiqui 2015年

44

foldLeft更通用的是,您可以使用它来产生与原始内容完全不同的东西。而reduceLeft只能产生与集合类型相同或超类型的最终结果。例如:

List(1,3,5).foldLeft(0) { _ + _ }
List(1,3,5).foldLeft(List[String]()) { (a, b) => b.toString :: a }

foldLeft将应用与最后折叠结果闭合(使用初始值第一次)和下一个值。

reduceLeft另一方面,将首先合并列表中的两个值,并将其应用于闭包。接下来,它将剩余的值与累积结果相结合。看到:

List(1,3,5).reduceLeft { (a, b) => println("a " + a + ", b " + b); a + b }

如果列表为空,则foldLeft可以显示初始值作为合法结果。reduceLeft另一方面,如果它在列表中找不到至少一个值,则没有合法值。


5

它们都在Scala标准库中的基本原因可能是因为它们都在Haskell标准库中(称为foldlfoldl1)。如果reduceLeft不是这样,通常会在不同项目中将其定义为一种便捷方法。


5

作为参考,reduceLeft如果应用于具有以下错误的空容器,将出错。

java.lang.UnsupportedOperationException: empty.reduceLeft

修改代码以使用

myList foldLeft(List[String]()) {(a,b) => a+b}

是一种潜在的选择。另一种方法是使用reduceLeftOption返回Option包装结果的变量。

myList reduceLeftOption {(a,b) => a+b} match {
  case None    => // handle no result as necessary
  case Some(v) => println(v)
}

2

摘自Scala中的函数式编程原理(马丁·奥德斯基):

该函数reduceLeft是根据更通用的函数来定义的foldLeft

foldLeft就像,reduceLeft但是将一个accumulator z作为附加参数,当foldLeft在一个空列表上被调用时返回该参数:

(List (x1, ..., xn) foldLeft z)(op) = (...(z op x1) op ...) op x

[与相对reduceLeft,当在空列表上调用时会引发异常。

本课程(请参阅第5.5节)提供了这些功能的抽象定义,尽管它们在模式匹配和递归使用方面非常相似,但它们可以说明它们的区别。

abstract class List[T] { ...
  def reduceLeft(op: (T,T)=>T) : T = this match{
    case Nil     => throw new Error("Nil.reduceLeft")
    case x :: xs => (xs foldLeft x)(op)
  }
  def foldLeft[U](z: U)(op: (U,T)=>U): U = this match{
    case Nil     => z
    case x :: xs => (xs foldLeft op(z, x))(op)
  }
}

请注意,foldLeft返回的type值U不一定与相同List[T],但reduceLeft返回的值与列表相同。


0

要真正了解折叠/缩小功能,请检查以下内容:http : //wiki.tcl.tk/17983 很好的解释。一旦了解了折叠的概念,reduce将与上面的答案一起出现:list.tail.foldLeft(list.head)(_)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.