跳至主要内容

聚合操作 (Aggregate operations)

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 來檢索最小和最大的元素:

這些函式在空集合上傳回 null。 還有一些替代方案 – maxOfminOfmaxOfWithminOfWith – 它們的功能與對應物相同,但在空集合上會拋出 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(),它接受一個選擇器函式,並傳回將其應用於所有集合元素的總和。 選擇器可以傳回不同的數值類型:IntLongDoubleUIntULong (在 JVM 上還有 BigIntegerBigDecimal)。


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