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 のタプルとなっているものを試していきます。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレス
- 発売日: 2016/09/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る