倭マン's BLOG

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

Scala の Seq に定義されているメソッドを試す (5) ~同じ要素数の Seq を返すメソッド~

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 今回は既にある Seq オブジェクトから新たに Seq オブジェクトを作るメソッド。 特に生成された Seq オブジェクトの要素数が元の Seq オブジェクトの要素数と一致するものを試していきます。 加えて、関数や部分関数 (PartialFunction) を返すメソッドも試します。

今回扱うメソッド

まずは、要素数の等しい新たな Seq オブジェクトを生成するメソッド。

def map[B](f: (A) ⇒ B): Seq[B]
def updated(index: Int, elem: A): Seq[A]
def reverse: Seq[A]
def reverseMap[B](f: (A) ⇒ B): Seq[B]

def sorted[B >: A](implicit ord: math.Ordering[B]): Seq[A]
def sortWith(lt: (A, A) ⇒ Boolean): Seq[A]
def sortBy[B](f: (A) ⇒ B)(implicit ord: math.Ordering[B]): Seq[A]

加えて、関数や部分関数を返すメソッド。 これらは PartialFunction トレイトから継承しているメソッドです。

def lift: (Int) ⇒ Option[A]
def orElse[A1 <: Int, B1 >: A](that: PartialFunction[A1, B1]): PartialFunction[A1, B1]
def compose[A](g: (A) ⇒ Int): (A) ⇒ A
def andThen[C](k: (A) ⇒ C): PartialFunction[Int, C]

サンプルコード(要素数が同じ Seq オブジェクトを返すメソッド)

まずは要素数が同じ Seq オブジェクトを返すメソッド。 scala.collection.Seq の apply ファクトリ・メソッドで返される実装ではイミュータブルな Seq が返されるので、ここで扱うメソッドは元の Seq を変更しません。

map メソッド
map メソッドは、元の Seq の要素型を引数にとる関数を引数にとり、各要素にその関数を適用した結果を要素とする新たな Seq を生成します。

  val seq = Seq("zero", "one", "two", "three", "four")

  // map メソッド
  assert( seq.map(_.length) == Seq(4, 3, 3, 5, 4) )  // 各文字列をその長さの Int 値に変換

map メソッドは filter, flatMap メソッドと共にモナド関連の話でも出てくる重要なメソッドですね。

updated メソッド
updated メソッドは、インデックスをしてしてその位置の要素を変更するメソッドです。 正確には指定した位置の要素を変更した Seq を返します。 Java のコレクションいう set メソッドですね。

  val seq = Seq("zero", "one", "two", "three", "four")

  // updated メソッド
  assert( seq.updated(2, "二") == Seq("zero", "one", "二", "three", "four") )

reverse メソッド
reverse メソッドは、要素の順序を反転した Seq オブジェクトを返します。

  val seq = Seq("zero", "one", "two", "three", "four")

  // reverse メソッド
  assert( seq.reverse == Seq("four", "three", "two", "one", "zero") )

reverseMap メソッド
reverseMap メソッドは、map メソッドと reverse メソッドを合わせたメソッドです。 実装クラス(LinearSeq とかかな)によってはパフォーマンスがいいんだと思います。

  val seq = Seq("zero", "one", "two", "three", "four")

  // reverseMap メソッド
  assert( seq.reverseMap(_.length) == Seq(4, 5, 3, 3, 4) )

sorted メソッド
sorted メソッドは、要素を昇順に並べます。 順序を指定しなければ自然な順序*1が使われます。 String オブジェクトに対しては辞書順序が使われます。

  val seq = Seq("zero", "one", "two", "three", "four")

  // sorted メソッド
  assert( seq.sorted == Seq("four", "one", "three", "two", "zero") )

独自の順序を使いたい場合は Ordering オブジェクトを作って引数として渡します。

  val lenOrd: Ordering[String] = (x, y) => x.length - y.length  // 文字列長が短い順
  assert( seq.sorted(lenOrd) == Seq("one", "two", "zero", "four", "three") )

sortWith メソッド
sortWith メソッドは、sorted メソッドに Ordering オブジェクトを渡す場合のように独自の順序で要素を並び替えます。 ただし、順序の指定としては Ordering オブジェクトではなく (A, A) => Boolean 型の関数(A は要素の型)を渡します。

  val seq = Seq("zero", "one", "two", "three", "four")

  // sortWith メソッド
  assert( seq.sortWith((x, y) => x.length < y.length) ==  // 文字列長が短い順
    Seq("one", "two", "zero", "four", "three") )

順序を定義する関数は、第1引数の方が小さい場合には true を、第2引数が小さい場合には false を返すように定義します。

sortBy メソッド
sortBy メソッドは、要素を別の型に変換して、その変換後のオブジェクトの自然な順序で並べ替えます。 返される Seq オブジェクトの要素は元の要素です(変換後の要素ではない)。

  val seq = Seq("zero", "one", "two", "three", "four")

  // sortBy メソッド
  assert( seq.sortBy(_.length) == Seq("one", "two", "zero", "four", "three") )
    // 文字列長で小さい順に並べる

サンプルコード(関数・部分関数を返すメソッド)

以下のメソッドは、PartialFunction トレイトに定義されているメソッドです。 どれも返り値は Seq ではなく関数もしくは部分関数 (PartialFunction) です。

lift メソッド
lift メソッドは、インデックスを指定するとその位置の要素を返すが、対応する要素がないときに例外が投げられないように Option Option で包んで返す関数を返します。

  // lift メソッド
  val lifted = seq.lift
  lifted(1) match {
    case Some(s) => println("[lift] " + s)
    case None    => println("[lift]error!")
  }
    // 「[lift] one」と表示される

orElse メソッド
orElse メソッドは、引数に別の部分関数をとり、返り値として部分関数を返します。 この返り値の部分関数は、インデックスに対応する Int 値を引数にとる apply メソッドを持ち、元の Seq オブジェクトがこのインデックスに対応する要素を持てばそれを返し、対応する要素がなければ orElse メソッドに渡された部分関数にインデックスの Int 値を渡してその結果を返します。

以下では、Seq が部分関数、つまり PartialFunction トレイトを継承していることを利用して、orElse の引数にも Seq を使っています。

  val seq1 = Seq("zero", "one", "two", "three", "four")
  val seq2 = Seq("0", "1", "2", "3", "4", "5", "6", "7")

  // orElse メソッド
  val oe1 = seq1 orElse seq2
  assert( oe1(0) == "zero" )  // seq1 より
  assert( oe1(4) == "four" )  // seq1 より
  assert( oe1(5) == "5" )  // seq2 より
  assert( oe1(7) == "7" )  // seq2 より

  // 逆に呼び出すと結果も異なる
  val oe2 = seq2 orElse seq1
  assert( oe2(0) == "0" )  // seq2 より

当然のことながら、orElse メソッドのレシーバと引数を逆にすると挙動が変わります。

compose メソッド
compose メソッドは数学的な関数の合成です。 関数 (f compose g)(x) は数学の関数の記法として f(g(x)) と同じです。

  val f = Seq("zero", "one", "two", "three")
  val g = Map("Alice" -> 0, "Bob" -> 1, "Eve" -> 2)

  // compose メソッド
  val fg = f compose g
  assert( fg("Alice") == "zero" )
  assert( fg("Bob")   == "one" )
  assert( fg("Eve")   == "two" )
  
  assert( fg("Alice") == f(g("Alice")) )  // (f compse g)(x) は f(g(x)) と同じ

andThen メソッド
andThen メソッドは compose メソッドと逆の合成をします。

  val f = Seq("zero", "one", "two", "three")
  val g: String => Char = _.charAt(0)  // 文字列の最初の1文字を返す

  // andThen メソッド
  val gf = f andThen g  // compose メソッドの逆
  assert( gf(0) == 'z' )
  assert( gf(1) == 'o' )
  assert( gf(2) == 't' )
  
  assert( gf(0) == g(f(0)) )  // (f andThen g)(x) は g(f(x)) と同じ

今回は以上です。

次回は元の Seq オブジェクトと要素数が異なる Seq オブジェクトを生成するメソッドを試す予定。

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

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

*1:scala.math.Ordering コンパニオン・オブジェクトに定義されている implicit な Ordering オブジェクトなど。