聚合操作
Kotlin 集合包含用于常用聚合操作的函数——即基于集合内容返回单个值的操作。它们中的大多数都广为人知,且与其他语言中的工作方式相同:
minOrNull()和maxOrNull()分别返回最小和最大的元素。在空集合上,它们返回null。average()返回数字集合中元素的平均值。sum()返回数字集合中元素的总和。count()返回集合中的元素数量。
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来检索最小和最大的元素:
maxByOrNull()和minByOrNull()接受一个选择器函数并返回使该函数返回最大或最小值的元素。maxWithOrNull()和minWithOrNull()接受一个Comparator对象并根据该Comparator返回最大或最小的元素。maxOfOrNull()和minOfOrNull()接受一个选择器函数并返回选择器本身返回的最大或最小值。maxOfWithOrNull()和minOfWithOrNull()接受一个Comparator对象并根据该Comparator返回选择器返回的最大或最小值。
这些函数在空集合上返回null。此外还有替代方案——maxOf、minOf、maxOfWith 和 minOfWith——它们与对应函数的作用相同,但在空集合上会抛出NoSuchElementException。
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)。
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()的第一步则使用第一个和第二个元素作为第一步的操作参数。
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() 类似,但从最后一个元素开始,然后继续处理前面的元素。请注意,在进行 right fold 或 reduce 时,操作参数会改变顺序:首先是元素,然后是累加值。
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()。
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()。
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("
", "Sum of first N elements with runningReduce:
"))
println(runningFoldSum.mapIndexed(transform).joinToString("
", "Sum of first N elements with runningFold:
"))
}如果在操作参数中需要索引,请使用runningFoldIndexed() 或 runningReduceIndexed()。
