S-JIS[2011-03-20/2011-04-02] 変更履歴

Scala List

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のクラス

List関連のクラス。

  パッケージ名 クラス名
(オブジェクト)
備考 参考
不変 scala.collection.immutable
scala
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
"A" :: List("B", "C")
"A" :: "B" :: "C" :: Nil
List(A)
List(A, B, C)
List(A, B, C)
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.iterate(1, 5)(_+2)
List(2, 4, 8, 16, 32, 64, 128, 256)
List(1, 3, 5, 7, 9)
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のメソッド

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", "B", "C", "B").filterNot(_ == "B")
List("A", "B", "C", "B").filterNot("B" ==)
List(A, C)
List[A] -- [B] that: List[B] List[B] filterNot List("A", "B", "C", "D", "B") -- List("B", "C")
List("A", "B", "C", "D", "B").filterNot(List("B", "C") contains)
List(A, D)
要素の追加・更新・削除(可変コレクションのみ)
ListBuffer[A] update   n: Int
x: A
Unit 添字nの要素をxに置き換える。
内部でListを作り直しているので、実行効率は良くない。
val lb = ListBuffer("A", "B", "C")
lb.update(1, "Z")
ListBuffer(A, Z, C)
ListBuffer[A] +=   x: A 自分 末尾にデータを追加する。
ListBufferは末尾の位置を保持しているので、効率は悪くない。
val lb = ListBuffer("A", "B", "C")
lb += "D"
ListBuffer(A, B, C, D)
ListBuffer[A] +=:   x: A 自分 先頭にデータを追加する。 val lb = ListBuffer("A", "B", "C")
"Z" +=: lb
ListBuffer(Z, A, B, C)
ListBuffer[A] prependToList   xs: List[A] List[A] ListBufferが空のときxsを返す。(自分は空のまま)
ListBufferが空以外のとき、末尾にxsを追加し、そのListを返す。
(自分も結合したリストを保持する)
val lb = ListBuffer("A", "B", "C")
lb.prependToList(List("D"))
List(A, B, C, D)
ListBuffer[A] remove   n: Int A 添字nの要素を削除し、その要素を返す。
実行時間は要素数に比例する。
val lb = ListBuffer("A", "B", "C")
lb.remove(1)
lb
B
ListBuffer(A, C)
全要素の順次処理
List[A] mapConserve [B] f: (A)=>A List[A] mapと同様に変換を行うが、
全ての要素に変更が無い場合は元のリストを返す。
val l1 = List("A", "B", "C")
val l2 = l1.mapConserve(n => n)
val l3 = l1.map(n => n)
println(l1 eq l2, l1 eq l3)
(true,false)
別のコレクションへの変換
ListBuffer[A] result     List[A] Listを返す。 ListBuffer(1,2,3).result List(1, 2, 3)

head・tailの実装

Listは先頭要素次のデータ(残りのList)を保持しているので、それを取り出す操作(headtail)はすごく速い。

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は先頭に追加するのも末尾に追加するのも同じ速度(速度の違いは誤差の範囲)。


Seqへ戻る / コレクションへ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま