ChatGPT解决这个技术问题 Extra ChatGPT

Scala中foldLeft和reduceLeft之间的区别

我已经了解了 foldLeftreduceLeft 之间的基本区别

左折叠:

必须传递初始值

减少左:

将集合的第一个元素作为初始值

如果集合为空,则抛出异常

还有其他区别吗?

有两种具有相似功能的方法的具体原因吗?

如果您将问题编辑为“Scala 中折叠和减少之间的差异”,那就太好了。

a
agilesteel

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

您的问题与左无关,而是关于减少和折叠之间的区别

区别根本不在于实现,只看签名。

这个问题与 Scala 没有任何关系,而是关于函数式编程的两个概念。

回到你的问题:

这是 foldLeft 的签名(也可以是 foldRight 来说明我要提出的观点):

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

这是 reduceLeft 的签名(同样,方向在这里并不重要)

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

这两个看起来非常相似,因此引起了混乱。 reduceLeftfoldLeft 的一个特例(顺便说一下,这意味着您有时可以通过使用它们中的任何一个来表达相同的东西)。

当您在 List[Int] 上调用 reduceLeft 时,它实际上会将整个整数列表缩减为单个值,该值将是 Int 类型(或 Int 的超类型,因此是 [B >: A]) .

当您在 List[Int] 上调用 foldLeft 时,它会将整个列表(想象一下滚动一张纸)折叠成一个值,但这个值甚至不必与 Int 相关(因此 {4 })。

这是一个例子:

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

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

观看 One Fold to rule them all 以获得更深入的解释。


您能解释一下为什么 BA 的超类型吗?看起来 B 实际上应该是 A 的子类型,而不是超类型。例如,假设 Banana <: Fruit <: Food,如果我们有一个 Fruit 列表,它似乎可能包含一些 Banana,但如果它包含任何 Food,那么类型将是 Food,正确?因此,在这种情况下,如果 BA 的超类型,并且有一个包含 BA 的列表,则该列表应该是 B 类型,而不是 A。你能解释一下这种差异吗?
我不确定我是否正确理解了您的问题。我 5 年前的回答是关于 reduce 函数的意思是,List[Banana] 可以简化为单个 Banana 或单个 Fruit 或单个 Food。因为 Fruit :> Banana 和 `Food :>香蕉'。
是的……这确实有道理,谢谢。我最初将其解释为“Banana 类型的列表可能包含 Fruit”,这是没有意义的。您的解释确实有道理——传递给 reduce()f 函数可能会导致 FruitFood,这意味着签名中的 B 应该是超类,而不是子类。
K
Kim Stebel

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

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

好答案。这也突出了为什么 fold 可以在空列表上工作而 reduce 不能。
R
Renaud

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 在列表中找不到至少一个值,则它不具有合法值。


M
Martin Smith

作为参考,如果将 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)
}

A
Alexey Romanov

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


C
Community

来自 Functional Programming Principles in Scala(马丁·奥德斯基):

函数 reduceLeft 是根据更通用的函数 foldLeft 定义的。 foldLeft 与 reduceLeft 类似,但将累加器 z 作为附加参数,在对空列表调用 foldLeft 时返回: (List (x1, ..., xn) foldLeft z)(op) = (...( z 操作 x1) 操作 ...) 操作 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 返回一个 U 类型的值,它不一定与 List[T] 类型相同,但 reduceLeft 返回一个与列表相同类型的值)。


H
Henry H.

要真正了解您在使用 fold/reduce 做什么,请查看以下内容:http://wiki.tcl.tk/17983 非常好的解释。一旦你了解了折叠的概念,reduce 将与上面的答案一起出现:list.tail.foldLeft(list.head)(_)


S
Saurav Sahu

Scala 2.13.3,演示:

val names = List("Foo", "Bar")
println("ReduceLeft: "+ names.reduceLeft(_+_))
println("ReduceRight: "+ names.reduceRight(_+_))
println("Fold: "+ names.fold("Other")(_+_))
println("FoldLeft: "+ names.foldLeft("Other")(_+_))
println("FoldRight: "+ names.foldRight("Other")(_+_))

输出:

ReduceLeft: FooBar
ReduceRight: FooBar
Fold: OtherFooBar
FoldLeft: OtherFooBar
FoldRight: FooBarOther