言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Kotlin辞典

  1. トップページ
  2. Kotlin辞典
  3. リスト — reduce() / fold()

リスト — reduce() / fold()

対応: Kotlin 1.0(2016)

Kotlinの『reduce()』は初期値なしでリストを畳み込みます。『fold()』は初期値を指定して畳み込みます。合計・積・文字列結合など、リストの要素を1つの値にまとめる処理に使います。

構文

val numbers = listOf(1, 2, 3, 4, 5)

// reduce — 初期値なし(最初の要素が初期値になります)
val sum = numbers.reduce { acc, n -> acc + n } // 15

// fold — 初期値あり(空リストでも安全)
val sumFold = numbers.fold(0) { acc, n -> acc + n } // 15
val product = numbers.fold(1) { acc, n -> acc * n } // 120

// 右から畳み込みます
val right = numbers.foldRight(0) { n, acc -> n + acc } // 15

メソッド一覧

メソッド概要
リスト.reduce { acc, v -> }左から畳み込みます。初期値はリストの最初の要素です。空リストは例外がスローされます。
リスト.reduceOrNull { acc, v -> }reduce と同じですが、空リストの場合は null を返します。
リスト.reduceRight { v, acc -> }右から畳み込みます(引数の順序に注意)。
リスト.fold(初期値) { acc, v -> }左から畳み込みます。初期値を指定できます。
リスト.foldRight(初期値) { v, acc -> }右から畳み込みます(引数の順序に注意)。
リスト.runningFold(初期値) { acc, v -> }各ステップの中間結果リストを返します(prefixSums など)。
リスト.runningReduce { acc, v -> }reduce の中間結果リストを返します。

サンプルコード

sample_list_reduce_fold.kt
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)

    // reduce — 合計(最初の要素が初期値)
    val sum = numbers.reduce { acc, n -> acc + n }
    println("合計: $sum") // 15

    // fold — 初期値を指定します(空リストでも安全)
    val product = numbers.fold(1) { acc, n -> acc * n }
    println("積: $product") // 120

    // fold で文字列を連結します
    val words = listOf("Kotlin", "is", "awesome")
    val sentence = words.fold("") { acc, w -> if (acc.isEmpty()) w else "$acc $w" }
    println("文章: $sentence") // Kotlin is awesome

    println()

    // 複雑な fold(合計を計算します)
    data class Staff(val name: String, val taskCount: Int, val rating: Int)
    val members = listOf(
        Staff("staff_a", 47, 120),
        Staff("staff_b", 63, 35),
        Staff("staff_c", 52, 55)
    )
    val total = members.fold(0) { acc, member -> acc + member.taskCount }
    println("担当件数合計: ${total}件") // 162件

    println()

    // runningFold — 累積和(各ステップの中間値を表示)
    val cumulative = numbers.runningFold(0) { acc, n -> acc + n }
    println("累積和: $cumulative") // [0, 1, 3, 6, 10, 15]

    // runningReduce — 累積最大値
    val runMax = numbers.map { it * it }.runningReduce { acc, n -> maxOf(acc, n) }
    println("累積最大: $runMax")

    println()

    // foldRight — 右から処理します(引数の順序に注意)
    val reversed = listOf("a", "b", "c").foldRight(mutableListOf()) { elem: String, acc: MutableList<String> ->
        acc.add(0, elem)
        acc
    }
    println("反転: $reversed")

    // 空リストの安全な処理
    val empty = emptyList<Int>()
    println("reduceOrNull: ${empty.reduceOrNull { acc, n -> acc + n }}") // null
    println("fold: ${empty.fold(0) { acc, n -> acc + n }}") // 0
}
list_reduce_fold.kt
kotlinc list_reduce_fold.kt -include-runtime -d list_reduce_fold.jar
java -jar list_reduce_fold.jar
合計: 15
積: 120
文章: Kotlin is awesome

担当件数合計: 162件

累積和: [0, 1, 3, 6, 10, 15]
累積最大: [1, 4, 9, 16, 25]

反転: [a, b, c]
reduceOrNull: null
fold: 0

よくあるミス

空リストに対する『reduce』、『foldRight』の引数順序、積演算の初期値に関してよくあるミスをまとめます。

ミス原因と対処
空リストに『reduce』を使って例外が発生する空リストに『reduce』を使うと『UnsupportedOperationException』が発生する。空リストに対応するには『reduceOrNull』または初期値を指定できる『fold』を使う。
『foldLeft』の書き方を『foldRight』に使う『foldRight』はラムダの第1引数が要素、第2引数が累積値になる(『foldLeft』とは逆順)。
掛け算の初期値に 0 を使う『fold(0) { acc, n -> acc * n }』は結果が必ず 0 になる(0 × 何でも 0)。掛け算の初期値は 1 を使う。

下記のサンプルコードで、正しい使い方を確認できます。

sample_list_reduce_fold_mistakes.kt
fun main() {
    // 空リストには fold か reduceOrNull を使う
    val empty = emptyList<Int>()
    val safeResult = empty.reduceOrNull { acc, n -> acc + n }
    println("reduceOrNull: $safeResult") // null
    val foldResult = empty.fold(0) { acc, n -> acc + n }
    println("fold: $foldResult") // 0

    // foldRight は要素が第1引数、累積値が第2引数
    val words = listOf("staff_a", "staff_b", "staff_c")
    val joined = words.foldRight("") { elem, acc ->
        if (acc.isEmpty()) elem else "$elem-$acc"
    }
    println("foldRight: $joined") // staff_a-staff_b-staff_c

    // 積の初期値は 1
    val numbers = listOf(1, 2, 3, 4, 5)
    val product = numbers.fold(1) { acc, n -> acc * n }
    println("積: $product") // 120

    // sum() の方が意図が明確
    val sumByReduce = numbers.reduce { acc, n -> acc + n }
    val sumBySum = numbers.sum()
    println("reduce: $sumByReduce, sum: $sumBySum")
}

コンパイルして実行すると次のようになります。

kotlinc sample_list_reduce_fold_mistakes.kt -include-runtime -d sample_list_reduce_fold_mistakes.jar
java -jar sample_list_reduce_fold_mistakes.jar
reduceOrNull: null
fold: 0
foldRight: staff_a-staff_b-staff_c
積: 120
reduce: 15, sum: 15

概要

『reduce()』は空リストで例外が発生するため、空の可能性があるリストには『fold()』か『reduceOrNull()』を使います。『fold()』はより汎用的で、初期値の型を変えることで「数値リストをマップに変換する」など異なる型への変換にも使えます。

単純な合計・最大値などには専用のショートハンド(『sum()』『max()』など)の方が簡潔です。『fold()』は複雑な集計処理に使います。

平坦化はリスト — flatMap() / flatten()を、グルーピングはリスト — groupBy() / partition()を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。