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を使用して、最小の要素と最大の要素を取得するための関数もあります。

  • maxByOrNull()minByOrNull()は、セレクタ関数を受け取り、それが最大または最小の値を返す要素を返します。
  • maxWithOrNull()minWithOrNull()は、Comparatorオブジェクトを受け取り、そのComparatorに従って最大または最小の要素を返します。
  • maxOfOrNull()minOfOrNull()は、セレクタ関数を受け取り、セレクタ自体の最大または最小の戻り値を返します。
  • maxOfWithOrNull()minOfWithOrNull()は、Comparatorオブジェクトを受け取り、そのComparatorに従って最大または最小のセレクタ戻り値を返します。

これらの関数は、空のコレクションではnullを返します。また、空のコレクションに対してNoSuchElementExceptionをスローするものの、対応する関数と同じ処理を行う代替の関数として、maxOfminOfmaxOfWithminOfWithがあります。

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上ではBigIntegerおよびBigDecimalも) といったさまざまな数値型を返すことができます。

kotlin
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()の最初のステップでは、最初の要素と2番目の要素を操作の引数として使用する点です。

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)

    //不正確: 結果で最初の要素が2倍になっていない
    //val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 } 
    //println(sumDoubledReduce)
}

上記の例は、その違いを示しています。fold()は2倍にされた要素の合計を計算するために使用されます。同じ関数をreduce()に渡すと、最初のステップでリストの最初の要素と2番目の要素を引数として使用するため、別の結果を返します。これにより、最初の要素が2倍にならないからです。

要素に逆順で関数を適用するには、reduceRight()およびfoldRight()関数を使用します。これらはfold()reduce()と同様に機能しますが、最後の要素から開始し、前の要素へと処理を続けます。右から畳み込み(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)
}

すべてのreduce操作は、空のコレクションに対して例外をスローします。代わりに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("
", "Sum of first N elements with runningReduce:
"))
    println(runningFoldSum.mapIndexed(transform).joinToString("
", "Sum of first N elements with runningFold:
"))
}

操作のパラメータにインデックスが必要な場合は、runningFoldIndexed()またはrunningReduceIndexed()を使用してください。