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 に格納されている要素を取得するメソッドを主に試してきました。 次回はインデックス関連のメソッドを試す予定。
- 作者: Martin Odersky,Lex Spoon,Bill Venners,羽生田栄一,水島宏太,長尾高弘
- 出版社/メーカー: インプレス
- 発売日: 2016/09/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る