1. 概要

コレクションから特定の要素を抽出したい場合があります。 Scalaには、この目的に使用できるfilterメソッドとwithFilterメソッドの両方があります。

このチュートリアルでは、それらの違いと、それらがパフォーマンスにどのように影響するかについて学習します。

2. 例

比較を具体的にするために、プログラマーのコレクションをフィルタリングします。 それらのそれぞれには、名前、既知の言語、およびレベルリストがあり、ジュニア、[ X128X] Mid 、または Senior

case class Programmer(name: String,
                      level: Level,
                      knownLanguages: List[String])
sealed trait Level
  object Level {
    case object Junior extends Level
    case object Mid extends Level
    case object Senior extends Level
  }

例で使用するプログラマーの候補リストを定義しましょう。

val programmers: List[Programmer] = List(
  Programmer(name = "Kelly",
             level = Level.Mid,
             knownLanguages = List("JavaScript")),
  Programmer(name = "John",
             level = Level.Senior,
             knownLanguages = List("Java", "Scala", "Kotlin")),
  Programmer(name = "Dave",
             level = Level.Junior,
             knownLanguages = List("C", "C++"))
)

複数の言語を知っていて、MidまたはSeniorレベルのプログラマーの名前を見つけようとします。

def isMidOrSenior(implicit counter: AtomicInteger): Programmer => Boolean =
  programmer => {
    counter.incrementAndGet()
    println("verify level " + programmer)
    List(Level.Mid, Level.Senior).contains(programmer.level)
  }

def knowsMoreThan1Language(implicit counter: AtomicInteger): Programmer => Boolean =
  programmer => {
    counter.incrementAndGet()
    println("verify number of known languages " + programmer)
    programmer.knownLanguages.size > 1
  }

val getName: Programmer => String =
  programmer => {
    println("get name " + programmer)
    programmer.name
  }

パフォーマンスの比較を理解しやすくするために、上記の方法でカウンターを追加で印刷およびインクリメントしています。

3. フィルターメソッド

3.1. サイン

filter メソッドは、述語関数を受け取り、指定された述語を満たす要素のみを保持する新しいコレクションを返します。

def filter(p: A => Boolean): Repr

Reprは、実際のコレクションのタイプであり、この場合はListです。

Aは、 コレクションの要素のタイプであり、この場合はプログラマーです。

3.2. 評価

希望するプログラマーを見つけましょう:

implicit val counter: AtomicInteger = new AtomicInteger(0)

val desiredProgrammers: List[Programmer] = programmers
  .filter(isMidOrSenior)
  .filter(knowsMoreThan1Language)

counter.get() shouldBe 5
desiredProgrammers.map(getName) shouldBe List("John")
counter.get() shouldBe 5

次の結果がコンソールに出力されます。

verify level Programmer(Kelly,Mid,List(JavaScript))
verify level Programmer(John,Senior,List(Java, Scala, Kotlin))
verify level Programmer(Dave,Junior,List(C, C++))
verify number of known languages Programmer(Kelly,Mid,List(JavaScript))
verify number of known languages Programmer(John,Senior,List(Java, Scala, Kotlin))
get name Programmer(John,Senior,List(Java, Scala, Kotlin))

isMidOrSenior述語がすべてのプログラマーに適用されたことがわかります。 ジュニアであるデイブを除外しました。 次に、これらの結果を調べて、knowsMoreThan1Language述語を適用しました。 ケリーは1つの言語しか知らないので、彼女を除外しました。

結局、私たちはジョンに残されました。ジョンはどちらもジュニアではなく、複数の言語を知っています。

目的の結果を達成するには、5つの述語操作が必要でした。

4. withFilterメソッド

4.1. サイン

withFilter メソッドには、filterメソッドとほぼ同じシグネチャがあります。 唯一の違いは、コレクションの代わりにFilterMonadicを返すことです。

def withFilter(p: A => Boolean): FilterMonadic[A, Repr]

ReprおよびAは、フィルターメソッドと同じ意味を持ちます。

4.2. 評価

次に、withFilterメソッドの動作を確認しましょう。

implicit val counter: AtomicInteger = new AtomicInteger(0)

val desiredProgrammers: FilterMonadic[Programmer, List[Programmer]] =
  programmers
    .withFilter(isMidOrSenior)
    .withFilter(knowsMoreThan1Language)

counter.get() shouldBe 0

desiredProgrammers.map(getName) shouldBe List("John")
counter.get() shouldBe 5

上記のコードから、withFilterメソッドを呼び出しても何も評価されないことがわかります。 評価は、mapメソッドの後でのみ行われます。

map を実行すると、次の結果がコンソールに出力されます。

verify level Programmer(Kelly,Mid,List(JavaScript))
verify number of known languages Programmer(Kelly,Mid,List(JavaScript))
verify level Programmer(John,Senior,List(Java, Scala, Kotlin))
verify number of known languages Programmer(John,Senior,List(Java, Scala, Kotlin))
get name Programmer(John,Senior,List(Java, Scala, Kotlin))
verify level Programmer(Dave,Junior,List(C, C++))

上記の出力から、述語( isMidOrSeniorknowsMoreThan1Language)とマッピング関数(getName)の両方が1回の反復で各プログラマーに適用されたことがわかります。

filter メソッドを使用して発生した3回の反復と比較して、2回のフィルタリング操作と1回のマッピング操作のコレクションには1回の反復しかないため、この動作によりパフォーマンスが大幅に向上します。

5. 比較

両方のメソッドのシグネチャが同じであるにもかかわらず、評価特性が完全に異なることがわかりました。 複数のフィルタリング操作を提供する必要がある場合は、パフォーマンスの観点から、withFilterを使用することをお勧めします。

したがって、なぜフィルターメソッドを使用するのでしょうか。 withFilter メソッドは、FilterMonadicでforeach、map、またはflatMapメソッドを呼び出す場合にのみ繰り返されることに注意してください。結果にマッピングせずに単一の述語のみを適用する場合は、フィルターメソッドを使用してコレクションを直接提供する必要があります。

FilterMonadic しかない場合は、前の操作の後で、恒等関数を使用して map メソッドを呼び出すことにより、コレクションを生成するように強制できます。

6. 結論

この短い記事では、 フィルター withFilter メソッド 。 ユースケースに応じて、それらの使用方法と選択方法を確認しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。