聚合操作
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() 相似,但從最後一個元素開始,然後繼續處理之前的元素。請注意,在進行右側的 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()。
