集計操作
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() があります。 この操作は、以前に累積された値とコレクションの要素の2つの引数を取ります。
これら2つの関数の違いは、fold() は初期値を受け取り、それを最初のステップの累積値として使用するのに対し、reduce() は最初のステップで第1要素と第2要素を操作の引数として使用する点です。
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)
//不正確:結果において最初の要素が2倍になりません
//val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
//println(sumDoubledReduce)
}上記の例はその違いを示しています。fold() は要素を2倍にした合計を計算するために使用されています。 同じ関数を reduce() に渡すと、最初のステップでリストの第1要素と第2要素を引数として使用するため、別の結果を返します。そのため、第1要素は2倍になりません。
要素に逆順で関数を適用するには、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)
}要素のインデックスをパラメータとして受け取る操作を適用することもできます。 この目的には、操作の第1引数として要素のインデックスを渡す 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() を使用してください。
