Mapのfindで条件に合う要素を一つ取得する方法

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

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

ScalaのMapクラスのfindメソッドの使い方について解説します。
後続の処理の内容によってはcollectFirstメソッドを使用することができるので、その書き換え方についても紹介します。

find メソッドの使い方

findメソッドは、特定の条件を満たす要素(キーや値)が存在する場合に、そのうちの一つを取得するメソッドです。
存在する場合にはSome(値)、存在しない場合にはNoneを返します。

次のマップを例にfindメソッドを使ってみましょう。
キーに数字、値にはフルーツの名前が小文字で入っています。

val map: Map[Int, String] = Map( 1 -> "apple", 2 -> "orange", 3 -> "grape", 4 -> "strawberry", )

このマップに含まれる要素のうち、 「キーが2で割り切れて、かつ、値に"g"という文字が含まれるもの」 のうちの最初の値を取得したい、という場合には、 findメソッドを使用すれば期待する結果を得る事ができます。

map.find { case (k, v) => k % 2 == 0 && v.contains("g") } .foreach { case (_, v) => println(v) }

結果として"orange"を得ることができました。

orange

collectFirst メソッドによる書き換え方

さて、先程の処理に加えて、「取得した値を大文字に変換する」という処理をしたい場合を考えてみましょう。

findメソッドを使用すると以下のように書くことができます。

map.find { case (k, v) => k % 2 == 0 && v.contains("g") } .map { case (_, v) => v.toUpperCase } .foreach(println)

結果はすべて大文字の"ORANGE"となります。

ORANGE

findメソッドとmapメソッドを使用する場合には、より簡潔に書き換えることができます。

ここで使用するのはcollectFirstメソッドです。
collectFirstメソッドは、条件に一致するものをひとつ抽出して、それに対して処理を行った結果を返すメソッドです。

実際にcollectFirstメソッドで書き換えてみましょう。

map.collectFirst { case (k, v) if k % 2 == 0 && v.contains("g") => v.toUpperCase }.foreach(println)

結果も同じくすべて大文字の"ORANGE"が返ってきます。

ORANGE

使用上の注意

毎回同じ結果が返ってくるとは限らない

Mapクラスの各要素は順序が保証されていないので、 条件に合致する要素が複数存在した場合は、 findメソッドの実行結果が変化する可能性があります。

これが気になる場合は、filterメソッドで条件に一致するものを抽出した上で、 さらに追加の条件を指定して好ましい要素を取得するのが良いでしょう。

次の例では、条件に合うもののうち最もキーの大きい要素を取得してみます。

map.filter { case (_, v) => v.contains("g") } .toList .sortBy(_._1) // キーでソートする .reverse // 降順にする .headOption // 先頭の要素を取得 .foreach(e => println(e._2))

結果は以下のようになります。
"g"という文字を含む要素の中で最もキーの番号が大きいのはgrapeなので、 期待通りに取得できていることがわかります。
この場合はfind メソッドを使わなくても取得できます。

grape

Scala 2.13以降ではより簡潔に書けるようになりました。
maxByOptionメソッドを使って以下のように書くことができます。

map.filter { case (_, v) => v.contains("g") } .maxByOption(_._1) .foreach(e => println(e._2))
grape

サイト内検索