9.1.1.9.2 聚合操作

原文链接: https://kotlinlang.org/docs/collection-aggregate.html

9.1.1.9.2 聚合操作

Kotlin 集合包含用于常见聚合操作的函数——聚合操作是只根据集合内容返回单个值的操作。其中大多数都很常见,并且行为与其他语言中相同:

  • minOrNull() 和 maxOrNull() 分别返回最小和最大的元素。对空集合它们返回 null。
  • average() 返回数字集合中元素的平均值。
  • sum() 返回数字集合中元素的总和。
  • count() 返回集合中元素的个数。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

fun main() {
    val numbers = listOf(6, 42, 10, 4)

    println("Count: ${numbers.count()}")
    println("Max: ${numbers.maxOrNull()}")
    println("Min: ${numbers.minOrNull()}")
    println("Average: ${numbers.average()}")
    println("Sum: ${numbers.sum()}")
}

还有一些函数用于按某个选择器函数或自定义 Comparator 获取最小和最大的元素:

这些函数对空集合返回 null。也有替代版本——maxOf、minOf、maxOfWith 和 minOfWith——它们的作用与对应版本相同,但对空集合会抛出 NoSuchElementException。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

fun main() {
    val numbers = listOf(5, 42, 10, 4)
    val min3Remainder = numbers.minByOrNull { it % 3 }
    println(min3Remainder)

    val strings = listOf("one", "two", "three", "four")
    val longestString = strings.maxWithOrNull(compareBy { it.length })
    println(longestString)
}

除了常规的 sum() 之外,还有一个高级求和函数 sumOf(),它接收一个选择器函数,并返回把它应用于所有集合元素后的总和。选择器可以返回不同的数值类型:Int、Long、Double、UInt 和 ULong(在 JVM 上还有 BigInteger 和 BigDecimal)。

1
2
3
4
5
6

fun main() {
    val numbers = listOf(5, 42, 10, 4)
    println(numbers.sumOf { it * 2 })
    println(numbers.sumOf { it.toDouble() / 2 })
}

Fold 与 reduce

针对更具体的场景,还有 reduce() 和 fold() 函数,它们把提供的操作依次应用于集合元素并返回累积结果。该操作接收两个参数:此前累积的值和当前集合元素。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fun main() {
    val numbers = listOf(5, 2, 10, 4)

    val simpleSum = numbers.reduce { sum, element -> sum + element }
    println(simpleSum)
    val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
    println(sumDoubled)

    // 不正确:结果中第一个元素没有被加倍
    // val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
    // println(sumDoubledReduce)
}

上面的例子展示了这种差异:fold() 用于计算各元素两倍后的总和。如果你把同样的函数传给 reduce(),它会返回另一个结果,因为它在第一步使用列表的第一个和第二个元素作为参数,所以第一个元素不会被加倍。

要按相反顺序对元素应用函数,请使用 reduceRight() 和 foldRight() 函数。它们的工作方式与 fold() 和 reduce() 类似,但从最后一个元素开始,然后继续向前。注意,在从右往左折叠或归约时,操作的参数顺序会改变:先是元素,然后是累积值。

1
2
3
4
5
6

fun main() {
    val numbers = listOf(5, 2, 10, 4)
    val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 }
    println(sumDoubledRight)
}

你也可以应用接收元素索引作为参数的操作。为此,请使用 reduceIndexed() 和 foldIndexed() 函数,把元素索引作为操作的第一个参数传入。

最后,还有一些函数会从右到左对集合元素应用这类操作——reduceRightIndexed() 和 foldRightIndexed()。

1
2
3
4
5
6
7
8
9

fun main() {
    val numbers = listOf(5, 2, 10, 4)
    val sumEven = numbers.foldIndexed(0) { idx, sum, element -> if (idx % 2 == 0) sum + element else sum }
    println(sumEven)

    val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
    println(sumEvenRight)
}

所有 reduce 操作在空集合上都会抛出异常。要改为得到 null,请使用它们的 *OrNull() 版本:

如果你想保存中间累积值,可以使用 runningFold()(或其同义词 scan())和 runningReduce() 函数。

1
2
3
4
5
6
7
8
9

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

如果你需要在操作参数中使用索引,请使用 runningFoldIndexed() 或 reduceIndexed 的对应版本。