ScalaのコレクションAPIを徹底解説!List・Map・Setの使い方から、foldLeft・map・filterなどの便利メソッドまで、サンプルコード付きで詳しく解説します。

動作環境

Scala:2.12

コレクションライブラリ(immutable と mutable)

Scalaには、一度作成したら変更できない(immutable)なコレクションと変更できる(mutable)通常のコレクションがある。

immutableなコレクションを使うメリット

  1. 関数型プログラミングで多用する再起との相性が良い
  2. 高階関数を用いて簡潔なプログラムを書くことができる
  3. 一度作ったコレクションが知らない箇所で
  4. 並行に動作するプログラムの中で、安全に受け渡しすることができる

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を使うことはそれほど多くなく、ListVectorといったデータ構造を多用する。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つの要素もマッチしなかった場合は、NoneOption型として返す。

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 = 2

flatMap: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特有ではなく、RangeArrayなどの他のコレクションでも同様に使うことができる。その理由は、これらのメソッドによる操作の大半は、コレクションのスーパークラスである共通トレイト中に宣言されているからである。各メソッドの詳細は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)

続きはその3へ!

AIを仕事の仕組みに変えたい20代へ

AIを使うだけでなく、1業務をチームの仕組みへ

社会人1〜3年目が身近な1業務を選び、人とAIの役割を分け、7日で試し、成果を共有する5ステップをまとめました。

若手社員のAI業務改善5ステップを見る
カフェ・オンライン30分の日時を見る