[毎日Kotlin] Day32. Compound tasks (Sequence)

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

はじめに

毎日Kotlinシリーズです。

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

問題

Compound tasks | Try Kotlin

Implement Customer.getMostExpensiveDeliveredProduct() and Shop.getNumberOfTimesProductWasOrdered() using functions from the Kotlin standard library.

// Return the most expensive product among all delivered products
// (use the Order.isDelivered flag)
fun Customer.getMostExpensiveDeliveredProduct(): Product? {
    TODO()
}

// Return how many times the given product was ordered.
// Note: a customer may order the same product for several times.
fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
    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 Customer.getMostExpensiveDeliveredProduct(): Product? {
    return orders.filter { it.isDelivered }.flatMap { it.products }.maxBy { it.price }
}

fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
    return customers.flatMap { it.getOrderedProductsList() }.count { it == product }
}

fun Customer.getOrderedProductsList(): List<Product> {
    return orders.flatMap { it.products }
}

今までの復習問題です。複数組み合わせ、シンプルに問題を解いていきましょう。

今日はこれで終わるとさみしいので、Sequenceを使って解いてみましょう。

別解:Sequence

いままでの処理の仕方はデータ数が少ないときには有効です。逆に言うと100万件など大量のデータを処理するときには向きません。

たとえば、これを実行するとループ回数はいくつでしょうか?

(1..10).map { it * 10 }.map { it * 10 }.map { it * 10 }

ループ回数は30回です。map毎にListをつくって、そのリストをさらにmapしていきます。一時的に使用するメモリ量は、最初と各mapでできるListなので4つのListが必要になります。filterなど他のでも同じです。

かなりムダだと思いませんか?しょぼいけど、このコードなら10回で済みます。

    for (it in 1..10) {
        var v = it * 10
        v = v * 10
        v = v * 10
    }

データ量が10個なら10回も30回もかわらないですが、100万回など大きくなっていくとメモリ的にもつらくなってきたり、速度も気にするぐらいになっていく可能性があります。

そこでSequenceです。使い方は簡単asSequenceにするだけです。まったく同じ名前のものがあるのでそれを使います。一部違いますが、ドキュメントをみればすぐにわかります。

(1..10).asSequence().map { it * 10 }.map { it * 10 }.map { it * 10 }.toList()

ループ回数が10回になります。Sequenceは1つのデータを最後まで処理して次の要素にいくためです。一時的に必要なメモリは、最初のList分と最後の結果のリストだけで済みます。

僕が説明するよりも詳しく優しく解説しておりますので、こちらを参照してください。

KotlinとSequence<T>と巨大なファイル - Qiita

Sequence版の解答例

fun Customer.getMostExpensiveDeliveredProduct(): Product? {
    return orders.asSequence().filter { it.isDelivered }.flatMap { it.products.asSequence() }.maxBy { it.price }
}

fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
    return customers.asSequence().flatMap { it.getOrderedProductsList().asSequence() }.count { it == product }
}

fun Customer.getOrderedProductsList(): List<Product> {
    return orders.asSequence().flatMap { it.products.asSequence() }.toList()
}

あとがき

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