在 Kotlin 折叠和缩减的区别,什么时候使用?

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

74734 次浏览

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

我想指出的主要功能差异(在另一个答案的注释中提到过,但可能很难理解)是,如果在一个空集合上执行 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)

Reduce -reduce()方法将给定的 收藏品转换为 单一结果

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

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

在这种情况下,fold是一个方便的工具

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

这里,我们提供了初始值。相反,对于 reduce(),如果集合是 空荡荡的,则将返回初始值,这将阻止您使用 RuntimeException

所提到的其他答案都没有提到的另一个不同之处是:

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
}

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

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

答案很简单

减少和折叠的结果是“一个 项目列表变了变成一个 单项”。

弃牌的情况下,我们提供了一个额外的参数除了列表,但在 减少的情况下,只有项目列表将被考虑。

弃牌

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


//output: stabilizerACFridge

在上面的情况下,考虑空调,冰箱从商店购买 & 他们给稳定器作为礼物(这将是在折叠参数通过)。你把这三样东西放在一起。请注意,freeGift 只能在第一次迭代中使用一次。

减速

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

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


//output: ACFridge

这两个函数的不同之处在于,old ()在第一步使用一个初始值作为累积值,而 reduce ()的第一步使用第一个和第二个元素作为第一步的操作参数。