collect/collectFirstの使い方:Mapの要素を抽出し、それぞれを処理した結果を返すには

Scala 3 (Dotty 0.26.0-RC1) 2.13.3 2.12.12
最終更新:2020年5月19日

[AD] scalapediaでは記事作成ボランティアを募集しています

Mapから条件に該当する要素を抽出して、それぞれに対して処理を適用した結果のコレクションを作成する方法をご紹介します。

以下のような、名前と金額のペアのMapを例に考えてみましょう。
"apple""orange" を抽出して、さらに金額を円からドルに変換するには、どうすれば良いでしょうか?

val map = Map( "apple" -> 150, "banana" -> 210, "orange" -> 99, "strawberry" -> 110, "grape" -> 150, )

collectメソッドを使用する

collectメソッドを使用することで、Mapから条件に該当する要素を抽出して、それぞれの要素に対して処理を適用した結果のコレクションを取得できます。

以下は、collectメソッドを使用したサンプルです。

val result = map.collect { case (k, v) if (k == "apple" || k == "orange") => (k, v / 108.0) } result.foreach { case (k, v) => val value = "%1$.2f".format(v) println(s"$k=$$ $value") }

実行結果は以下のようになります。

apple=$ 1.39 orange=$ 0.92

collectメソッドに渡しているのは以下のような、Mapの要素のタプルを受け取り、タプルの第一要素が "apple" または "orange" の場合に、名前とドルがペアになったタプルを返す部分関数(PartialFunction)です。

PartialFunctionとは、ある引数の場合は値を返しますが、値を返さない場合もある関数です。
前述のサンプルでcollectメソッドに渡しているのは、以下のPartialFunctionです。
この関数は、 "apple" または "orange" の場合には名前とドルがペアになったタプルを返しますが、それ以外は値を返しません。

val func: PartialFunction[(String, Int), (String, Double)] = { case (k, v) if(k == "apple" || k == "orange") => (k -> v / 108.0) }

collectFirstメソッドを使用する

Map内の要素から条件に該当する最初の要素に対して処理を適用した結果を取得するには、collectFirstメソッドを使用します。

collectFirstメソッドの戻り値はOptionです。
値がある場合はSome、値がない場合はNoneを返します。

以下の例では、キーが "apple" の要素の金額を、円からドルに変換しています。

val result = map.collectFirst { case ("apple", v) => v / 108.0 } result match { case Some(v) => println(s"$$ ${"%1$.2f".format(v)}") case None => println("Not Found.") }

実行結果は、以下のとおりです。

$ 1.39

以下の例では、キーが"grapefruit" の要素の金額を、円からドルに変換しています。
ところが、"grapefruit" は存在しないため、collectFirstメソッドはNoneを返します。

val result = map.collectFirst { case ("grapefruit", v) => v / 108.0 } result match { case Some(v) => println(s"$$ ${"%1$.2f".format(v)}") case None => println("Not Found.") }

実行結果は、以下のとおりです。

Not Found.

mapメソッドとの違い

mapメソッドを使用すると、Mapの要素のそれぞれに対して処理を適用した結果のコレクションを作成できます。
mapメソッドはcollectメソッドと違い、Map内の要素を条件で選択することができません。

val result = map.map { case (k, v) => (k -> v / 108.0) } result.foreach { case (k, v) => println(s"${k}=$$ ${"%1$.2f".format(v)}") }

実行結果は、以下のとおりです。

orange=$ 0.92 apple=$ 1.39 strawberry=$ 1.02 banana=$ 1.94 grape=$ 1.39

filterメソッドとの違い

filterメソッドを使用して、Mapから条件に該当する要素を抽出できます。
filterメソッドはcollectメソッドと違い、Mapの要素のそれぞれに対して処理を適用することができません。

val result = map.filter { case (k, v) => k == "apple" || k == "orange" } result.foreach { case (k, v) => println(s"${k}=\\ ${"%d".format(v)}") }

実行結果は、以下のとおりです。

apple=150 orange=99

filterメソッドと、mapメソッドを使用する

最後にfilterメソッドとmapメソッドを使用して、collectメソッドと同様に、Mapから条件に該当する要素を抽出して、それぞれに対して処理を適用した結果のコレクションを作成する方法をご紹介します。

以下のように、filterメソッドで、Mapから条件に該当する要素を抽出した結果のMapに対して、mapメソッドでそれぞれの要素に対して処理を適用します。

val result = map.filter { case (k, v) => k == "apple" || k == "orange" }.map { case (k, v) => (k -> v / 108.0) } result.foreach { case (k, v) => println(s"${k}=$$ ${"%1$.2f".format(v)}") }

実行結果は、以下のとおりです。

apple=$ 1.39 orange=$ 0.92

ただし、この方法は、filterメソッドでMapを作成してから、mapメソッドでMapを作成しており、処理効率が悪くなります。
そのため、collectメソッド、collectFirstメソッドの使用をおすすめします。

サイト内検索