[毎日Kotlin] Day31. Fold

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

毎日Kotlinシリーズです。

このシリーズを初めての方はこちらです。「毎日Kotlin」はじめました | Developers.IO

問題

Fold | Try Kotlin

Implement Shop.getSetOfProductsOrderedByEveryCustomer() using fold.

listOf(1, 2, 3, 4).fold(1, {
    partProduct, element ->
    element * partProduct
}) == 24
// Return the set of products that were ordered by every customer
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    TODO()
}

data class Shop(val name: String, val customers: List<Customer>)

data class Customer(val name: String, val city: City, val orders: List<Order>) {
    override fun toString() = "$name from ${city.name}"
}

data class Order(val products: List<Product>, val isDelivered: Boolean)

data class Product(val name: String, val price: Double) {
    override fun toString() = "'$name' for $price"
}

data class City(val name: String) {
    override fun toString() = name
}

狙い

ここで考えて欲しい問題の意図はなんだろうか?

コレクションを処理する便利関数はたくさんあるので使って覚えよう。

解答例

fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
    val allProducts = customers.flatMap { it.orders.flatMap { it.products }}.toSet()
    return customers.fold(allProducts, {
        orderedByAll, customer ->
        orderedByAll.intersect(customer.orders.flatMap { it.products }.toSet())
    })
}

だんだん複数組み合わせて処理していますね。今回初登場のものをご紹介します。

foldは、再帰的に処理をしていきます。前の結果と今回の要素を処理して、その結果と次の要素が処理される。処理をラムダで書きます。

例えば、以下を例に説明

listOf(1, 2, 3, 4).fold(1, {
    partProduct, element ->
    element * partProduct
}) == 24
1(初期値)
=> 1 * 1 (次の要素)
=> 1(答え) * 2(次の要素)
=> 2(答え) * 3(次の要素)
=> 6(答え) * 4(次の要素)
=> 24

わかりやすい等価コードを書きました。

fun fold(list: List<Int>, initial: Int): Int {
    var accumulator = initial
    for (element in list) accumulator = accumulator * element
    return accumulator
}

上を一般的な記述にしたのが fold /_Arrays.kt at 1.2.30 · JetBrains/kotlin

public inline fun <T, R> Array<out T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

似たような関数reduceがあります。差は初期値を指定するかどうかです。

    listOf(1, 2, 3, 4).fold(1, { partProduct, element ->
        element * partProduct
    })

    listOf(1, 1, 2, 3, 4).reduce { partProduct, element ->
        element * partProduct
    }

intersectは重複の要素を抽出してくれます。こちらも便利ですね。

// [4, 8, 12]
listOf(4, 8, 12, 16, 20, 24).intersect(listOf(2, 4, 6, 8, 10, 12))

あとがき

Day32.でまたお会いしましょう。