Scalaの基本(その2)List・Map・Setの使い方
ScalaのコレクションAPIを徹底解説!List・Map・Setの使い方から、foldLeft・map・filterなどの便利メソッドまで、サンプルコード付きで詳しく解説します。
動作環境
Scala:2.12
コレクションライブラリ(immutable と mutable)
Scalaには、一度作成したら変更できない(immutable)なコレクションと変更できる(mutable)通常のコレクションがある。
immutableなコレクションを使うメリット
- 関数型プログラミングで多用する再起との相性が良い
- 高階関数を用いて簡潔なプログラムを書くことができる
- 一度作ったコレクションが知らない箇所で
- 並行に動作するプログラムの中で、安全に受け渡しすることができる
Array(mutable)
Arrayの定義例:
val arr = Array(1, 2, 3, 4, 5)
// 型を省略せずに書くと
val arr = Array[Int](1, 2, 3, 4, 5)ここで、[Int]の部分は型パラメータと呼ぶ。Arrayだけではどの型か分からないので、[Int]と付けることでどの型のArrayかを指定している。(しかし、この場面では、Arrayの要素型はIntだとわかっているので、冗長な書き方)
この型パラメータは型推論を補うために、色々な箇所で出てくる。
Range
Rangeは範囲を表すオブジェクト
Rangeの使用例:
scala> 1 to 5
res1: scala.collection.immutable.Range.Inclusive = Range 1 to 5
scala> (1 to 5).toList
res2: List[Int] = List(1, 2, 3, 4, 5)
scala> 1 until 5
res3: scala.collection.immutable.Range = Range 1 until 5
scala> (1 until 5).toList
res4: List[Int] = List(1, 2, 3, 4)List(immutable)
ScalaではArrayを使うことはそれほど多くなく、ListやVectorといったデータ構造を多用する。Listの特徴は、一度作成したら中身を変更できない(immutable)であること。中身を変更できないデータ構造(永続データ構造と呼ぶ)はScalaがサポートしている関数型プログラミングにとって重要な要素。
Listの実行例:
scala> val lst = List(1, 2, 3, 4, 5)
lst: List[Int] = List(1, 2, 3, 4, 5)
scala> lst(0) = 7
<console>:13: error: value update is not a member of List[Int]
lst(0) = 7
^Listは値を更新することはできないが、その代わりとしてListを元に新たなListを作ることができる。
Listの先頭に要素をくっつける
::(コンスと読む)は既にあるListの先頭に要素をくっつけるメソッド
実行例:
scala> val a1 = 1 :: Nil
a1: List[Int] = List(1)
scala> val a2 = 2 :: a1
a2: List[Int] = List(2, 1)List同士の連結
++はList同士を連結するメソッド
実行例:
scala> List(1, 2) ++ List(3, 4)
res1: List[Int] = List(1, 2, 3, 4)※ 大きなList同士を連結する場合、計算量が大きくなるので注意
mkString:文字列のフォーマッティング
mkStringフォーマッティングのやり方は3種類ある。
- mkString(引数なしバージョン)
scala> List(1, 2, 3, 4, 5).mkString
res1: String = 12345※ 引数なしメソッドのmkStringは()を付けて読みだすことができない。逆に()を使って定義されたメソッドは、()を付けても付けなくても良いことになっているので注意
- mkString(sep: String)
scala> List(1, 2, 3, 4, 5).mkString(",")
res1: String = 1,2,3,4,5- mkString(start: String, sep: String, end: String)
scala> List(1, 2, 3, 4, 5).mkString("[", ",", "]")
res1: String = [1,2,3,4,5]foldLeft:左からの畳み込み
foldLeftメソッドの宣言:
def foldLeft[B](z: B)(f: (B, A) => B): B実行例:
// Listの要素の合計を求める場合の例
scala> List(1, 2, 3).foldLeft(0)((x, y) => x + y)
res0: Int = 6
// Listの要素を全て掛け合わせた結果を求める場合の例
scala> List(2, 2, 3).foldLeft(1)((x, y) => x * y)
res1: Int = 12与える引数の順序がちょうど対称となっているfoldRightメソッドも存在する。
map:各要素を加工した新しいListを返す
mapメソッドは、1引数の関数を引数に取り、各要素に関数を適用した結果で生まれた要素からなる新たなListを返す
Listの各要素を2倍したmapメソッドの実行例:
scala> List(1, 2, 3, 4, 5).map(x => x * 2)
res0: List[Int] = List(2, 4, 6, 8, 10)x => x * 2の部分は、無名関数を定義するための構文。
filter:条件に合った要素だけを抽出した新しいListを返す
filterメソッドはBoolean型を返す1引数の関数を引数に取り、各要素に関数を適用し、trueになった要素のみを抽出した新たなListを返す
List内の奇数だけ抽出する実行例:
scala> List(1, 2, 3, 4, 5).filter(x => x % 2 == 1)
res0: List[Int] = List(1, 3, 5)find:条件に合った最初の要素を返す
findメソッドはBoolean型を返す1引数の関数を引数に取り、各要素に前から順番に関数を適用し、最初にtrueになった要素をSomeでくるみ、Option型をして返す。1つの要素もマッチしなかった場合は、NoneをOption型として返す。
List内の最初の奇数だけを抽出する実行例:
scala> List(1, 2, 3, 4, 5).find(x => x % 2 == 1)
res0: Option[Int] = Some(1)takeWhile:先頭から条件を満たしている間を抽出する
takeWhileメソッドは、Boolean型を返す1引数の関数を引数に取り、前から順番に関数を適用し、結果がtrueの間のみからなるListを返す
List内の5より前の4要素を抽出する実行例:
scala> List(1 ,2 ,3, 4, 5).takeWhile(x => x != 5)
res0: List[Int] = List(1, 2, 3, 4)count:Listの中で条件を満たしている要素の数を計算する
countメソッドは、Boolean型を返す1引数の関数を引数に取り、全ての要素に関数を適用し、trueが返ってきた要素の数を計算する。
List内の偶数の数を計算する例:
scala> List(1, 2, 3, 4, 5).count(x => x % 2 == 0)
res0: Int = 2flatMap:Listを平らにする
flatMapメソッドの宣言:
final def flatMap[B](f: (A) => GenTraversableOnce[B]): List[B]GenTraversableOnce[B]はあらゆるコレクション(要素の型はB型である)を入れることができる型と考える。
flatMapの使用例1:使用例1は、ネストしたListの各要素にflatMapの中でmapを適用し、Listの各要素に1を足したものを平らにしている。
scala> List(List(1, 2, 3), List(4, 5)).flatMap{e => e.map{g => g + 1}}
res0: List[Int] = List(2, 3, 4, 5, 6)flatMapの使用例2:使用例2は、List(1, 2, 3)とList(4, 5)の2つのListについてループし、各々の要素を掛け合わせた要素からなるListを抽出している。
scala> List(1, 2, 3).flatMap{e => List(4, 5).map(g => e * g)}
res0: List[Int] = List(4, 5, 8, 10, 12, 15)Listの性能特性
Listの先頭要素へのアクセスは高速にできる- 要素へのランダムアクセスや末尾へのデータ追加は、
Listの長さに比例した時間がかかってしまう
紹介したメソッドについて
mkStringをはじめとしたflatMapなどListの色々なメソッドを紹介してきたが、これらの大半は、List特有ではなく、RangeやArrayなどの他のコレクションでも同様に使うことができる。その理由は、これらのメソッドによる操作の大半は、コレクションのスーパークラスである共通トレイト中に宣言されているからである。各メソッドの詳細はScalaのAPIドキュメントより。
他のコレクションライブラリについては、以下の公式ドキュメントを参照
Vector(immutable)
Vectorは一度データ構造を構築したら変更できないimmutableなデータ構造である。要素へのランダムアクセス・長さの取得・データの挿入や削除などいずれの操作も十分高速にできる。
Vectorの使用例:
scala> Vector(1, 2, 3, 4, 5)
res0: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5)
scala> Vector(1, 2, 3, 4, 5) :+ 6
res1: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 5, 6)
scala> Vector(1, 2, 3, 4, 5).updated(2, 8)
res2: scala.collection.immutable.Vector[Int] = Vector(1, 2, 8, 4, 5)Map(immutable・mutable)
Mapはキーから値へのマッピングを提供するデータ構造。(他の言語では、辞書や連想配列と呼ばれたりする)Scalaでは、一度作成したら変更できないimmutableなMapと変更できるmutableなMapの2種類が提供されている。
Scalaでは、何も設定せずMapと書いた場合、scala.collection.immutable.mapが使われる。
scala.collection.immutable.mapの使用例:
scala> val m = Map("A" -> 1, "B" -> 2, "C" -> 3)
m: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)
scala> m.updated("A", 0) //一見元のMapが変更されたように見えるが
res1: scala.collection.immutable.Map[String,Int] = Map(A -> 0, B -> 2, C -> 3)
scala> m // 元のMapはそのままで変更されていない
res2: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)scala.collection.mutable.mapの使用例:
scala> import scala.collection.mutable
import scala.collection.mutable
scala> val m = mutable.Map("A" -> 1, "B" -> 2, "C" -> 3)
m: scala.collection.mutable.Map[String,Int] = Map(A -> 1, C -> 3, B -> 2)
scala> m("A") = 0 // A -> 0のマッピングに置き換える
scala> m // 変更が反映されている
res0: scala.collection.mutable.Map[String,Int] = Map(A -> 0, C -> 3, B -> 2)Set(immutable・mutable)
Setは値の集合を提供するデータ構造。Setの中では同じ値が2以上存在しない。(重複した値は自動で削除される)
Mapと同じでScalaでは何も設定せずSetと書くとscala.colletion.immutable.Setが使われる
scala.colletion.immutable.Setの使用例:
scala> val s = Set(1, 1, 3, 4, 5)
s: scala.collection.immutable.Set[Int] = Set(1, 3, 4, 5)
scala> s - 5 // 5を削除した後も
res0: scala.collection.immutable.Set[Int] = Set(1, 3, 4)
scala> s // 元のSetはそのまま
res1: scala.collection.immutable.Set[Int] = Set(1, 3, 4, 5)scala.colletion.mutable.Setの使用例:
scala> import scala.collection.mutable
import scala.collection.mutable
scala> val s = mutable.Set(1, 1, 3, 4, 5)
s: scala.collection.mutable.Set[Int] = Set(1, 5, 3, 4)
scala> s -= 5 // 5を削除したら
res0: s.type = Set(1, 3, 4)
scala> s // 変更が反映される
res1: scala.collection.mutable.Set[Int] = Set(1, 3, 4)