Scalaのコレクションメソッド .containsと.existsについて

ごあいさつ

こんにちは。meviyのWebシステムを開発している大崎です。
今回は、Scalaのコレクション内に要素の存在チェックに使われるメソッドについて触れたいと思います。

Scalaでコレクションを操作するとき、「該当する要素が存在するか」「特定の条件を満たす要素が存在するか」を確認する場面が出てきます。
その際に使われるのが .contains および .exists メソッドです。
本記事では、これらのメソッドの基本的な使い方、実装例、内部実装、そしてJavaでの同等機能との比較を交えつつ、このメソッドを紹介していきます。

各メソッドの紹介

.contains メソッド

コレクション内に、指定した要素が存在するかを判定するメソッドです。

def contains(elem: Any): Boolean

返り値としては、指定した要素が存在すればtrue、存在しなければfalseとなります。

例を見てみましょう。

val numbers = List(1, 2, 3, 4, 5)
val hasThree = numbers.contains(3)
println(hasThree) // true

ここでは、リスト内に3が存在するため、trueが返されます。

.exists メソッド

コレクション内に、指定した条件を満たす要素が1つでも存在するかを判定するメソッドです。

def exists(p: A => Boolean): Boolean

返り値としては、条件を満たす要素があればtrue、なければfalseとなります。

簡単な例を見てみましょう。

val numbers = List(1, 2, 3, 4, 5)
val hasEven = numbers.exists(_ % 2 == 0)
println(hasEven) // true

ここでは、リスト内に偶数が存在するため、trueが返されます。

.contains との違いとしては、 .contains は特定の要素が存在するかを確認するのに対し、 .exists は条件を満たす要素が存在するかを確認する点です。
引数の型定義を見てみると、 .contains は要素そのものを引数に取るのに対して、 .exists は条件を満たす要素を判定するための関数を引数に取ります。

実装例

.contains メソッドの実装例

まずは、 .contains を使った実装例として、リスト内に特定の要素が存在するかを確認する例を示します。

val tags = List("Scala", "Java", "Kotlin", "Server-side", "JVM")
val hasScala = tags.contains("Scala")
println(hasScala) // true

このように、コレクション内に特定の要素が存在するかを確認するのに使えます。

.exists メソッドの実装例

次に .exists を使った実装をいくつか示します。

数値リストの条件チェック

val numbers = List(10, 20, 30, 40, 50)
val hasNumberGreaterThan25 = numbers.exists(_ > 25)
println(hasNumberGreaterThan25) // true

.exists に渡されている _ > 25 というのは、値が25より大きいかを判定する関数です。
_ は無名関数の引数を表し、ここではリスト内の各要素である、1020 といった値がここに入ります。
.exists はこの関数を用いてコレクションの各要素をそれぞれ評価し、条件を満たす要素が1つでも存在すればtrueを返します。

.contains と違い、指定された要素と合致する要素が存在するかではなく、与えられた条件を満たす要素が存在しているかを判定するようになっていることが分かります。

文字列内に特定の単語が存在するかのチェック

val bannedWords = List("spam", "scam", "fake")
val inputMessage = "This is a scam message!"
val containsBannedWord = bannedWords.exists(inputMessage.contains)
println(containsBannedWord) // true

特定の単語が文字列内に存在するかを確認しています。
inputMessage.contains は、文字列内に特定の文字列が存在するかを判定する関数です。上の例のように引数内で関数を生成するのではなく、既存の関数をそのまま引数に渡すこともできます。
ここでは、禁止ワード3つが文字列内に含まれているかを確認し、どれかが存在していればtrueを返します。

真偽値の確認

case class Task(name: String, completed: Boolean)
val tasks = List(Task("Task1", false), Task("Task2", true), Task("Task3", false))
val hasCompletedTask = tasks.exists(_.completed)
println(hasCompletedTask) // true

タスクリスト内に「完了済み」のタスクが存在するかを確認しています。
_.completed は、tasks の各要素であるTaskクラスのオブジェクトを、そのオブジェクトの completed フィールドの値に変換する関数です。
.exists としては、返り値が真偽値である関数を与えられれば問題ないので、このように単にフィールドの値を取り出す関数を渡すこともできます。

内部実装

Scala.exists は短絡評価を採用しており、条件を満たす要素を見つけた時点で処理を終了します。
全ての要素を評価することはないので効率的です。

val largeList = (1 to 1000000).toList
val hasLargeNumber = largeList.exists(_ > 999)
println(hasLargeNumber) // true

この場合、最初に条件を満たす1000が見つかった時点で探索を終了します。

内部実装を見てみましょう。以下のようになっています。

  def exists(p: A => Boolean): Boolean = {
    var res = false
    val it = iterator
    while (!res && it.hasNext) res = p(it.next())
    res
  }

ポイントとしては、イテレータを用いて要素を順次評価し、条件を満たす最初の要素が見つかればwhileループを終了してtrueを返します。全要素を評価しても条件を満たす要素が見つからなければfalseを返します。

FYI:

また、.contains の実装には内部で .exists が使われているため、同様に短絡評価が行われます。(以下 Iterator トレイトの例 )

  def contains(elem: Any): Boolean = exists(_ == elem)

Javaでの同等の実装

比較のためにJavaの例を見てみましょう。

まず、.contains は以下のようになります。

import java.util.List;
public class ContainsExample {
    public static void main(String[] args) {
        var tags = List.of("Scala", "Java", "Kotlin", "Server-side", "JVM");
        var hasScala = tags.contains("Scala");
        System.out.println(hasScala); // true
    }
}

比較は Object.equals メソッドを使って等価比較で行われるため、Scala.contains と同様に指定した要素が存在するかを確認することができます。
こちらは書き味はScalaと同様です。
FYI:

次に、.exists についてです。
JavaではStream APIanyMatch メソッドを使うことで同じような処理が可能です。
こちらもScala.exists と同様に短絡評価が行われるとされています。
FYI:

import java.util.List;
public class ExistsExample {
    public static void main(String[] args) {
        var numbers = List.of(1, 2, 3, 4, 5);
        var hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
        System.out.println(hasEven); // true
    }
}

比較してみると、Scala.exists はコレクションメソッドとして組み込まれており、シンプルで直感的に使える点が特徴です。

まとめ

Scala.exists メソッドは、指定した条件を満たす要素の有無を判定する処理をシンプルに記述、効率的に実行するのに有用なメソッドとなっております。短絡評価を行うので、条件を満たす要素が存在することが判明した時点で結果を返します。
条件に合致する要素を含んでいるかどうかだけが知りたい場合には、.contains メソッドを使うことで簡潔に書くことができます。こちらも内部で .exists をラップしているため、短絡評価が行われます。

終わりに

本記事ではScalaのコレクションの要素の存在チェックについて紹介しました。 他にも、Scalaには様々な機能があり、それらを駆使することでより簡潔にコードを書くことができます。

本ブログでは今後もScalaをはじめとする、DTダイナミクスで用いられている技術やプロダクトなどの魅力を発信していきますので、興味を持っていただけると幸いです。

最後にほかにも、Scalaに関する記事がございますので、ぜひご覧ください。