倭マン's BLOG

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

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

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 今回扱うのは、元の Seq オブジェクトとは要素が異なる(場合がある)Seq オブジェクトを返すメソッドです。 まぁ大体、要素の追加や削除を行うメソッドです。 前回と同様に、Seq のコンパニオン・オブジェクトに定義されているファクトリ・メソッドで返されたインスタンスイミュータブルなので、これらの追加・削除のメソッドを呼び出しても元の Seq オブジェクト自体はは変更されません。

今回扱うメソッド

素数を追加するメソッドには以下のようなものがあります。

def +:(elem: A): Seq[A]
def :+(elem: A): Seq[A]

def ++[B](that: GenTraversableOnce[B]): Seq[B]
def ++:[B](that: TraversableOnce[B]): Seq[B]
def ++:[B >: A, That](that: collection.Traversable[B])(implicit bf: CanBuildFrom[Seq[A], B, That]): That

def padTo(len: Int, elem: A): Seq[A]
def patch(from: Int, that: GenSeq[A], replaced: Int): Seq[A]

「+:」「++:」は関数型のプログラミング言語でよくある「::」「:::」と同じ様なものです。 「::」「:::」は Seq のサブ型の List に定義されています。

要素を削除するメソッドには以下のようなものがあります。

def slice(from: Int, until: Int): Seq[A]

def filter(p: (A) ⇒ Boolean): Seq[A]
def filterNot(p: (A) ⇒ Boolean): Seq[A]

def take(n: Int): Seq[A]
def takeRight(n: Int): Seq[A]
def takeWhile(p: (A) ⇒ Boolean): Seq[A]

def drop(n: Int): Seq[A]
def dropRight(n: Int): Seq[A]
def dropWhile(p: (A) ⇒ Boolean): Seq[A]

def distinct: Seq[A]

def collect[B](pf: PartialFunction[A, B]): Seq[B]

ではサンプルコードを試していきましょう。

サンプルコード(要素の追加)

まずは要素を追加するメソッド。

+:, :+ メソッド
+: メソッドはレシーバの Seq オブジェクトの先頭に引数のオブジェクトを追加します。 「:」で終わるので、演算子風に書いたとき(ピリオドと括弧を省略したとき)右結合になります。 :+ メソッドはレシーバの Seq オブジェクトの末尾に引数のオブジェクトを追加します。

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

  // +:, :+ メソッド
  val result = "-one" +: seq
  assert( result1 == Seq("-one", "zero", "one", "two") )

  val result2 = seq :+ "three"
  assert( result2 == Seq("zero", "one", "two", "three") )

+: も :+ も「:」(colon) がある方にレシーバのコレクション (collection) を書きます。

++:, ++ メソッド
++: メソッドはレシーバの Seq オブジェクトの先頭に引数のコレクションを追加します。 このメソッドも「:」で終わるので右結合ですね。 ++ メソッドはレシーバの Seq オブジェクトの末尾に引数のコレクションを追加します。

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

  // ++:, ++ メソッド
  val result1 = Seq("-two", "-one") ++: seq
  assert( result1 == Seq("-two", "-one", "zero", "one", "two") )

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

末尾に Seq を追加するメソッドは「:++」ではないんですね。 まぁ、List などの LinearSeq 型のコレクションは末尾に要素を追加するよりも先頭に要素を追加する方が高速なので、普通は「++」ではなく「++:」 を使う方がいいでしょう。

padTo メソッド
padTo メソッドは、第1引数で指定された長さになるまで第2引数を末尾に追加するメソッドです。 元々の Seq オブジェクトの長さが第1引数で指定された長さよりも長ければ、何もしません。

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

  // padTo メソッド
  val result = seq.padTo(6, "null")
  assert( result == Seq("zero", "one", "two", "null", "null", "null") )
  assert( seq.padTo(2, "null") == Seq("zero", "one", "two") )
    // 元々指定した長さより長ければそのまま

patch メソッド
patch メソッドは、第1引数で指定された位置から、第3引数で指定された長さだけ、第2引数で指定された Seq オブジェクトで置き換えます。 場合によっては長さが長くなったり短くなったりします。

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

  // patch メソッド
  val result1 = seq.patch(2, Seq("Two", "Three", "Four"), 1)
  assert( result1 == Seq("zero", "one", "Two", "Three", "Four", "three", "four") )
    // 位置2の "two" を引数の Seq で置き換える

  val result2 = seq.patch(2, Seq("Two", "Three", "Four"), 3)
  assert( result2 == Seq("zero", "one", "Two", "Three", "Four") )
    // 位置2から長さ3の "two", "three", "four" を引数の Seq で置き換える

  // 結果の Seq の長さを元と同じにするには
  val subseq = Seq("Two", "Three", "Four")
  val result3 = seq.patch(2, subseq, subseq.length)  // 引数の Seq の長さを指定
  assert( result3 == Seq("zero", "one", "Two", "Three", "Four") )

第3引数の役割が一見分かりにくい気がしますが、どうなんでしょう。

サンプルコード(要素の削除)

次は要素を削除するメソッド。 以前にやった tail, init メソッドもこのカテゴリのメソッドですが、head, last と一緒に試した方が分かりやすいかと思って先に試しました。

slice メソッド
slice メソッドは第1引数で指定される位置から第2引数で指定される位置(この位置の要素は含まない)の間の部分 Seq (サブ Seq)を返します。

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

  // slice メソッド
  val result = seq.slice(1, 3)
  assert( result == Seq("one", "two") )

filter, filterNot メソッド
filter メソッドは、要素に対する述語関数(要素を引数にとり Boolean 値を返す関数)を引数にとり、述語関数に適合する要素のみを返すメソッドです。 filterNot メソッドは、逆に述語関数に適合しない要素のみを返します。

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

  // filter, filterNot メソッド
  val result1 = seq.filter(_.length <= 3)  // 文字列の長さが3以下の要素
  assert( result1 == Seq("one", "two") )

  val result2 = seq.filterNot(_.length <= 3)
  assert( result2 == Seq("zero", "three", "four") )

filter メソッドは map, flatMap メソッドと合わせてモナド云々の話で出てくる重要なメソッドですね。

take, takeRight メソッド
take メソッドは、指定した個数だけ先頭から取り出した Seq を返します。 takeRight メソッドは指定した個数だけ末尾から取り出した Seq を返します。

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

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

takeWhile メソッド
takeWhile メソッドは、要素に対する述語関数を引数にとり、先頭から述語関数に適合する要素が続く分だけ取り出します。 一度適合しない要素が現れれば、それ以降の要素は捨てられます。

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

  // takeWhile メソッド
  val result = seq.takeWhile(_.length <= 4)  // 先頭から長さが4以下の要素だけを取り出す
  assert( result == Seq("zero", "one", "two") )

drop, dropRight メソッド
drop メソッドは take メソッドとは逆に、指定した個数だけ先頭から落とした Seq を返します。 dropRight は指定した個数だけ末尾から落とした Seq を返します。

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

  // drop, dropRight メソッド
  assert( seq.drop(3) == Seq("three", "four") )
  assert( seq.dropRight(3) == Seq("zero", "one") )

dropWhile メソッド
dropWhile メソッドは、要素に対する述語関数を引数にとり、takeWhile メソッドとは逆に、先頭から述語関数に適合する要素が続く限り落とし、述語関数に適合しない要素が最初に出てきたところ以降の要素を Seq として返します。

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

  // dropWhile メソッド
  val result = seq.dropWhile(_.length <= 4)  // 先頭から長さが4以下の要素を落とす
  assert( result == Seq("three", "four") )

distinct メソッド
distinct メソッドは重複する要素を削除します。

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

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

要素が同じかどうかは == メソッドで評価されるようで、(sorted メソッドの順序の指定のように)外部から指定することはできないようです。 まぁ、Ordering のようなトレイトが等値評価にないので当然かと思いますが。

collect メソッド
collect メソッドは、要素に対する部分関数を引数にとり、定義域に入っている要素には変換を施し、そうでない要素は落とします。 filter メソッドと map メソッドを合わせたようなメソッドです。 返り値の Seq の要素型は引数の部分関数の結果型となり、レシーバの Seq オブジェクトの要素型とは異なる場合があります(以下のサンプルでは同じ String 型ですが)。

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

  // collect メソッド
  val result = seq.collect{
    case s if s.startsWith("t") => "T" + s.substring(1)  // 最初の "t" を "T" に変える
  }
  assert( result == Seq("Two", "Three") )
}

個人的には filter と map で書いた方が読みやすいと思いますが、パフォーマンス的に良かったりするんでしょうか。

前回と今回で、元の Seq オブジェクトの要素を変更するメソッドを試してみました。 次回は要素全体を走査して値を計算する fold や reduce などを試す予定。

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

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