倭マン's BLOG

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

Scala の Seq に定義されているメソッドを試す (4) ~インデックス~

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 今回はインデックスに関連するメソッドを試していきます。

今回扱うメソッド

今回扱うメソッドは以下の通り。 最初の2つ以外はインデックスの Int 値を返すメソッドです。 似たようなメソッドがありますが一貫した命名・シグニチャで定義されているので分かりやすいかと思います。

def isDefinedAt(idx: Int): Boolean
def indices: Range

// 指定した要素のインデックスを返す
def indexOf(elem: A): Int
def indexOf(elem: A, from: Int): Int
def lastIndexOf(elem: A): Int
def lastIndexOf(elem: A, end: Int): Int

// 指定した Seq が始まるインデックスを返す
def indexOfSlice[B >: A](that: GenSeq[B]): Int
def indexOfSlice[B >: A](that: GenSeq[B], from: Int): Int
def lastIndexOfSlice[B >: A](that: GenSeq[B]): Int
def lastIndexOfSlice[B >: A](that: GenSeq[B], end: Int): Int

// 指定した条件に合う要素のインデックスを返す
def indexWhere(p: (A) ⇒ Boolean): Int
def indexWhere(p: (A) ⇒ Boolean, from: Int): Int
def lastIndexWhere(p: (A) ⇒ Boolean): Int
def lastIndexWhere(p: (A) ⇒ Boolean, end: Int): Int

サンプルコード

isDefinedAt メソッド
isDefinedAt メソッドは、そのインデックスに対応する要素が存在するかどうかを返します。 つまり、Seq オブジェクトの長さを length として、引数の Int 値 i が 0 <= i < length の範囲内にあれば true を、そうでなければ false を返します。 このメソッドは PartialFunction トレイトから継承しているメソッドです。

  val seq = Seq("a", "a", "b", "a", "b", "c")  // seq.length == 6

  // isDefinedAt メソッド
  assert( seq.isDefinedAt(4) )
  assert( !seq.isDefinedAt(10) )

インデックスをチェックするための条件式が簡単に書けていいような、逆に可読性が下がるような、なんとも言えないメソッドな気が。

indices メソッド
indices*1 メソッドは、その Seq オブジェクトのインデックスを Range オブジェクト(Seq[Int] のサブ型)として返します。 まぁ、実質的に (0 until seq.length) と同じです。

  val seq = Seq("a", "a", "b", "a", "b", "c")

  // indices メソッド
  assert( seq.indices == (0 until seq.length) )

  // 例えば for 文でこのように使える
  for(i <- seq.indices){
    println(s"$i: ${seq(i)}")
  }

Seq 内の要素を走査して何かしらの処理をしたいが、インデックスの値も使いたい場合、前回やった foreach メソッドではインデックスの取得が面倒ですが、上記のサンプルコードの後半にあるように indices で for 文を回すと簡単にインデックスを扱えます(そのうちやる zipWithIndex メソッドを使ってもできますが)。

indexOf, lastIndexOf メソッド
indexOf メソッドは、指定した要素が最初に見つかった位置を返します。 第2引数に Int 値を与えると、その位置より後(その位置も含む)に見つかった要素の位置を返します。

lastIndexOf メソッドは指定した要素が最後に見つかった位置を返します。 第2引数に Int 値を与えると、その位置よりも前(その位置を含む)までに見つかった要素の位置を返します。

どちらのメソッドも要素が見つからなければ -1 を返します。

  val seq = Seq("a", "a", "b", "a", "b", "c")

  // indexOf メソッド
  assert( seq.indexOf("b") == 2 )
  assert( seq.indexOf("b", 3) == 4)  // 位置3から検索
  assert( seq.indexOf("z") == -1 )  // 該当する要素がなければ -1 を返す

  // lastIndexOf メソッド
  assert( seq.lastIndexOf("b") == 4)
  assert( seq.lastIndexOf("b", 2) == 2 )  // 位置2まで検索(位置2の要素も含む)
  assert( seq.lastIndexOf("z") == -1 )

indexOfSlice, lastIndexOfSlice メソッド
indexOfSlice メソッドは、指定した Seq を部分 Seq として含む最初の位置(部分 Seq の先頭の位置)を返します。 第2引数に Int 値を指定すると、その位置から検索を開始します。

lastIndexOfSlice メソッドは、指定した Seq を部分 Seq として含む最後の位置(部分 Seq の先頭の位置)を返します。 第2引数に Int 値を指定すると、その位置までの検索をします。 ただし、指定した Seq がその位置を超えて続いていても、先頭がその位置よりも前(その位置も含む)ならば検索にかかります。

どちらのメソッドでも指定した Seq が含まれていなければ -1 を返します。 また、空 Seq を指定すると先頭もしくは末尾の位置が返されます。

  val seq = Seq("a", "a", "b", "a", "b", "c")

  // indexOfSlice メソッド
  val ab = Seq("a", "b")
  assert( seq.indexOfSlice(ab) == 1 )
  assert( seq.indexOfSlice(ab, 2) == 3)  // 位置2から検索を開始する

  val xyz = Seq("x", "y", "z")
  assert( seq.indexOfSlice(xyz) == -1 )
  assert( seq.indexOfSlice(Seq.empty) == 0 )  // 空 Seq を渡すと先頭の位置が返される
  assert( seq.indexOfSlice(Seq.empty, 3) == 3 )  // 空 Seq を渡すと検索開始の位置が返される

  // lastIndexOfSlice メソッド
  assert( seq.lastIndexOfSlice(ab) == 3)
  assert( seq.lastIndexOfSlice(ab, 2) == 1 )  // 位置2まで検索を行う
    // 含まれている部分 Seq が位置2を超えていても、先頭が位置2かそれより前ならば検索にかかる
  assert( seq.lastIndexOfSlice(ab, 3) == 3 )  // 位置3まで検索を行う
    // 部分 Seq が位置3から始まっていても3が返される

  assert( seq.lastIndexOfSlice(xyz) == -1 )
  assert( seq.lastIndexOfSlice(Seq.empty) == seq.length )  // 空 Seq を渡すとレシーバの Seq の長さが返される
  assert( seq.lastIndexOfSlice(Seq.empty, 3) == 3 )  // 空 Seq を渡すと検索終了位置が返される

lastIndexOfSlice メソッドは、第2引数に Int 値を指定したときに、その位置までに指定した部分 Seq が含まれていないといけないかと思ったんですが、そういう挙動ではないようですね。 もしそういうインデックスが必要なら、そのうちやる slice メソッド(take メソッドの方がいいかな?)で部分 Seq を取得してから lastIndexOfSlice メソッドを使えばいいかと思います。

indexWhere, lastIndexWhere メソッド
indexWhere メソッドは、述語関数(要素に対する条件)を引数にとり、その述語関数に適合する最初の要素の位置を返します。 第2引数に Int 値を指定すると、その位置から検索を開始します。

lastIndexWhere メソッドは、述語関数を引数にとり、その述語関数に適合する最後の要素の位置を返します。 第2引数に Int 値を指定すると、その位置まで(その位置を含む)検索を行います。

述語関数に合致する要素がなければ -1 を返します。

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

  // indexWhere メソッド
  assert( seq.indexWhere(_.length == 3) == 2 )  // 長さが3の要素のインデックスを返す
  assert( seq.indexWhere(_.length == 3, 3) == 4)

  // lastIndexWhere メソッド
  assert( seq.lastIndexWhere(_.length == 3) == 5)
  assert( seq.lastIndexWhere(_.length == 3, 3) == 2 )

まぁ特に問題はないかと思います。

今回はインデックスを扱うメソッドを試してきました。 インデックス関連では他に zipWithIndex メソッドというのありますが、これは zip メソッドなどとまとめてやることにします。

次回は Seq に含まれている要素のマッピングに関連するメソッドを試していく予定。

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

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

*1:indices は index の複数形ですね。 念のため。