Skip to content

聚合操作

Kotlin 集合中包含用于常用 聚合操作 的函数——这些操作根据集合内容返回单个值。其中大多数都广为人知,并以与其他语言相同的方式工作:

  • minOrNull()maxOrNull() 分别返回最小和最大的元素。对于空集合,它们返回 null
  • average() 返回数字集合中元素的平均值。
  • sum() 返回数字集合中元素的总和。
  • count() 返回集合中元素的数量。
kotlin

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。此外还有一些替代函数——maxOfminOfmaxOfWithminOfWith——它们执行与对应函数相同的功能,但在空集合上会抛出 NoSuchElementException 异常。

kotlin

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(),它接受一个选择器函数,并返回其应用于所有集合元素的总和。选择器可以返回不同的数字类型:IntLongDoubleUIntULong(在 JVM 上还支持 BigIntegerBigDecimal)。

kotlin

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

折叠与归约

对于更特定的情况,可以使用函数 reduce()fold(),它们将提供的操作顺序地应用于集合元素并返回累积结果。该操作接受两个参数:之前累积的值和集合元素。

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

kotlin
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(),但从最后一个元素开始,然后继续到前一个。请注意,当进行右折叠或右归约时,操作参数的顺序会改变:先是元素,然后是累积值。

kotlin

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()

kotlin

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)
}

所有归约操作在空集合上都会抛出异常。若要改为接收 null,请使用其 *OrNull() 对应的函数:

对于你想保存中间累加器值的情况,可以使用函数 runningFold()(或其同义词 scan())和 runningReduce()

kotlin

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("
", "使用 runningReduce 计算前 N 个元素的和:
"))
    println(runningFoldSum.mapIndexed(transform).joinToString("
", "使用 runningFold 计算前 N 个元素的和:
"))
}

如果你需要在操作参数中包含索引,请使用 runningFoldIndexed()runningReduceIndexed()