倭マン's BLOG

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

Scala の Seq に定義されているメソッドを試す (2) ~要素の有無・要素数~

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 前回はコンパニオン・オブジェクトに定義されているファクトリ・メソッドを試しましたが、今回からは Seq トレイトに定義されているメソッドを見ていきます。 一度に全てを見るのは無理なので適当に分類しています。 今回は Seq 内の要素の有無と要素数に関連するメソッドです。

今回扱うメソッド

今回扱うメソッドの定義を見ておきましょう。 まずは Seq 内の要素の有無に関するメソッド。 Boolean 値を返すメソッドを集めています:

// 要素の有無に関するメソッド
def isEmpty: Boolean
def nonEmpty: Boolean

def contains[A1 >: A](elem: A1): Boolean
def containsSlice[B](that: GenSeq[B]): Boolean

def startsWith[B](that: GenSeq[B]): Boolean
def startsWith[B](that: GenSeq[B], offset: Int): Boolean
def endsWith[B](that: GenSeq[B]): Boolean

def sameElements(that: GenIterable[A]): Boolean

def forall(p: (A) ⇒ Boolean): Boolean
def exists(p: (A) ⇒ Boolean): Boolean
def corresponds[B](that: GenSeq[B])(p: (A, B) ⇒ Boolean): Boolean

もう一つは Seq の要素の個数に関するメソッド。 主に要素数に関する Int 値を返すメソッドを集めています。 hasDefiniteSize メソッドは Boolean 値を返しますが。

// 要素の個数に関するメソッド
def hasDefiniteSize: Boolean

def length: Int
def size: Int

def lengthCompare(len: Int): Int

def count(p: (A) ⇒ Boolean): Int
def prefixLength(p: (A) ⇒ Boolean): Int
def segmentLength(p: (A) ⇒ Boolean, from: Int): Int

では1つ1つメソッドを試していきましょう。

サンプルコード(要素の有無)

まずは要素の有無に関するメソッド。

isEmpty メソッド
isEmpty メソッドは、その Seq オブジェクトが空かどうかを返します。 空の場合は true、空でない場合は false を返します。

  val emp = Seq.empty[Int]
  val seq = Seq(0, 1, 2, 3, 4)

  // isEmpty メソッド
  assert( emp.isEmpty )
  assert( !seq.isEmpty )  // seq0.nonEmpty の方が望ましい

空でないことを確かめたい(空でないときに true を返す)場合には、次の nonEmpty メソッドを使う方が良いようです。

nonEmpty メソッド
nonEmpty メソッドは、その Seq オブジェクトが空でないかどうかを返します。 空でない場合は true、空の場合は false を返します。

  val emp = Seq.empty[Int]
  val seq = Seq(0, 1, 2, 3, 4)

  // nonEmpty メソッド
  assert( !emp.nonEmpty )  // empty の方が望ましい
  assert( seq.nonEmpty )

contains メソッド
contains メソッドは、その Seq オブジェクトが引数で指定した要素を含んでいるかどうかを返します。

  val seq = Seq(0, 1, 2, 3, 4)

  // contains メソッド
  assert( seq.contains(1) )
  assert( !seq.contains(5) )

containsSlice メソッド
containsSlice メソッドは、その Seq オブジェクトが指定した Seq をサブ Seq (部分 Seq)を含んでいるかどうかを返します。

  val seq = Seq(0, 1, 2, 3, 4)

  // containsSlice メソッド
  assert( seq.containsSlice(Seq(1, 2, 3)) )
  assert( !seq.containsSlice(Seq(1, 5)) )  // 「5」を含んでないのでダメ
  assert( !seq.containsSlice(Seq(1, 3)) )  // 「1, 3」という連なりで含んでいないのでダメ

  assert( seq.containsSlice(Nil) )  // 空 Seq は全ての Seq に含まれる

サブ Seq として含まれているかどうかなので、順序や連なりも合っていないといけません。 また、空 Seq を引数として渡すと true が返されます。

startsWith, endsWith メソッド
startsWith, endsWith メソッドは、その Seq オブジェクトがそれぞれ指定された Seq で始まる、もしくは終わるかどうかを返します。 startsWith メソッドで2つの引数をとるものは、2つ目の引数でサブ Seq を調べ始める位置を指定します。

  val seq = Seq(0, 1, 2, 3, 4)

  // startsWith メソッド
  assert( seq.startsWith(Seq(0, 1)) )
  assert( !seq.startsWith(Seq(2, 3)) )
  assert( seq.startsWith(Seq(2, 3), 2) )
    // seq の位置2以降の Seq、つまり Seq(2, 3, 4) に対して startsWith

  assert( seq.startsWith(Nil) )  // 空 Seq を渡すと true を返す
  assert( seq.startsWith(Nil, 2) )

  // endsWith メソッド
  assert( seq.endsWith(Seq(2, 3, 4)) )

  assert( seq.endsWith(Nil) )  // 空 Seq を渡すと true を返す

contains と同じように、引数として空 Seq を渡した場合は true を返します。

sameElementsメソッド
sameElments メソッドは、その Seq オブジェクトが指定された Seq と同じ要素を同じ順序で含んでいるかどうかを返します。 Seq オブジェクトに対する「==演算子と同じ等価性評価なので、「==」を使う方がいいようです。

  val seq = Seq(0, 1, 2, 3, 4)

  // sameElements メソッド
  assert( seq.sameElements(0 to 4) )  // 実装クラスが違っても大丈夫
  assert( !seq.sameElements(Seq(4, 3, 2, 1, 0)) )
  assert( seq == (0 to 4) )  // 「==」と同じ

forall メソッド
forall メソッドは、要素に対する述語関数(要素を受け取りBoolean 値を返す関数)を引数にとって、全ての要素がその述語関数を満たすかどうかを返します。 各要素に対する述語関数の結果を論理積 AND で合わせたものですね。

  val seq = Seq(0, 1, 2, 3, 4)
  val emp = Seq.empty[Int]

  // forall メソッド
  assert( seq.forall(_ < 10) )  // 全ての要素が10未満 ⇒ true
  assert( !seq.forall(_ < 3) )  // 全ての要素が3未満 ⇒ false
  assert( emp.forall(_ => false) )  // 空 Seq に対しては常に true

空 Seq に対して呼び出すと true を返します。

exists メソッド
exists メソッドは、要素に対する述語関数を引数にとって、ある要素がその述語関数を満たすかどうかを返します。 こちらは各要素に対する述語関数の結果を論理和 OR で合わせたものですね。

  val seq = Seq(0, 1, 2, 3, 4)
  val emp = Seq.empty[Int]

  // exists メソッド
  assert( seq.exists(_ < 1) )  // 1未満の要素が存在する ⇒ true
  assert( !seq.exists(_ > 10) )  // 10より大きい要素が存在する ⇒ false
  assert( !emp.exists(_ => true) )  // 空 Seq に対しては常に false

空 Seq に対して呼び出すと false を返します。 そんな要素は存在しないので。

corresponds メソッド
corresponds メソッドは2つの Seq オブジェクトが何かしらの対応関係があるかどうかを返します。 対応関係は2つ目の引数リストで指定します。

  val seq = Seq(0, 1, 2, 3, 4)
  val seq1 = Seq(0, 2, 4, 6, 8)

  // corresponds メソッド
  // ちょっと制御構造風に書いてみた
  assert(
    (seq corresponds seq1){ (i, j) =>
      i*2 == j  // seq の要素を2倍すると、対応する seq1 の要素になるかどうか
    }
  )

  val isHalfOf: (Int, Int) => Boolean = (i, j) => i*2 == j
  assert( !(seq corresponds Seq(0, 1, 4, 6, 7))(isHalfOf) )  // 「7」がダメ
  assert( !(seq corresponds Seq(0, 1, 4, 6))(isHalfOf) )  // 要素数が違うとダメ

  // 2つの Seq オブジェクトの要素型が一致する必要はない
  val seq2 = Seq("0", "1", "2", "3", "4")  // Seq[String]
  assert( (seq corresponds seq2)((i, s) => i.toString == s) )

比べる2つの Seq オブジェクトは、要素数が一致していないと false を返します(例外が投げられたりはしない)。 また、要素型が一致している必要はありません。

サンプルコード(要素数

次は要素数に関連するメソッド。

hasDefiniteSize メソッド
hasDefiniteSize メソッドは、その Seq オブジェクトが有限の長さを持つかどうかを返します。

  // hasDefiniteSize メソッド
  assert( Seq(0, 1, 2, 3, 4).hasDefiniteSize )
  assert( !Stream(1, 2, 3).hasDefiniteSize )  // Stream は遅延評価する Seq

Stream は Seq トレイトのサブ型で遅延評価される Seq ですが、Stream オブジェクトの hasDefiniteSize メソッドは有限で最後まで評価されていなければ false を返します。 Iterator なども同様です。

length, size メソッド
length メソッドは、その Seq オブジェクトの要素数を返します。 size メソッドは Seq オブジェクトに対しては length メソッドと同じ動作をします*1

  val seq = Seq(0, 1, 2, 3, 4)

  // length, size メソッド
  assert( seq.length == 5 )
  assert( seq.length == seq.size)  // length も size も要素数

lengthCompare メソッド
lengthCompare メソッドは、その Seq オブジェクトの長さと引数の Int 値を比べて、大小関係を表す Int 値を返します。 レシーバの Seq オブジェクトの長さが引数より大きければ正の Int 値を、同じなら0を、小さければ負の Int 値を返します。

  val seq = Seq(0, 1, 2, 3, 4)

  // lengthCompare メソッド
  seq lengthCompare 5 match {
    case x if x < 0 => println("too short!")
    case 0          => println("nice!")
    case x if x > 0 => println("too long!")
  }
    // 「nice!」と表示 

返される Int 値が -1, 0, 1 に限定されてれば case の条件がもっと書きやすいんですけどね。

count メソッド
count メソッドは、引数に要素に対する述語関数を指定し、その述語関数に適合する(true を返す)要素の個数を返します。

  val seq = Seq(0, 1, 2, 3, 4)

  // count メソッド
  assert( seq.count(_ % 2 == 0) == 3 )  // 偶数の個数

prefixLength, segmentLength メソッド
prefixLength メソッドは、引数に要素に対する述語関数を指定し、Seq の先頭から述語関数に適合する要素の個数を返します。 述語関数に適合しない要素が現れれば、それ以降の要素は評価されません。

segmentLength メソッドは、引数に要素に対する述語関数と評価を開始するインデックスを指定し、そのインデックスの位置から初めて述語関数に適合する要素の個数を返します。 評価を始める位置以外は prefixLength と同じです。

  val seq = Seq(0, 1, 2, 3, 4)

  // prefixLength メソッド
  assert( seq.prefixLength(_ < 3) == 3 )  // 先頭から3未満の要素の数 ⇒ 0, 1, 2 の3個
  assert( seq.prefixLength(_ > 2) == 0 )  // 先頭から2より大きい要素の数 ⇒ 0個

  // segmentLength メソッド
  assert( seq.segmentLength(_ < 3, 2) == 1 )  // 0, 1, [[2, 3, 4]]
  assert( seq.segmentLength(_ > 2, 3) == 2 )  // 0, 1, 2, [[3, 4]]

以上です。

2, 3個のメソッドはシグニチャを見ただけでは動作がちょっと分かりにくいものもありましたが、基本的にはシグニチャだけで大まかな使い方は分かるものがほとんどでした。 細かいところでは空の Seq に対して呼び出したり、空の Seq を引数に渡したりするとどうなるのか?というのが気になるものもありましたが、普通に使ってる分にはあまり問題にならなさそうですね。 次回は要素へのアクセスに関するメソッドを見ていく予定。

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

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

*1:size メソッドは、コレクションの最上位である TraversableOnce トレイトに定義されていて、Iterator や Set などでも使えます。 一方、length は Seq で定義され、並べられた要素の個数というイメージで使われているのだと思います。 と言いつつ、SortedSet などに length メソッドが定義されているわけではありませんが。