聚合操作

Kotlin 集合包含用于常用的 聚合操作 (基于集合内容返回单个值的操作)的函数 。 其中大多数是众所周知的,并且其工作方式与在其他语言中相同。

  • minOrNull()maxOrNull() 分别返回最小和最大的元素。 On empty collections, they return null.
  • average() 返回数字集合中元素的平均值。
  • sum() 返回数字集合中元素的总和。
  • count() 返回集合中元素的数量。
  1. fun main() {
  2. val numbers = listOf(6, 42, 10, 4)
  3. println("Count: ${numbers.count()}")
  4. println("Max: ${numbers.maxOrNull()}")
  5. println("Min: ${numbers.minOrNull()}")
  6. println("Average: ${numbers.average()}")
  7. println("Sum: ${numbers.sum()}")
  8. }

还有一些通过某些选择器函数或自定义 Comparator 来检索最小和最大元素的函数。

These functions return null on empty collections. There are also alternatives – maxOf, minOf, maxOfWith, and minOfWith – which do the same as their counterparts but throw a NoSuchElementException on empty collections.

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(5, 42, 10, 4)
  4. val min3Remainder = numbers.minByOrNull { it % 3 }
  5. println(min3Remainder)
  6. val strings = listOf("one", "two", "three", "four")
  7. val longestString = strings.maxWithOrNull(compareBy { it.length })
  8. println(longestString)
  9. //sampleEnd
  10. }

Besides regular sum(), there is an advanced summation function sumOf() that takes a selector function and returns the sum of its application to all collection elements. Selector can return different numeric types: Int, Long, Double, UInt, and ULong (also BigInteger and BigDecimal on the JVM).

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(5, 42, 10, 4)
  4. println(numbers.sumOf { it * 2 })
  5. println(numbers.sumOf { it.toDouble() / 2 })
  6. //sampleEnd
  7. }

fold 与 reduce

对于更特定的情况,有函数 reduce()fold(),它们依次将所提供的操作应用于集合元素并返回累积的结果。 操作有两个参数:先前的累积值和集合元素。

这两个函数的区别在于:fold() 接受一个初始值并将其用作第一步的累积值,而 reduce() 的第一步则将第一个和第二个元素作为第一步的操作参数。

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(5, 2, 10, 4)
  4. val simpleSum = numbers.reduce { sum, element -> sum + element }
  5. println(simpleSum)
  6. val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
  7. println(sumDoubled)
  8. //错误:第一个元素在结果中没有加倍
  9. //val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
  10. //println(sumDoubledReduce)
  11. //sampleEnd
  12. }

上面的实例展示了区别:fold() 用于计算加倍的元素之和。 如果将相同的函数传给 reduce(),那么它会返回另一个结果,因为在第一步中它将列表的第一个和第二个元素作为参数,所以第一个元素不会被加倍。

如需将函数以相反的顺序应用于元素,可以使用函数 reduceRight()foldRight() 它们的工作方式类似于 fold()reduce(),但从最后一个元素开始,然后再继续到前一个元素。 记住,在使用 foldRight 或 reduceRight 时,操作参数会更改其顺序:第一个参数变为元素,然后第二个参数变为累积值。

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(5, 2, 10, 4)
  4. val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 }
  5. println(sumDoubledRight)
  6. //sampleEnd
  7. }

你还可以使用将元素索引作为参数的操作。 为此,使用函数 reduceIndexed()foldIndexed() 传递元素索引作为操作的第一个参数。

最后,还有将这些操作从右到左应用于集合元素的函数——reduceRightIndexed()foldRightIndexed()

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(5, 2, 10, 4)
  4. val sumEven = numbers.foldIndexed(0) { idx, sum, element -> if (idx % 2 == 0) sum + element else sum }
  5. println(sumEven)
  6. val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
  7. println(sumEvenRight)
  8. //sampleEnd
  9. }

All reduce operations throw an exception on empty collections. To receive null instead, use their *OrNull() counterparts:

For cases where you want to save intermediate accumulator values, there are functions runningFold() (or its synonym scan()) and runningReduce().

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf(0, 1, 2, 3, 4, 5)
  4. val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
  5. val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }
  6. //sampleEnd
  7. val transform = { index: Int, element: Int -> "N = ${index + 1}: $element" }
  8. println(runningReduceSum.mapIndexed(transform).joinToString("\n", "Sum of first N elements with runningReduce:\n"))
  9. println(runningFoldSum.mapIndexed(transform).joinToString("\n", "Sum of first N elements with runningFold:\n"))
  10. }

If you need an index in the operation parameter, use runningFoldIndexed() or runningReduceIndexed().