S-JIS[2011-03-20/2011-04-02] 変更履歴
ScalaのListはJavaのListとはかなり異なる。
JavaではListは代表的なコレクションのひとつだが、ScalaではSeqが代表であり、ListはSeqの一種。
ScalaのListはJavaのListとは異なり、単方向(片方向)のリスト(かなり原始的でその分シンプルなリスト)である。
継承関係は、Seq←LinearSeq←List。
JavaのListはインターフェースであり、具象クラスでよく使われるのはArrayListやLinkedList。
JavaのListでデータを追加するメソッドはadd()であり、データはリストの末尾に追加される。
ArrayListは内部で配列を使ってデータを保持しており、配列サイズを超えるデータが追加される場合には拡張される。
LinkedListは双方向リストであり、末尾データへの“ポインター(参照)”を保持しているので、末尾へ簡単にアクセスできる。
ScalaのList(不変List)の構造は単純なリスト(単方向リスト)であり、データと“次のデータへのポインター”しか保持していない。
“次のデータへのポインター”しか保持していないので、末尾データへアクセスするにはリスト内の全データを辿る必要があり、効率が悪い。
逆に先頭データへはすぐアクセスできるので、データを追加する場合はリストの先頭へ追加するのが効率が良い。
(データ数を取得するにも全データを辿る必要がある。JavaのArrayListやLinkedListはデータ数を別途保持している)
また、再帰呼び出しと非常に相性が良い。
(色々なコレクションに対して再帰呼び出しを行うと、たいていListが一番速い)
List関連のクラス。
パッケージ名 | クラス名 (オブジェクト) |
備考 | 参考 | |
---|---|---|---|---|
不変 | scala.collection.immutable |
List[A] |
不変な単方向リスト。 | リスト |
::[A] |
Listは抽象クラスであり、その具象クラスが「:: 」。 |
|||
Nil |
空(要素数が0)を表すリスト。 | |||
scala.collection.immutable |
ListMap[K,V] |
内部がリスト構造をしているMap。[2011-04-02] | リストマップ | |
scala.collection.immutable |
ListSet[A] |
内部がリスト構造をしているSet。[2011-04-02] | ||
可変 | scala.collection.mutable |
LinkedList[A] |
可変な単方向リスト。 | 線形リスト |
scala.collection.mutable |
DoubleLinkedList[A] |
可変な双方向リスト。 | 二重線形リスト | |
scala.collection.mutable |
ListBuffer[A] |
内部データをリストで管理しているBuffer。 | リストバッファ | |
scala.collection.mutable |
MutableList[A] |
StackやQueueの実装に使われているList。 内部でLinkedListとその末尾へのポインターを持っている。 |
可変リスト | |
scala.collection.mutable |
ListMap[K,V] |
内部構造にListを使用しているMap。[/2011-04-02] |
Listのインスタンスを作る(初期化する)方法。
クラス オブジェクト |
メソッド | 備考 | 例 | |
---|---|---|---|---|
コーディング | 結果 | |||
List | empty | 空のListを返す。 | List.empty[Int] |
List[Int]() |
Nil | 空のListを表す。 | Nil |
List[Nothing]() |
|
List | apply | 初期値(要素)を指定する。 | List("A","B","C") |
List(A, B, C) |
List | :: |
要素とリストを結合する。 | "A" :: Nil |
List(A) |
List | concat | 他のコレクションからListを生成する。 | List.concat(Array("A","B"), Seq("C","D")) |
List(A, B, C, D) |
List | fill | 同じ値で埋める。 | List.fill(5)("A") |
List(A, A, A, A, A) |
List | tabulate | 添字から要素を計算する。 | List.tabulate(5)(i => i*i) |
List(0, 1, 4, 9, 16) |
List | iterate | 前の要素の値を元に要素を計算する。 初期値sに対し、s, f(s), f(f(s))…となる。 |
List.iterate(2, 8)(n => n*2) |
List(2, 4, 8, 16, 32, 64, 128, 256) |
List | range | 範囲(開始・終了・増分)を指定する。 | List.range(10, 15+1) |
List(10, 11, 12, 13, 14, 15) |
List.range(1, 10+1, 2) |
List(1, 3, 5, 7, 9) |
List関連のメソッド。
(→Seq・Iterable・Traversableのメソッド)
クラス | メソッド | 備考 | 例 | ||||
---|---|---|---|---|---|---|---|
名称 | 型 | 引数 | 戻り型 | 演算 | 結果 | ||
要素の追加・更新 | |||||||
List[A] | :: |
x: A |
List[A] |
先頭に要素を追加したリストを返す。 (+ : と同機能) |
9 :: List(1,2,3) |
List(9, 1, 2, 3) |
|
List[A] | ::: |
prefix: List[A] |
List[A] |
他のリストと結合したリストを返す。 (++と同機能) |
List(1,2,3) ::: List(4,5,6) |
List(1, 2, 3, 4, 5, 6) |
|
List[A] | reverse_::: |
prefix: List[A] |
List[A] |
prefixを逆順にしてリストの先頭に追加する。 | List(1,2,3) reverse_::: List(4,5,6) |
List(3, 2, 1, 4, 5, 6) |
|
List[A] | - |
[B] |
elem: B |
List[B] |
→filterNot | List("A", "B", "C", "B") - "B" |
List(A, C) |
List[A] | -- |
[B] |
that: List[B] |
List[B] |
→filterNot | List("A", "B", "C", "D", "B") -- List("B", "C") |
List(A, D) |
要素の追加・更新・削除(可変コレクションのみ) | |||||||
ListBuffer[A] | update |
n: Int |
Unit |
添字nの要素をxに置き換える。 内部でListを作り直しているので、実行効率は良くない。 |
val lb = ListBuffer("A", "B", "C") |
ListBuffer(A, Z, C) |
|
ListBuffer[A] | += |
x: A |
自分 |
末尾にデータを追加する。 ListBufferは末尾の位置を保持しているので、効率は悪くない。 |
val lb = ListBuffer("A", "B", "C") |
ListBuffer(A, B, C, D) |
|
ListBuffer[A] |
+=: |
x: A |
自分 |
先頭にデータを追加する。 | val lb = ListBuffer("A", "B", "C") |
ListBuffer(Z, A, B, C) |
|
ListBuffer[A] | prependToList |
xs: List[A] |
List[A] |
ListBufferが空のときxsを返す。(自分は空のまま) ListBufferが空以外のとき、末尾にxsを追加し、そのListを返す。 (自分も結合したリストを保持する) |
val lb = ListBuffer("A", "B", "C") |
List(A, B, C, D) |
|
ListBuffer[A] | remove |
n: Int |
A |
添字nの要素を削除し、その要素を返す。 実行時間は要素数に比例する。 |
val lb = ListBuffer("A", "B", "C") |
B |
|
全要素の順次処理 | |||||||
List[A] | mapConserve |
[B] |
f: (A)=>A |
List[A] |
mapと同様に変換を行うが、 全ての要素に変更が無い場合は元のリストを返す。 |
val l1 = List("A", "B", "C") |
(true,false) |
別のコレクションへの変換 | |||||||
ListBuffer[A] | result |
List[A] |
Listを返す。 | ListBuffer(1,2,3).result |
List(1, 2, 3) |
Listは先頭要素と次のデータ(残りのList)を保持しているので、それを取り出す操作(head・tail)はすごく速い。
sealed abstract class List[+A] extends LinearSeq[A] 〜 { def isEmpty: Boolean def head: A def tail: List[A] 〜 }
final case class ::[B](private var hd: B, private[scala] var tl: List[B]) extends List[B] { override def head : B = hd override def tail : List[B] = tl override def isEmpty: Boolean = false 〜 }
したがって、関数型プログラミングの例でよく出てくる再帰関数にhead・tailを使用するのは、Listではとても相性が良い。
@scala.annotation.tailrec def sum(s: Int, list: List[Int]): Int = { if (list.isEmpty) { s } else { sum(s + list.head, list.tail) } }
→他のSeq具象クラスのhead・tailの実行時間
→sumの例
Listはデータを先頭に追加するのが速いが、来た順にデータを追加していくと、出来たListは逆順になってしまう。そこでreverseを使えば来た順になる。
var list = List.empty[Int] for(i <- 1 to N){ list = i :: list } list.reverse
あるいは、ListBufferやMutableListで末尾に追加していってtoListでListを取得することも出来る。[/2011-03-21]
val buffer = scala.collection.mutable.ListBuffer[Int]() for(i <- 1 to N){ buffer += i } buffer.toList
val mlist = new scala.collection.mutable.MutableList[Int] for(i <- 1 to N){ mlist += i } mlist.toList
val N = 10 benchmark(10, 10*10000) |
val N = 100 benchmark(10, 10000) |
val N = 1000 benchmark(10, 1000) |
|||
MutableList | 末尾に追加 | 正順 | 416 | 424 | 319 |
List | 先頭に追加してreverse | 正順 | 203 | 187 | 172 |
ListBuffer | 末尾に追加 | 正順 | 176 | 131 | 147 |
ListBuffer | 先頭に追加 | 逆順 | 176 | 127 | 147 |
List | 先頭に追加 | 逆順 | 113 | 94 | 111 |
どうやら、来た順に並べたいならListBufferを使う方が多少効率が良いようだ。
逆順(あるいは順序にこだわらない)でいいならListの先頭に追加していく方が速い。
ListBufferは先頭に追加するのも末尾に追加するのも同じ速度(速度の違いは誤差の範囲)。