倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Scala の Seq に定義されているメソッドを試す (1) ~ファクトリ・メソッド~

Scala の Seq トレイト、コンパニオン・オブジェクトにはたくさんのメソッドが定義されていますが、あまりにもたくさんのメソッドがありすぎて、コードをある程度書いた後で「このメソッド使ったら簡単に実装できるやん!」みたいなことが幾度かあったので、ここいらへんで片っ端からメソッドを試しておこうかと思います(いくつか飛ばすものもありますが)。

このシリーズでは Scala 2.12 を使います。 どうも 2.13 になるとコレクション API の構造がガラッと変わるっぽいのですが、使えるメソッドは変わらなさそうなので、気にしないことにします。

シリーズ目次

  1. ファクトリ・メソッド
  2. 要素の有無・要素数
  3. 要素の取得
  4. インデックス
  5. 同じ要素数の Seq オブジェクトを返すメソッド
  6. 異なる要素数の Seq オブジェクトを返すメソッド
  7. 畳み込み
  8. 多重コレクション
  9. タプル関連

Seq コンパニオン・オブジェクトに定義されているメソッド

今回扱うメソッドは、Seq コンパニオン・オブジェクトに定義されているファクトリ・メソッド群。 fill と tabulate がたくさんありますが、1つ目の引数リストが1つ2つのものが分かれば後は同じです。

def empty[A]: Seq[A]
def apply[A](elems: A*): Seq[A]

def fill[A](n: Int)(elem: ⇒ A): Seq[A]
def fill[A](n1: Int, n2: Int)(elem: ⇒ A): Seq[Seq[A]]
def fill[A](n1: Int, n2: Int, n3: Int)(elem: ⇒ A): Seq[Seq[Seq[A]]]
def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: ⇒ A): Seq[Seq[Seq[Seq[A]]]]
def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: ⇒ A): Seq[Seq[Seq[Seq[Seq[A]]]]]

def tabulate[A](n: Int)(f: (Int) ⇒ A): Seq[A]
def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) ⇒ A): Seq[Seq[A]]
def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) ⇒ A): Seq[Seq[Seq[A]]]
def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) ⇒ A): Seq[Seq[Seq[Seq[A]]]]
def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) ⇒ A): Seq[Seq[Seq[Seq[Seq[A]]]]]

def concat[A](xss: Traversable[A]*): Seq[A]

def range[T](start: T, end: T)(implicit arg0: Integral[T]): Seq[T]
def range[T](start: T, end: T, step: T)(implicit arg0: Integral[T]): Seq[T]

def iterate[A](start: A, len: Int)(f: (A) ⇒ A): Seq[A]

def newBuilder[A]: Builder[A, Seq[A]]

// 今回は扱わないメソッド
def unapplySeq[A](x: Seq[A]): Some[Seq[A]]
def ReusableCBF: GenericCanBuildFrom[Nothing]
implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Seq[A]]

サンプルコード

empty メソッド
empty メソッドは空の Seq を返します。

  // empty メソッド
  val seq0 = Seq.empty
  assert( seq0 == Seq() )
  assert( seq0 == Nil )

要素がないと要素の型が推論できないときがあるので、要素の型を指定したい場合は型パラメータを指定します。

val sSeq = Seq.empty[String]
// val sSeq: Seq[String] = Seq.empty でも可。

apply メソッド
コンパニオン・オブジェクトに定義されている apply メソッドは new キーワードを使わないコンストラクタのように使えるファクトリ・メソッドです*1。 Seq オブジェクトを生成するのに一番よく使うメソッドでしょう。

  // apply メソッド
  val seq1 = Seq(0, 1, 2)
  assert( seq1.mkString("[", ", ", "]") == "[0, 1, 2]" )

ちなみに、返されるオブジェクトは scala.collection.immutable.List 型のインスタンスです。

fill メソッド
fill[A](Int)(=> A) というシグニチャの fill メソッドは、指定された長さで全て同じ要素の Seq を返します。 1つ目の引数リストが Seq の長さ、2つ目の引数が Seq の要素となるオブジェクトです。 正確には、2つ目の引数リストの関数は各要素ごとに評価されるので、例えば Random.nextInt() などを指定すれば異なる値になり得ます。

  // fill メソッド その1
  val seq2 = Seq.fill(3)(1)
  assert( seq2 == Seq(1, 1, 1) )

fill メソッドには、1つ目の引数リストに複数の Int 値をとるものもありますが、これは多重 Seq (Seq の Seq など)オブジェクトを返します。 例えば、2つの Int 値をとる fill メソッドでは

  // fill メソッド その2
  val seq3 = Seq.fill(3, 4)(1)
  assert( seq3 == Seq(
                    Seq(1, 1, 1, 1),
                    Seq(1, 1, 1, 1),
                    Seq(1, 1, 1, 1)) )

となります。 1つ目の引数リストの Int 値は、最初のものが一番外側の Seq の長さになります。 他の fill メソッドも挙動は同じで、5重 Seq を作れる fill メソッドまでが定義されています。 まぁ、使っても2重くらいまでな気がするけど・・・

tabulate メソッド
tabulate メソッドは fill メソッドと似ていますが、2つ目の引数リストにインデックスの Int 値(の組)をとる関数を渡します。 例えば、1つ目の引数リストに Int 値を1つだけとる tabulate メソッドは

  // tabulate メソッド その1
  val seq4 = Seq.tabulate(4)(i => "a"*i)
  assert( seq4 == Seq("", "a", "aa", "aaa") )

のように使います。 要は、要素を計算する際にインデックスを使えるということですね。 tabulate メソッドも fill メソッドと同様に多重 Seq を生成するメソッドが定義されています:

  // tabulate メソッド その2
  val seq5 = Seq.tabulate(3, 4)((i, j) => "a"*i + "b"*j)
  assert( seq5 == Seq(
                    Seq(""  , "b"  , "bb"  , "bbb"),
                    Seq("a" , "ab" , "abb" , "abbb"),
                    Seq("aa", "aab", "aabb", "aabbb")) )

これも5重 Seq まであります。

concat メソッド
concat メソッドは2つ以上の Seq オブジェクトを連結させて1つの Seq オブジェクトにします。

  // 準備
  val seq1 = Seq(0, 1, 2)
  val seq2 = Seq.fill(3)(1)

  // concat メソッド
  val seq6 = Seq.concat(seq1, seq2)
  assert( seq6 == Seq(0, 1, 2, 1, 1, 1) )
  assert( seq6 == (seq1 ++: seq2) )  // 2つを繋げるくらいなら「++:」でいいんじゃない?

2つの Seq を繋げるくらいなら、concat メソッドを使うよりも Seq トレイトに定義されている「++:」メソッドを使った方が読みやすいかな。

range メソッド
range メソッドは引数として整数値*2をとって、第1引数から始まり、第2引数まで(ただし第2引数自体は含まない)、第3引数で指定された幅(指定されていなければ1)で増える整数列を生成します。 第1引数が初項で、第3引数(なければ1)が交差の等差数列ですね。 まぁ、Int 値などに使える until メソッドと同じです。

  // range メソッド その1
  val seq7 = Seq.range(10, 15)
  assert( seq7 == Seq(10, 11, 12, 13, 14) )
  assert( seq7 == (10 until 14) )  // until と同じようなもの

  // range メソッド その2
  val seq8 = Seq.range(10, 16, 2)
  assert( seq8 == Seq(10, 12, 14) )
  assert( seq8 == (10 until 14 by 2) )  // until ... by と同じようなもの

iterate メソッド
iterate メソッドは初項と漸化式を与えて数列を定義するようなメソッドです。 1つ目の引数リストには初項と項数を、2つ目の引数リストには漸化式に相当する関数を渡します。 例えば、初項が1、項数が5で、前の項を2倍して次の項を計算する漸化式によって生成される数列の Seq は以下のようになります:

  // iterate メソッド
  val seq9 = Seq.iterate(1, 5)(i => i*2)
  assert( seq9 == Seq(1, 2, 4, 8, 16) )

初項や漸化式という用語を用いましたが、要素の型は任意の型で大丈夫です。

newBuilder メソッド
newBuilder メソッドは、Java の StringBuilder のような構築用のミュータブルな Builder オブジェクトを生成します。 この Builder オブジェクトに対して「+=」などで要素を付加し、最後に result メソッドでイミュータブルな Seq オブジェクトを取得します。

  // newBuilder メソッド
  val sb = Seq.newBuilder[Int]
  for(i <- 1 to 5) sb += i  // 要素を付加
  val seq10 = sb.result  // イミュータブルな Seq を取得
  assert( seq10 == Seq(1, 2, 3, 4, 5) )

Builder クラスには他にもいくつかメソッドが定義されているので、使う際にはチェックしておいた方がいいかも。

今回は準備も兼ねて Seq オブジェクトを取得するファクトリ・メソッドを見てきました。 次回からは Seq トレイトに定義されているメソッドを試していく予定。

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

*1:個人的に「apply ファクトリ・メソッド」と呼んでますが、広まらないかなぁ。

*2:scala.math.Integral 型、まぁ、Int や Long や BigInt など