【Kotlin練習問題】コレクション reduce()とfold()

Kotlin

reduce()とfold()による集計操作

コレクションの集計操作(最大値や最小値などを求める操作)に関しては以下の記事でも解説しました。

しかし、上記事で紹介した関数以外にも、reduce()fold()という関数があります。

これらは与えられた処理をコレクションの要素に順次行っていき、蓄積された結果を返り値として返します。処理では二つの引数を取り、一つ目が蓄積された値で二つ目がコレクションの要素です。

reduce()とfold()の違いは、fold()は初期値を設定することができ、それを一番最初に蓄積された値として使いますが、reduce()は一つ目の要素と二つ目の要素を最初の処理で引数として使います。

何はともあれ、まずは使い方を実際のコードで見てみるのが一番わかりやすいかと思います。

val numbers = listOf(5, 2, 10, 4)

val sum = numbers.reduce { sum, element -> sum + element }
println(sum) //21
val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
println(sumDoubled) //42

reduceの一回目の処理ではsumとelementという引数にnumbersというリストの最初の要素(5)と二番目の要素(2)が入っています。二回目の処理ではsumに7、elementに10というように順に処理を行っていくことで要素全体の合計を求めることができます。

一方でfoldのほうでは引数に0を与えているので、sumに0が代入されます。elementには5が入っています。処理の中身ではelementに代入された値を二倍してsumに足しているので、最終的には要素全体を2倍した数の合計が求められます。

上記のfoldで行っている処理をreduceで書こうとして失敗した例が以下になります。

val numbers = listOf(5, 2, 10, 4)
val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
println(sumDoubledReduce) //37

何が間違っているかわかりますか?

これも全て二倍にして足しているように見えますが、実は一回目の処理でsumには5が代入されますが、5は二倍になっていないのです。だからといって処理の中身で sum * 2 + element * 2のようにしてしまうと、処理ごとに合計が2倍されてしまうため、求めていた結果は得られません。

このようにして行わせたい処理に応じてfoldとreduceを使い分けることが大切です。

問題

foldを使って全ての顧客によって注文された商品のセットを返す関数を実装してください。

また前回の問題(コレクション flatMapでネスト構造をフラットにする)で実装したCustomer.getOrderedProducts()を使うこともできます。

なお、全ての関連するクラスはShop.ktに含まれています。

問題コード:

//全ての顧客によって注文された商品のセットを返す
fun Shop.getProductsOrderedByAll(): Set<Product> {
    TODO()
}

fun Customer.getOrderedProducts(): List<Product> =
    orders.flatMap(Order::products)

Shop.kt:

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
}

ヒント

2つのリストの積集合(リストAにもリストBにも含まれる要素)を求めるにはintersect関数を用います。

使用例)

val listA = listOf(1, 2, 3)
val listB = listOf(3, 4, 5)

val result = listA.intersect(listB)
println(result) //[3]
ヒント

答え

//全ての顧客によって注文された商品のセットを返す
fun Shop.getProductsOrderedByAll(): Set<Product> {
    val allProducts = customers.flatMap { it.getOrderedProducts() }.toSet()
    return customers.fold(allProducts, { orderedByAll, customer ->
        orderedByAll.intersect(customer.getOrderedProducts())
    })
}

fun Customer.getOrderedProducts(): List<Product> =
    orders.flatMap(Order::products)

[解説]

初めにallProductsという定数に一人でも顧客が注文した商品全てのセットを保存します。

それをfoldの初期値に設定し、その要素とそれぞれの顧客が注文した商品で一致するものだけをorderedByAllという変数に格納していくことで、全ての顧客によって注文された商品のセットを作成することができます。

答え

その他の問題はこちらからどうぞ。

この問題が解けた方は既に入門レベルを超えています。

次のステップとして、より実践的なKotlinスキルを身に着けたい方は以下の書籍がおすすめです。

プロフィール

プロフィール
コードラボJP

大学卒業後SEに就職、現在は退職しフリーランスとして活動中。
『初心者でも挫折せずに一人でプログラミングを学べる』をモットーに、コードラボJPを開設
お問い合わせ等はcodelabsjp@gmail.comまで

コードラボJPをフォローする
タイトルとURLをコピーしました