LL脳がscalaの勉強を始めたよ その57
Scalaコップ本の17章に入っていきますよー、ようやく折り返しですよー
17章はコレクションに関するアレやコレ、配列やリストやマップや集合についてやっていきますかねー
ライブラリーの概要
Scalaのコレクションライブラリーは数多くのクラスやトレイトが含まれるらしいんですが、概要として以下のトレイトだけを抑えておこーぜ!とのことです。
メインのトレイとがIterableで、そのサブトレイトとしてシーケンス(Seq)、集合、マップがある形ですね。シーケンス、集合、マップはそれぞれimmutableとmutableがあるっすね。シーケンスは配列やリストのような順序性があるもので、集合は乱暴に言って重複なしのコレクションで、マップはキーと値の対応関係のあるコレクションですな。
イテレータの生成
メインとなるIterableはelementsというメソッドでIteratorを作り出せるコレクションオブジェクトを表す名前だそうです…ということなのでIteratorを作ってみますよ
// とりあえずリストを生成で scala> val l = List(1,2,3) l: List[Int] = List(1, 2, 3) // elementsメソッドでイテレータを生成しますよ scala> val li = l.elements li: Iterator[Int] = non-empty iterator // hasNextメソッドで次の値の存在チェック scala> li.hasNext res17: Boolean = true // nextメソッドで次の値を取得します scala> li.next res18: Int = 1 // 次の値 scala> li.next res19: Int = 2 // 次の値 scala> li.next res20: Int = 3 // 存在チェックです、値が無いのでfalseが返ります scala> li.hasNext res21: Boolean = false // 次の値がない状態でアクセスするとエラー出まくりです scala> li.next java.util.NoSuchElementException: next on empty Iterator at scala.List$$anon$2.next(List.scala:603) at .<init>(<console>:7) at .<clinit>(<console>) at RequestResult$.<init>(<console>:3) at RequestResult$.<clinit>(<console>) at RequestResult$result(<console>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMeth...
上記のIteratorを使ってたくさんの具象メソッドが提供されているみたいですね。前回までにやったflatMapとかfilterおかexistsとかfindとかとか…とりあえず反復処理の根源みたいな存在ですかねー
ちなみにiteratorトレイトのスーパートレイとはAnyRefとのことです
シーケンス
ほんでは順序性のあるコレクションのシーケンスをざざっと見ていきますよー
リスト
シーケンスと言えば前の章でやったリストですなー。リストの主な特徴としてはイミュータブルな連結リスト構造で、先頭項目への追加削除が高速だけども任意の添字アクセスはデータ構造の都合上あまり高速ではないってのがそれですな。
欠点ぽく見える上記構造ですが、多くのアルゴリズムに適合する構造であったりで、またScala的にはパターンマッチに適している構造みたいです。そのうえイミュータブルなので値の保証もあるしねー、ということです。
とりあえず詳しくは16章とか22章なんですが、簡単なサンプルだけのっけておきますねー
// リストを定義しますよー scala> val l = List(1,2,3) l: List[Int] = List(1, 2, 3) // リストの先頭要素にアクセスしますね scala> l.head res26: Int = 1 // リストの先頭要素以外を取り出しますよー scala> l.tail res27: List[Int] = List(2, 3)
配列
リストが余り得意でない添字アクセスを効率的に行うことが出来るのが配列デス。とりあえず配列サンプルをのっけてみます。
// 配列定義してみました scala> val a = Array(1,2,3,4,5) a: Array[Int] = Array(1, 2, 3, 4, 5) // 4番目の要素にアクセスしてみました // 添字アクセスは()です scala> a(3) res28: Int = 4 // 他の言語風にアクセスすると怒られます(´・ω・`) scala> a[3] <console>:1: error: identifier expected but integer literal found. a[3] ^
ちなみに空の配列を作るときはこんな感じです
// 要素が整数で要素数が5つの空配列を生成します scala> val a = new Array[Int](5) a: Array[Int] = Array(0, 0, 0, 0, 0)
リストバッファー
Listクラスは先頭へのアクセスは高速な反面、末尾へのアクセスが遅かったので末尾へのアクセスが頻繁になる場合はreverseメソッドで要素順を反転するYO!って内容を16章でやりました。
でもListBufferを使えばそんな悩みともオサラバらしいです。ミュータブルなオブジェクトのListBufferは要素の追加を効率化してくれる(先頭でも末尾でも一定時間で処理してくれるみたいです)素敵構造みたいなので、リストを構築するためのツールっていう位置づけで良いかしら?
ちなみに次のような感じで使うみたいです
// scala.collection.mutableパッケージからListBufferを読み込みます scala> import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer // ListBufferオブジェクトを生成します scala> val buf = new ListBuffer[Int] buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer() // += 演算子で末尾に追加します scala> buf += 1 // もう一個末尾に追加します scala> buf += 2 // 追加結果はこんな感じです scala> buf res32: scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2) // +:演算子で先頭に追加します scala> 3 +: buf res33: scala.collection.mutable.Buffer[Int] = ListBuffer(3, 1, 2) // toListメソッドでListに変換しますよ scala> buf.toList res34: List[Int] = List(3, 1, 2)
ListBufferを使うことでスタックオーバーフローの危険を避けることができるそうです。また必要な再帰アルゴリズムが末尾再帰でない場合はfor式、whileループ、ListBufferの組み合わせを使うのがコップ本的オススメみたいです。詳しくは22章でー
ちなみにスタックオーバーフローはこんな感じみたいですねー ⇒ wikipedia:スタックオーバーフロー
コールスタックに格納できる情報量には上限がある。コールスタックに蓄積するデータ量が多すぎるとスタックは「オーバーフロー」し、プログラム側で対策をとっていなければ通常はクラッシュしてしまう。
・・・(中略)・・・
スタックオーバーフローの一番の原因は再帰による無限ループである。ただし、末尾最適化を実装した言語では末尾再帰をループへ展開することができ、末尾再帰ではスタックオーバーフローは起こらない。末尾再帰はループ処理に最適化されるので、再帰することそれ自体でスタックを消費することが無いからである。
配列バッファー
配列と同様の操作が可能だけども、先頭と末尾の要素を追加削除できるものとしてArrayBufferがあるみたいです。ただしArrayBufferは配列よりも若干速度が遅く、追加ds駆除操作は平均では一定時間になるものの、バッファーの内容新しい配列で保持する必要上、サイズに比例する時間が必要になる場合があるみたいです。
これだけ聞くとなんのメリットもなさそうなんですが、配列のサイズを指定せずに構築出来るのがメリットっぽいですな(´・ω・`)
とりあえず触ってみますかね
// パッケージをインポートします scala> import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer // ArrayBufferオブジェクトを生成します scala> val buf = new ArrayBuffer[Int]() buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer() // 末尾に追加します scala> buf += 1 // さらに末尾に追加しますよ scala> buf += 2 // 配列の内容を見てみますねー scala> buf res57: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer(1, 2) // 先頭に要素を追加しますね scala> 3 +: buf res58: scala.collection.mutable.Buffer[Int] = ArrayBuffer(3, 1, 2) //// 配列操作もできますよ(若干効率悪いけども) // 要素数を取得します scala> buf.length res59: Int = 3 // 要素にアクセスしますよ scala> buf(2) res60: Int = 2 // 配列に変換するときはtoArrayっすね scala> buf.toArray res61: Array[Int] = Array(8, 1, 2)
待ち行列(Queue)
Listの構造なんかだと先入れ後出しの処理が向いてたりするみたいなんですが、先入れ先出しのシーケンスが必要だったらQueueクラスを使えばいいYO!とのことです。Queueにもミュータブルとイミュータブルの2種類あるらしいデスが…とりあえず触ってみますかねー
// パッケージをインポートします // 今回はイミュータブル版を使ってみますよ scala> import scala.collection.immutable.Queue import scala.collection.immutable.Queue // 空のQueueを生成しますよ scala> val empty = new Queue[Int] empty: scala.collection.immutable.Queue[Int] = Queue() // enqueueメソッドで空Queueに要素を追加します scala> val has1 = empty.enqueue(1) has1: scala.collection.immutable.Queue[Int] = Queue(1) // 複数の要素を追加しますねー // 複数の要素を追加する場合はコレクション(ここではList)を指定しますです scala> val has123 = has1.enqueue(List(2,3)) has123: scala.collection.immutable.Queue[Int] = Queue(1, 2, 3) // dequeueメソッドでQueueから要素を取り出し(削除)しマス // (削除した要素、残りの要素) 形式のタプルで返ってきますねー scala> val (element, has23) = has123.dequeue element: Int = 1 has23: scala.collection.immutable.Queue[Int] = Queue(2, 3)
上記はイミュータブル版なのでenqueueやdequeueメソッドを使って新しいQueueを生成しているんですが、ミュータブル版だと要素の追加は演算子で処理できますね
// イミュータブル版をインポートしますよー scala> import scala.collection.mutable.Queue import scala.collection.mutable.Queue // 空のQueueを定義します scala> val queue = new Queue[Int] queue: scala.collection.mutable.Queue[Int] = Queue() // Queueに要素を追加する場合は+=演算子を使いマス scala> queue += 1 // 複数の要素をコレクション形式で追加する場合は++=演算子を使います scala> queue ++= List(2,3) // Queueの内容です scala> queue res64: scala.collection.mutable.Queue[Int] = Queue(1, 2, 3) // Queueの先頭要素(先入れしたやつ)をdequeueメソッドで削除します // 戻りは削除した要素が返されます scala> queue.dequeue res65: Int = 1 // 削除されたQueueの中身です scala> queue res66: scala.collection.mutable.Queue[Int] = Queue(2, 3)
スタック
後入れ先出しだったらStackを使うべき!だそうです。こいつもミュータブルとイミュータブルがあるみたいですが…とりあえずミュータブル版のサンプルをやってみますねー
// ミュータブル版のパッケージをインポートデス scala> import scala.collection.mutable.Stack import scala.collection.mutable.Stack // 空のスタックを生成します scala> val stack = new Stack[Int] stack: scala.collection.mutable.Stack[Int] = Stack() // スタックに値を追加します scala> stack.push(1) // スタックの中身はこんな感じです scala> stack res71: scala.collection.mutable.Stack[Int] = Stack(1) // スタックに更に値を追加します scala> stack.push(2) // 追加結果です scala> stack res73: scala.collection.mutable.Stack[Int] = Stack(1, 2) // 最後入れ(次のpopの候補)の要素を表示します scala> stack.top res74: Int = 2 // 要素は変更されません scala> stack res75: scala.collection.mutable.Stack[Int] = Stack(1, 2) // 最後入れの要素を取り出しますよ scala> stack.pop // 取り出した値が戻ってきます res76: Int = 2 // 取り出した要素は削除されます scala> stack res77: scala.collection.mutable.Stack[Int] = Stack(1)
ついでなんでイミュータブル版もやってみますかね
// イミュータブル版のパッケージをインポートします scala> import scala.collection.immutable.Stack import scala.collection.immutable.Stack // 空のスタックを生成します scala> val empty = new Stack[Int] empty: scala.collection.immutable.Stack[Int] = Stack() // 要素を追加しますね scala> val has1 = empty.push(1) has1: scala.collection.immutable.Stack[Int] = Stack(1) // さらに要素を追加しますね scala> val has12 = has1.push(2) has12: scala.collection.immutable.Stack[Int] = Stack(1, 2) // 最後入れ要素を確認します scala> has12.top res79: Int = 2 // 最後入れ要素を取り出しますよ // mutable版と違って取り出した要素は取得できないっすね(´・ω・`) scala> val has1pop2 = has12.pop has1pop2: scala.collection.immutable.Stack[Int] = Stack(1)
うん、QueueとStackは使いこなせるようになりたいな(`・ω・´)
文字列(RichString)
Seq[Char]という文字列シーケンスとしてRichStringってのがあるみたいです。ScalaではStringからRichStringへの暗黙の型変換を提供しているので、全ての文字列はSeq[Char]として使えるとのこと…
とりあえずサンプルやってみますかねー
// 大文字が含まれているかどうかをチェックしますよ scala> "Hello".exists(_.isUpperCase) res84: Boolean = true // 大文字がなければfalseデスネー scala> "hello".exists(_.isUpperCase) res85: Boolean = false
上記の処理の流れとしては、"Hello"というStringが暗黙の型変換によってSeq[Char]に変換され、existsメソッド経由で文字一つ一つに対してisUpperCaseメソッドを適用されるような感じですかねー