Kotlin中折叠和减少之间的区别,何时使用?

Posted

技术标签:

【中文标题】Kotlin中折叠和减少之间的区别,何时使用?【英文标题】:Difference between fold and reduce in Kotlin, When to use which? 【发布时间】:2017-11-09 19:07:25 【问题描述】:

我对 Kotlin 中的 fold()reduce() 这两个函数感到很困惑,谁能给我一个具体的例子来区分它们?

【问题讨论】:

fold 和 reduce。 查看this,了解有关该主题的深入基础讨论 @LunarWatcher,我看到了那些文档,但没有得到它,这是你发布的问题,你能举个例子吗? @MattKlein 完成 【参考方案1】:

fold 接受一个初始值,传递给它的 lambda 的第一次调用将接收该初始值和集合的第一个元素作为参数。

例如,使用以下代码计算整数列表的总和:

listOf(1, 2, 3).fold(0)  sum, element -> sum + element 

对 lambda 的第一次调用将使用参数 01

如果您必须为您的操作提供某种默认值或参数,那么能够传入初始值会很有用。例如,如果您正在查找列表中的最大值,但由于某种原因希望返回至少 10,您可以执行以下操作:

listOf(1, 6, 4).fold(10)  max, element ->
    if (element > max) element else max


reduce 不采用初始值,而是以集合的第一个元素作为累加器开始(在以下示例中称为 sum)。

例如,让我们再次对整数求和:

listOf(1, 2, 3).reduce  sum, element -> sum + element 

此处对 lambda 的第一次调用将使用参数 12

当您的操作不依赖于您所应用到的集合中的值以外的任何值时,您可以使用reduce

【讨论】:

很好的解释!我还要说,空集合不能减少,但可以折叠。 看,m 在 Kotlin 中处于非常初级的水平,你给出的第一个例子你能用一些步骤来解释它吗?最后的答案是什么?会有很大帮助 @TapanHP emptyList<Int>().reduce acc, s -> acc + s 会产生异常,但emptyList<Int>().fold(0) acc, s -> acc + s 是可以的。 reduce 还强制返回的 lambda 与列表成员的类型相同,而 fold 则不是这样。这是使列表的第一个元素成为累加器的初始值的重要结果。 @andresp:作为完整性说明:它不必是 same 类型。列表成员也可以是累加器的子类型:这确实有效listOf<Int>(1, 2).reduce acc: Number, i: Int -> acc.toLong() + i (列表类型为 Int,而累加器类型被声明为 Number,实际上是 Long)【参考方案2】:

我要指出的主要功能差异(在另一个答案的 cmets 中提到,但可能难以理解)是 reduce 如果在空集合。

listOf<Int>().reduce  x, y -> x + y 
// java.lang.UnsupportedOperationException: Empty collection can't be reduced.

这是因为.reduce 不知道在“无数据”的情况下要返回什么值。

将此与.fold 进行对比,后者要求您提供一个“起始值”,如果集合为空,它将是默认值:

val result = listOf<Int>().fold(0)  x, y -> x + y 
assertEquals(0, result)

因此,即使您不想将集合聚合为不同(不相关)类型的单个元素(只有 .fold 允许您这样做),如果您的起始集合可能为空,那么您必须先检查您的收藏大小,然后再检查.reduce,或者只使用.fold

val collection: List<Int> = // collection of unknown size

val result1 = if (collection.isEmpty()) 0
              else collection.reduce  x, y -> x + y 

val result2 = collection.fold(0)  x, y -> x + y 

assertEquals(result1, result2)

【讨论】:

【参考方案3】:

reduce - reduce() 方法将给定的集合转换为单个结果

val numbers: List<Int> = listOf(1, 2, 3)
val sum: Int = numbers.reduce  acc, next -> acc + next 
//sum is 6 now.

fold - 在前面的 空列表 的情况下会发生什么?实际上,没有正确的返回值,所以reduce() 会抛出一个RuntimeException

在这种情况下,fold 是一个方便的工具。你可以通过它设置一个初始值 -

val sum: Int = numbers.fold(0,  acc, next -> acc + next )

在这里,我们提供了初始值。相反,对于reduce(),如果集合是empty,则会返回初始值,这将阻止您访问RuntimeException

【讨论】:

【参考方案4】:

没有提到其他答案的另一个区别如下:

reduce 操作的结果将始终与正在减少的数据属于同一类型(或超类型)。 从reduce方法的定义我们可以看出:

public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S 
    val iterator = this.iterator()
    if (!iterator.hasNext()) throw UnsupportedOperationException("Empty collection can't be reduced.")
    var accumulator: S = iterator.next()
    while (iterator.hasNext()) 
        accumulator = operation(accumulator, iterator.next())
    
    return accumulator

另一方面,折叠操作的结果可以是任何东西,因为在设置初始值时没有任何限制。 因此,例如,假设我们有一个包含字母和数字的字符串。我们要计算所有数字的总和。 我们可以通过 fold 轻松做到这一点:

val string = "1a2b3"
val result: Int = string.fold(0,  currentSum: Int, char: Char ->
    if (char.isDigit())
        currentSum + Character.getNumericValue(char)
    else currentSum
)

//result is equal to 6

【讨论】:

【参考方案5】:

简单答案

reduce 和 fold 的结果都是“项目列表转换单个项目”。

fold的情况下,我们在列表之外提供1个额外的参数,但在reduce的情况下,只考虑列表中的项目。

弃牌

listOf("AC","Fridge").fold("stabilizer")  freeGift, itemBought -> freeGift + itemBought 

//output: stabilizerACFridge

在上述情况下,将空调视为从商店购买的冰箱,他们将稳定器作为礼物赠送(这将是折叠中传递的参数)。所以,您将所有 3 件物品放在一起。

减少

在reduce的情况下,我们将列表中的项目作为参数,并可以对其执行所需的转换。

listOf("AC","Fridge").reduce  itemBought1, itemBought2 -> itemBought1 + itemBought2 

//output: ACFridge

【讨论】:

以上是关于Kotlin中折叠和减少之间的区别,何时使用?的主要内容,如果未能解决你的问题,请参考以下文章

javascript中self和this之间的区别以及何时使用它们中的任何一个[重复]

仅关联和聚合之间的区别以及何时使用它们

任何人都可以向我描述 Flutter 中 Material 和 MaterialApp 之间的区别,以及何时使用哪个?

知道 ko.applyBindings(vm) 何时结束并调用 vm.anymethod()

linux下.so.ko.a的区别

[译]KotlinConf2017概要