読者です 読者をやめる 読者になる 読者になる

倭マン's BLOG

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

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

Scala

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 オブジェクトなど。

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

Scala

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 の複数形ですね。 念のため。

Scala の Seq に定義されているメソッドを試す (3) ~要素の取得~

Scala

Scala の Seq に定義されているメソッドを試すシリーズ(目次)。 今回は Seq に格納されている要素を取得するメソッドと、各要素に対して副作用のある処理を行うメソッド(まぁ、foreach メソッドなんですが)を試します。

今回扱うメソッド

まずは要素を取得するメソッド。 Java の配列のようにインデックスを指定して要素を取得するには apply() メソッドを使います。

def apply(idx: Int): A
def applyOrElse[A1 <: Int, B1 >: A](x: A1, default: (A1) ⇒ B1): B1

def head: A
def headOption: Option[A]
def tail: Seq[A]

def last: A
def lastOption: Option[A]
def init: Seq[A]

def find(p: (A) ⇒ Boolean): Option[A]
def collectFirst[B](pf: PartialFunction[A, B]): Option[B]

また、各要素を走査して副作用を伴う処理を実行するメソッドは次の2つ:

def foreach(f: (A) ⇒ Unit): Unit
def runWith[U](action: (A) ⇒ U): (Int) ⇒ Boolean

foreach() メソッドはいろいろな言語に同様のメソッドがあるので使い方は問題ないでしょう。

Seq[A] は PartialFunction[Int, A] トレイト(部分関数)を拡張していますが、上記の中で

  • apply メソッド
  • applyOrElse メソッド
  • runWith メソッド

は PartialFunction トレイトから受け継いでいるメソッドです(他にもありますが)。

サンプルコード(要素の取得)

ではサンプルコードを見ていきましょう。 前回までは Int 値を要素とする Seq オブジェクトを主に使っていましたが、今回は String を要素とする Seq オブジェクトを使います。

apply メソッド
まずは基本となる、インデックスを指定して要素を取り出す apply メソッド。 Scala では apply メソッドは () で呼び出せますね(念のため)。

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

  // apply メソッド
  assert( seq(0) == "zero" )  // seq.head の方が望ましい
  assert( seq(3) == "three" )

  // 範囲外の要素にアクセスしたら例外を投げる
  try{
    seq(10)  // IndexOutOfBoundsException が投げられる
    assert(false)
  }catch{
    case ex: IndexOutOfBoundsException => assert( true )
  }

0番目の要素にアクセスする場合には、後で見る head メソッドを使った方が高速でアクセスできます(List などの LinearSeq の場合)。 指定したインデックスに要素がなかった場合は例外 (IndexOutOfBoundsException) が投げられます。

applyOrElse メソッド
applyOrElse メソッドは指定したインデックスに対応する要素があれば apply メソッドと同じようにその要素を返しますが、対応する要素がなかった場合に、apply メソッドのように例外を投げるのではなく、第2引数に指定した関数を評価した結果を返します。 第2引数に指定する関数は、引数が Int 値で返り値が要素の型とします。 まぁ、計算できるデフォルト値を設定して、対応する要素がなくても例外が投げられないようにする apply メソッドですね。

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

  // applyOrElse メソッド
  assert( seq.applyOrElse(0, (i: Int) => i.toString) == "zero")
  assert( seq.applyOrElse(3, (_:Int).toString) == "three" )
  assert( seq.applyOrElse(10, Integer.toString) == "10" )

第2引数に渡す関数をいろいろな形で書いてみました(他にも書き方ありますが)。 同じ関数を使うなら変数に格納して使い回した方がいいかと思います。

head メソッド
head メソッドは先頭(インデックスが0)の要素を取得します。 List などの LinearSeq を拡張する Seq では、apply メソッドに0を指定して要素を取得するより高速です。

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

  // head メソッド
  assert( seq.head == "zero")
  assert( seq.head == seq(0))  // インデックス0でも同じ要素を取得可

  try{
    Seq.empty.head
    // 空の Seq に対しては例外を投げる
    assert(false)
  }catch{
    case ex: NoSuchElementException => assert( true )
  }

空の Seq に対して呼び出すと例外 (NoSuchElementException) を投げます。

headOption メソッド
headOption メソッドは head メソッドのように先頭の要素を取得するメソッドですが、空の Seq に対して呼び出しても例外が投げられないように Option で包んで返します。 したがって、要素型が A なら、headOption メソッドの返り値の型は Option[A] となります。

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

  // headOption メソッド
  assert( seq.headOption == Some("zero") )
  assert( Seq.empty.headOption == None )

  // match などといっしょに使うのがいいかな?
  seq.headOption match {
    case Some(s) => println("[headOption] " + s)
    case None    => println("[headOption] error!")
  }
    // 「[headOption] zero」と表示される
    // seq の代わりに空の Seq に対して呼び出すと「[headOption] error!」と表示される

tail メソッド
tail メソッドは、元の Seq オブジェクトから head 要素を除いた Seq オブジェクトを返します。 head と tail で元の Seq を要素の過不足無く分割します。 このメソッドも LinearSeq では高速です。

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

  // tail メソッド
  assert( seq.tail == Seq("one", "two", "three", "four") )
  assert( (seq.head +: seq.tail) == seq)  // 「+:」まだやってないけど。
  assert( Seq("zero").tail == Nil )  // 要素が1つの場合は tail は空 Seq

  try{
    Seq.empty.tail
    // 空 Seq に対しては例外を投げる
    assert(false)
  }catch{
    case ex: UnsupportedOperationException => assert(true)
  }

1つの要素しか持たない Seq に対して tail メソッドを呼び出すと空の Seq が返され、空の Seq に対して tail メソッドを呼び出すと例外が投げられます。

last メソッド
last メソッドは head メソッドとは逆に、Seq の最後の要素を返します。 List などの LinearSeq を拡張する Seq に対して呼び出すと、要素数が多ければパフォーマンスが劣化するので注意。 使い方は head メソッドと同じです。

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

  // last メソッド
  assert( seq.last == "four" )
  assert( seq.last == seq(seq.length-1) )

  try{
    Seq.empty.last
    // 空の Seq に対して呼び出すと例外を投げる
    assert(false)
  }catch{
    case ex: NoSuchElementException => assert( true )
  }

空の Seq に対して last メソッドを呼び出すと、head メソッドの場合と同じように例外を投げます。

lastOption メソッド
lastOption メソッドは、最後の要素を取得する際に、最後の要素がなくても例外が投げられないように Option で包むメソッドです。 head メソッドに対する headOption メソッドと同じです。 使い方は headOption と同じ。

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

  // lastOption メソッド
  assert( seq.lastOption == Some("four") )
  assert( emp.lastOption == None )

  // match といっしょに使う
  seq.lastOption match {
    case Some(s) => println("[lastOption] " + s)
    case None    => println("[lastOption] error!")
  }
    // 「[lastOption] four」と表示される

init メソッド
init メソッドは、元の Seq オブジェクトから last 要素を除いた Seq オブジェクトを返します。 init と last で元の Seq を過不足なく分割します。 init メソッドも LinearSeq に対してはパフォーマンスを劣化させます。

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

  // init メソッド
  assert( seq.init == Seq("zero", "one", "two", "three") )
  assert( (seq.init :+ seq.last) == seq )  // 「:+」もまだやってない
  assert( Seq("zero").init == Nil )  // 要素が1つの Seq に足しては空の Seq を返す

  try{
    Seq.empty.init
    // 空の Seq に対して呼び出すと例外を投げる
    assert(false)
  }catch{
    case ex: UnsupportedOperationException => assert(true)
  }

find メソッド
find メソッドは、述語関数(条件)を指定して、それに適合する最初の要素を返します。 ただし、適合する要素がない場合にも例外が投げられないように、Option で包みます。

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

  // find メソッド
  assert( seq.find(_.length == 3) == Some("one") )  // 文字列長が3の要素
  assert( seq.find(_.length == 2) == None )

  // match といっしょに使ってみた
  seq.find(_.length == 3) match {
    case Some(s) => println("[find] " + s)
    case None    => println("[find] error!")
  }
    // 「[find] one」と表示される

collectFirst メソッド
collectFirst メソッドは PartialFunction オブジェクトを引数にとり、Seq オブジェクトの要素の中で最初にその PartialFunction の定義域に含まれている(isDefinedAt メソッドが true を返す)要素に対して部分関数を適用して、その結果を返します。 ただし、定義域に含まれている要素がない場合にも例外が投げられないように Option で包みます。 大雑把には、find メソッドの結果(None でなければ)の中身に変換を施して返すようなメソッドです。

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

  // collectFirst メソッド
  val result = seq.collectFirst{
    case s if s.startsWith("t") =>  // "t" で始まる文字列がこの部分関数の定義域
      "T" + s.substring(1)          // 最初の "t" を "T" に変えて返す
  }
  assert( result == Some("Two") )

  // match といっしょに使ってみた
  seq.collectFirst{
    case s if s.startsWith("t") => "T" + s.substring(1)
  } match {
    case Some(s) => println("[collectFirst] " + s)
    case None    => println("[collectFirst] error!")
  }
    // 「[collectFirst] Two」と表示される

なんかサンプルコードがいろいろぎこちないですが、まぁあまり気にしない方向で。

サンプルコード(副作用を伴う処理)

次は副作用を伴う処理を行うメソッド。 副作用を伴うと言っても元の Seq を変更するわけではなく、返り値が Unit の関数とともに使うという意味です。

foreach メソッド
foreach メソッドは、要素に対する副作用を伴う関数を引数にとり、各要素にその処理を施します。 Seq に含まれる要素全てに(副作用を伴う)同じ処理を行いたいときに使います。

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

  // foreach メソッド
  seq.foreach { s =>
    println("[foreach] " + s)
  }

runWith メソッド(PartialFunction より)
runWith メソッドは PartialFunction トレイトに定義されているメソッドです。 副作用を伴う関数(返り値が Unit の関数)を引数にとるので foreach メソッドといっしょに書いてますが、あまり要素の取得という感じのメソッドではありません。 どの程度使われるメソッドなのか分かりませんが、説明が面倒なのでサンプルコードのみで。

  val classmates = Seq("Alice", "Bob", "Eve")

  // runWith メソッド
  val greet: Int => Boolean =  // greet の型指定は別になくても OK
    classmates.runWith{ s =>
      println(s"Hello $s!")
    }

  greet(0)  // 「Hello Alice!」と表示(true を返す)
  greet(2)  // 「Hello Eve!」と表示(true を返す)
  greet(3)  // 何も表示しない(false を返す)

いつかこれが使える局面に出会うんでしょうか。

今回は Seq に格納されている要素を取得するメソッドを主に試してきました。 次回はインデックス関連のメソッドを試す予定。

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

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