聚合操作
Kotlin 集合包含一些常用的聚合操作 (aggregate operations) 的函数——这些操作基于集合的内容返回一个单一的值。它们中的大多数都是众所周知的,并且工作方式与其他语言中的一样:
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)
//incorrect: the first element isn't doubled in the result
//val sumDoubledReduce = numbers.reduce { sum, element `->` sum + element * 2 }
//println(sumDoubledReduce)
}
上面的示例显示了区别:fold()
用于计算加倍元素的总和。如果将相同的函数传递给 reduce()
,它将返回另一个结果,因为它在第一步中使用列表的第一个和第二个元素作为参数,因此第一个元素不会加倍。
要以相反的顺序将函数应用于元素,请使用函数 reduceRight()
和 foldRight()
。它们的工作方式类似于 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()
。