メインコンテンツまでスキップ

集約オペレーション

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を返します。代替として、maxOfminOfmaxOfWith、および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()があります。セレクタは、IntLongDoubleUInt、および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()の最初のステップでは、最初と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)

//incorrect: the first element isn't doubled in the result
//val sumDoubledReduce = numbers.reduce { sum, element `->` sum + element * 2 }
//println(sumDoubledReduce)

}

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

関数を逆の順序で要素に適用するには、関数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()を使用します。