倭マン's BLOG

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

Scala の Seq に定義されているメソッドを試す (8) ~多重コレクション~

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 今回は Seq の Seq や Seq の Iterator に関連するメソッドを試していきます。 Seq のタプルやタプルの Seq を返すメソッドも試そうと思ったのですが、長くなりそうなのでこれらは次回に。

今回扱うメソッド

// 多重コレクション関連
def flatten[B]: Seq[B]
def flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): Seq[B]

def transpose[B](implicit asTraversable: (A) ⇒ GenTraversableOnce[B]): Seq[Seq[B]]

// Seq の Iterator を返すメソッド
def grouped(size: Int): Iterator[Seq[A]]

def sliding(size: Int): Iterator[Seq[A]]
def sliding(size: Int, step: Int): Iterator[Seq[A]]

def inits: Iterator[Seq[A]]
def tails: Iterator[Seq[A]]

サンプルコード(多重コレクション関連)

まずは多重コレクションに関するメソッド。 flatten メソッドと transpose メソッドは二重の Seq(つまり Seq の Seq)に対して呼び出さないとコンパイル・エラーになります。 flatMap メソッドは直接多重コレクションには関係していませんが、flatten と似ているのでここで扱っています。

flatten メソッド
flatten メソッドは二重の Seq に対して呼び出せ、内側の Seq をバラして一重の Seq にします。

  val metaSeq0 = Seq(
    Seq(1, 2, 3, 4),
    Seq(1, 3, 5, 7),
    Seq(1, 4, 7, 10))

  // flatten メソッド
  assert( metaSeq0.flatten == Seq(1, 2, 3, 4, 1, 3, 5, 7, 1, 4, 7, 10) )

二重 Seq に対して二項演算「+:」で reduce したようなものですかね。 一重の Seq に対して呼び出そうとするとコンパイル・エラーになります。 また、返り値の型から明らかですが、三重の Seq に対して呼び出すと二重の Seq になります(完全に平坦化されるわけではない)。

  val metaSeq1 =
    Seq(
      Seq(
        Seq(0, 1),
        Seq(2, 3)),
      Seq(
        Seq(4, 5),
        Seq(6, 7)))

  assert( metaSeq1.flatten ==
    Seq(
      Seq(0, 1),
      Seq(2, 3),
      Seq(4, 5),
      Seq(6, 7)) )

flatMap メソッド
flatMap メソッドは、map メソッドと flatten メソッドを併せて行うようなメソッドです。 レシーバとなる Seq オブジェクトは一重の Seq で構いません(ここで扱ってますが、引数や返り値に直接多重コレクションは関係してません)。 引数として、各要素を任意の要素型の Seq オブジェクトに変換する関数をとり、返り値はそれらの関数を適用した結果の Seq を(flatten を呼び出したように)平坦化します。

  val intSeq = Seq(1, 2, 3)

  // flatMap メソッド
  val result = intSeq.flatMap(i => Seq.fill(4)(i))  // 同じ値を4つ要素とする Seq を返す
  assert( result == Seq(1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3) )

  // flatMap は map & flatten と同じ
  val f: Int => Seq[Int] = i => Seq.fill(4)(i)
  assert( intSeq.map(f).flatten == intSeq.flatMap(f) )

flatMap メソッドは、filter メソッド、map メソッドとともにモナド関連のメソッドとしても重要です。

transpose メソッド
transpose メソッドは、二重の Seq オブジェクトに対して呼び出せ、二重の Seq を行列と見做したときの転置行列(に対応する二重 Seq)を返します。

  val metaSeq = Seq(
    Seq(1, 2, 3, 4),
    Seq(1, 3, 5, 7),
    Seq(1, 4, 7, 10))

  // transpose メソッド
  assert( metaSeq.transpose ==
    Seq(
      Seq(1, 1, 1),
      Seq(2, 3, 4),
      Seq(3, 5, 7),
      Seq(4, 7, 10)) )

数学にしか使わないんじゃないかという気もしますが、複数の Seq オブジェクトに対して、先頭だけを集めた Seq、2番目だけを集めた Seq、などを取り出したいときにも使えます。

サンプルコード(Seq の Iterator を返すメソッド)

次は Seq オブジェクトを要素とする Iterator オブジェクトを返すメソッド。 assert 文で妥当性検証をしている箇所で、各メソッドの返り値に対して toSeq メソッドを呼び出していますが、これは == で要素が等しいことを検証するためです(Iterator オブジェクトに対しては == でこの検証ができないため)。

grouped メソッド
grouped メソッドは、指定した Int 値の個数ずつ元の Seq の要素を分割した Seq の Iterator を返します。

  val strSeq = Seq("a", "b", "c", "d", "e", "f", "g", "h")

  // grouped メソッド
  assert( strSeq.grouped(3).toSeq ==
    Seq(
      Seq("a", "b", "c"),
      Seq("d", "e", "f"),
      Seq("g", "h")) )  // 要素数が2個

元の Seq の長さによっては、最後の Seq の個数が指定した個数より少なくなることがあります。

sliding メソッド
sliding メソッドには引数を1つとるものと2つとるものがあります。

Int 値1つを引数にとる sliding メソッドは、grouped メソッドと同じように指定した個数ずつ元の Seq オブジェクトの要素を返しますが、grouped メソッドと異なり先頭を1ずつずらして要素をまとめていきます。 したがって、2以上の値を引数に指定すると、要素がいくつかの Seq に重複して含まれます。

  val strSeq = Seq("a", "b", "c", "d", "e")

  // sliding メソッド その1
  assert( strSeq.sliding(3).toSeq ==
    Seq(
      Seq("a", "b", "c"),
      Seq("b", "c", "d"),
      Seq("c", "d", "e")) )

引数1つの sliding メソッドでは、grouped メソッドと異なり全ての Seq の要素数が指定した Int 値となります(ただし、元の Seq オブジェクトの長さより大きい値を引数として指定すると、指定した長さと異なる Seq オブジェクトが返されます)。

Int 値2つを引数にとる sliding メソッドは、1つ目の Int 値はまとめる要素の個数、2つ目の Int 値は先頭をとるときにずらすステップ数です。

  val strSeq = Seq("a", "b", "c", "d", "e", "f", "g", "h")

  // sliding メソッド その2
  assert( strSeq.sliding(3, 2).toSeq ==
    Seq(
      Seq("a", "b", "c"),
      Seq("c", "d", "e"),
      Seq("e", "f", "g"),
      Seq("g", "h")) )

引数を2つとる sliding メソッドでは、grouped メソッドと同じように、最後の Seq オブジェクトの要素数が小さくなる場合があります。 また、第1引数と第2引数が同じ場合は grouped メソッドと同じ内容の Iterator を返します。

  // sliding メソッドの2つの引数が同じなら grouped メソッドと同じ
  assert( strSeq.sliding(3, 3).toSeq == strSeq.grouped(3).toSeq )

tails メソッド
tails メソッドは、元の Seq オブジェクトから始め、その tail、そのまた tail、そのまた tail と、空の Seq オブジェクトになるまで tail を返す Iterator を返します。

  val strSeq = Seq("a", "b", "c", "d", "e")

  // tails メソッド
  assert( strSeq.tails.toSeq ==
    Seq(
      Seq("a", "b", "c", "d", "e"),
      Seq("b", "c", "d", "e"),
      Seq("c", "d", "e"),
      Seq("d", "e"),
      Seq("e"),
      Seq()) )

返される Iterator の要素数は、元の Seq オブジェクトの長さ +1 となります。

ちなみに、tails メソッドの返り値は Seq.iterate や scanRight メソッドを使って以下のように書くことができます:

  val strSeq = Seq("a", "b", "c", "d", "e")

  // Seq.iterate を使って
  assert( strSeq.tails.toSeq == Seq.iterate(strSeq, strSeq.length+1)(_.tail) )

  // scanRight を使って
  assert( strSeq.tails.toSeq == strSeq.scanRight(Seq[String]())(_+:_) )

だからどうだということでもないんですが。

inits メソッド
inits メソッドは、tails メソッドと同じように(逆に?)元の Seq オブジェクトから始めて、init をとっていきます。

  val strSeq = Seq("a", "b", "c", "d", "e")

  // inits メソッド
  assert( strSeq.inits.toSeq ==
    Seq(
      Seq("a", "b", "c", "d", "e"),
      Seq("a", "b", "c", "d"),
      Seq("a", "b", "c"),
      Seq("a", "b"),
      Seq("a"),
      Seq()) )

inits メソッドは Seq.iterate メソッドを使って以下のように書けます。

  val strSeq = Seq("a", "b", "c", "d", "e")

  // Seq.iterate を使って
  assert( strSeq1.inits.toSeq == Seq.iterate(strSeq1, strSeq1.length+1)(_.init) )

inits メソッドの方は scanRight や scanLeft などを使って簡単には書けなさそうですかね。 reverse とか使えば書けるかと思いますが。

今回は多重コレクションに関連するメソッドを試しましたが、次回は似たようなメソッドで、返り値がタプルの Seq、もしくは Seq のタプルとなっているものを試していきます。

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

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