[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、Scalaの scala.collection.immutable.List
クラスにおいて、要素を特定の基準で分類して処理する方法をご紹介します。
要素を分類して処理するには groupBy
、groupMap
、groupMapReduce
メソッドを使うことができます。
以下のような、生徒を表す Student
クラスを例として用いて説明します。
case class Student(name: String, grade: Int) val students = Seq( Student("John", 1), Student("Mary", 3), Student("Chris", 2), Student("Bob", 3), Student("Nick", 1), Student("Daisy", 1), Student("Rod", 2), Student("Watson", 3), Student("Andy", 2) )
Student
クラスは、生徒の名前と学年を保持しています。
生徒の一覧として変数 students
を用意しました。
それでは、このサンプルを分類して処理してみましょう。
groupBy
メソッドを使用して、リストの要素を特定の基準で分類する
リストの要素を特定の基準で分類するには、groupBy
メソッドを使用します。
def groupBy[K](f: (A) => K): immutable.Map[K, C]
引数として f: (A) => K
を受け取ります。
この引数ではリストの要素の何を元にして分類するかを示してあげます。
戻り値として immutable.Map[K, C]
を返します。
これは分類した基準値をキー(K)とし、当てはまる要素のコレクションを値(C)とするマップです。
サンプルコードはこちらです。
for { (grade, list) <- students.groupBy(_.grade) str = list.map(_.name).mkString(", ") } println(s"$grade -> $str")
このコードでは、生徒を学年で分類し、名前を文字列としてまとめて出力しています。
出力は以下のようになります。
1 -> John, Nick, Daisy 2 -> Chris, Rod, Andy 3 -> Mary, Bob, Watson
生徒が学年ごとにコンマ区切りで出力されています。 各行の順番は入れ替わることがあることに注意してください。
2.13 で追加された便利なメソッドを使用する
Scala 2.13では、groupMap
と groupMapReduce
という、さらに便利なメソッドが追加されました。
これらのメソッドを使用すると、先ほどの例をよりシンプルに記述し、しかも効率よく処理することができます。
早速それぞれのメソッドについて見てみましょう。
groupMap
メソッドを使用して、リストの要素を特定の基準で分類してそれぞれ処理する
リストの要素を特定の基準で分類してそれぞれ処理するには、groupMap
メソッドを使用します。
def groupMap[K, B](key: (A) => K)(f: (A) => B): immutable.Map[K, CC[B]]
引数として key: (A) => K
と f: (A) => B
の2つを受け取ります。
第一引数はgroupBy
と同様に、リストの要素の何を元にして分類するかを示してあげます。
第二引数では map
メソッドと同様に、分類後のグループのそれぞれの要素をどう処理するかを示してあげます。
戻り値として immutable.Map[K, CC[B]]
を返します。
これは分類した基準値をキー(K)とし、当てはまる要素をそれぞれ処理した結果のコレクションを値(CC[B])とするマップです。
サンプルコードはこちらです。
for { (grade, group) <- students.groupMap(_.grade)(_.name) str = group.mkString(", ") } println(s"$grade -> $str")
このコードでは先ほどの例と同様に、生徒を学年によって分類し、名前を文字列としてまとめて出力しています。
groupMap
メソッドを使用することにより、map
メソッドを使用する必要がなくなったことがわかりますね。
出力は以下のようになります。
1 -> John, Nick, Daisy 2 -> Chris, Rod, Andy 3 -> Mary, Bob, Watson
生徒を学年ごとにコンマ区切りで出力することができています。
また、こちらも出力結果の各行は順不同であることに注意してください。
groupMapReduce
メソッドを使用して、リストの要素を特定の基準で分類してそれぞれ処理し、最後にグループごとにまとめる
リストの要素を特定の基準で分類してそれぞれ処理し、最後にグループごとにまとめるには、groupMapReduce
メソッドを使用します。
def groupMapReduce[K, B](key: (A) => K)(f: (A) => B) (reduce: (B, B) => B): immutable.Map[K, B]
引数として key: (A) => K
と f: (A) => B
と reduce: (B, B) => B
の3つを受け取ります。
第一引数は groupBy
と同様に、リストの要素の何を元にして分類するかを示してあげます。
第二引数では groupMap
メソッドと同様に、分類後のグループのそれぞれの要素をどう処理するかを示してあげます。
第三引数では reduce
メソッドと同様に、分類後のグループのそれぞれの要素をどうひとまとめにするかを示してあげます。
戻り値として immutable.Map[K, B]
を返します。
これは分類した基準値をキー(K)とし、当てはまる要素をそれぞれ処理しまとめた結果を値(B)とするマップです。
サンプルコードはこちらです。
for { tuple <- students.groupMapReduce(_.grade)(_.name)(_ + ", " + _) } println(tuple)
このコードでも同様に、生徒を学年によって分類し、名前を文字列としてまとめて出力しています。
当初のコードでは mkString
のために一行消費していましたが、その代わりにこのサンプルでは名前どうしを +
で結合しています。
groupしてmapしてreduceする、という一連の処理が3つの括弧としてそのままの順番でポンポンポンと並んでいますね。
このことによって、これらの処理が一体何をしているかを捉えやすいというのがこのメソッドの特長です。
出力は以下のようになります。
1 -> John, Nick, Daisy 2 -> Chris, Rod, Andy 3 -> Mary, Bob, Watson
こちらも生徒を学年ごとにコンマ区切りで出力することができていますね。
groupMapReduce
メソッドのサンプルコードでは println
に tuple
を渡していたことにお気付きの方もいると思います。
それでも当初の例と同様に出力できている理由について説明します。
groupMapReduce
メソッドの戻り値はimmutable.Map[K, B]
です。
したがってこれをfor
式で処理すると、それぞれの要素は Tuple2
となります。
Tuple2
はtoString
で文字列に変換すると "key -> value"
のように出力されるため、その結果、当初の例と同様に出力できたというわけです。
さて、ここでも、各行の順番が入れ替わる可能性があることには注意してください。
これらのメソッドはscala.collection.IterableOps
に定義されている
groupBy
、groupMap
、groupMapReduce
メソッドは、それぞれ scala.collection.IterableOps
に定義されています。
IterableOps#groupBy
IterableOps#groupMap
IterableOps#groupMapReduce
したがってこれらのメソッドは、
Listだけでなく scala.collection.mutable.ListBuffer
や、scala.collection.Map
、scala.collection.Set
など、
IterableOps
を継承しているクラスにおいても共通に利用することができます。
これは全くの余談ですが、 groupBy
メソッドは、Scala 2.12 以前には scala.collection.GenTraversableLike
クラスに定義されていました。
それまでは似たような名前のトレイトが何枚も重なっていたのですが、やはり「わかりにくい」という話になり、2.13において整理されました。
その結果として、groupBy
メソッドは IterableOps
に定義されることになったというわけです。
GenTraversableLike
まとめ
List
などのコレクションクラスにおいて、要素を特定の基準で分類するにはgroupBy
メソッドを使用します。
また、要素を特定の基準で分類してそれぞれ処理するにはgroupMap
メソッドを使用します。
また、要素を特定の基準で分類してそれぞれ処理し、最後にグループごとにまとめるにはgroupMapReduce
メソッドを使用します。