読者です 読者をやめる 読者になる 読者になる

seratch's weblog in Japanese

About Scala, Java and Ruby programming in Japaense. If you need English information, go to http://blog.seratch.net/

Scala School意訳(More collections)

Scala Scala School

http://twitter.github.com/scala_school/coll2.html

以下は私の方でtypoや表示崩れを直したものです。

https://github.com/seratch/scala_school/blob/master/web/_posts/2011-05-08-lesson.textile

誤訳などありましたら、お手数ですが、ご指摘いただければ幸いです。

The basics

List
スタンダードな単方向リスト(Singly linked list)です。

scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

関数型言語であれば期待する通り、consでリストに付け加えることができます。

scala> 1 :: 2 :: 3 :: Nil
res1: List[Int] = List(1, 2, 3)
Seq
Seqは順序を持ったコレクションです。

scala> Seq(1, 1, 2)
res3: Seq[Int] = List(1, 1, 2)
Map
Mapはキーと値のコンテナです。

scala> Map('a' -> 1, 'b' -> 2)

res4: scala.collection.immutable.Map[Char,Int] = Map((a,1), (b,2))

The Hierarchy

これらは全てtraitです。mutableパッケージとimmutableパッケージは、これらのtraitに対してそれぞれ独自の実装を持っています。
Traversable
全てのコレクションはその要素を巡回することができます。このtraitは、もしあなたがforeachを実装すればforeachの単位として書かれたコンビネータを提供します。

(訳者追記)
これはforeachだけが「Abstract Value Members」にあることを言っています。

http://www.scala-lang.org/api/current/index.html#scala.collection.Traversable

Iterable
要素のイテレータを返すiterator()というメソッドを持っています。
Seq
順序を持った要素の並びです。
Set
重複した要素を持たないコレクションです。
Map
キーと値のペアの集まりです。

The methods

Traversable
以下にある全てのメソッドはずっと下まで有効です。引数や戻り値の型は常に同じではなく、サブクラスは自由にoverrideすることができます。

def head : A
def tail : Traversable[A]

ここに関数のコンビネータが定義されています。

def map [B] (f: (A) => B) : CC[B]

各要素を関数fで変換したコレクションを返します。

def foreach[U](f: Elem => U): Unit

各要素に対して関数fを実行してコレクションに変化を起こします。

def find (p: (A) => Boolean) : Option[A]

述語関数(predicate function)にマッチする最初の要素を返します。

def filter (p: (A) => Boolean) : Traversable[A]

述語関数にマッチする全ての要素を返します。


Partitioning:

def partition (p: (A) ⇒ Boolean) : (Traversable[A], Traversable[A])

コレクションを述語関数に基いて二つに分割します。

def groupBy [K] (f: (A) => K) : Map[K, Traversable[A]]


Conversion:

興味深いことに、あるコレクション型を別の型に変換することができます。

def toArray : Array[A]
def toArray [B >: A] (implicit arg0: ClassManifest[B]) : Array[B]
def toBuffer [B >: A] : Buffer[B]
def toIndexedSeq [B >: A] : IndexedSeq[B]
def toIterable : Iterable[A]
def toIterator : Iterator[A]
def toList : List[A]
def toMap [T, U] (implicit ev: <:<[A, (T, U)]) : Map[T, U]
def toSeq : Seq[A]
def toSet [B >: A] : Set[B]
def toStream : Stream[A]
def toString () : String
def toTraversable : Traversable[A]

MapをArrayに変換してみましょう。キーと値のペアの配列が得られます。

scala> Map(1 -> 2).toArray
res41: Array[(Int, Int)] = Array((1,2))
Iterable
イテレータへのアクセスが追加されます。

def iterator: Iterator[A]

イテレータによって何がもたらされるのでしょうか?

def hasNext(): Boolean
def next(): A

これは非常にJava風なインタフェースです。
きっとScalaではイテレータを使っているのを見かけることはあまりないでしょう。
おそらく関数のコンビネータやfor式を多く見ることになるでしょう。
Set
def contains(key: A): Boolean
def +(elem: A): Set[A]
def -(elem: A): Set[A]
Map
キーによる探索つきのキーと値のペアのシーケンスです。

このようにペアのリストをapply()メソッドに渡すか

scala> Map("a" -> 1, "b" -> 2)
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,2))

あるいはこのようにします。

scala> Map(("a", 2), ("b", 2))
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,2), (b,2))

余談ですが

「->」は一体何でしょう?これは特別な構文という分けではありません。タプル(ペア)を返すメソッドなのです。

scala> "a" -> 2
res0: (java.lang.String, Int) = (a,2)

忘れないでください。これは以下の記述のただのシンタックスシュガーです。

scala> "a".->(2)
res1: (java.lang.String, Int) = (a,2)

また「++」を使ってMapを構築する事もできます。

scala> Map.empty ++ List(("a", 1), ("b", 2), ("c", 3))
res0: scala.collection.immutable.Map[java.lang.String,Int] = Map((a,1), (b,2), (c,3))

Commonly used subclasses

HashSet and HashMap
高速な探索が可能で、これらのコレクションの最も一般的な形態です。
TreeMap
これはSortedMapのサブクラスです。順序づけられたアクセスが可能です。
Vector
ランダムアクセスと更新が高速です。

scala> IndexedSeq(1, 2, 3)
res0: IndexedSeq[Int] = Vector(1, 2, 3)

(訳者追記)
IndexedSeqのデフォルトの実装がVectorです。

Range
一定の間隔で順序付けられたInt型のシーケンスです。
これまではカウントつきのforループで使われていたような場所でこれをよく見かけるでしょう。

scala> for (i <- 1 to 10) { println(i) }
1
2
3
4
5
6
7
8
9
10

Rangeは普通の関数で使えるようなコンビネータを持っています。

scala> (1 to 10).map { i => i }
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Defaults

これらのtraitはそれぞれ(コンパニオンオブジェクトに定義された)applyメソッドを使うとデフォルトの実装でインスタンスを得ることができます。
例えば、Iterable(1,2)はデフォルトの実装としてList型を返します。

scala> Iterable(1, 2)
res0: Iterable[Int] = List(1, 2)

Seqでも同様です。

scala> Seq(1, 2)
res3: Seq[Int] = List(1, 2)

scala> Iterable(1, 2)
res1: Iterable[Int] = List(1, 2)

scala> Sequence(1, 2)
warning: there were deprecation warnings; re-run with -deprecation for details
res2: Seq[Int] = List(1, 2)

Setはこのようになります。

scala> Set(1, 2)
res31: scala.collection.immutable.Set[Int] = Set(1, 2)

(訳者追記)

一箇所warningでていますが「scala -deprecation」で起動してみると「Sequenceはもう使うな、Seqを使え」ということですね。

scala> Sequence(1,2)
<console>:8: warning: value Sequence is deprecated: use Seq instead
       Sequence(1,2)
       ^
res0: Seq[Int] = List(1, 2)

scala> Seq(1,2)
res1: Seq[Int] = List(1, 2)
IndexedSeq
要素へのランダムアクセスと長さの取得が高速です。
LinearSeq
headメソッドによる最初の要素へのアクセスが高速です。tailメソッドによる処理(最初の要素を除いた2番目以降の要素全件の取得)も高速です。
Mutable vs. Immutable
immutableに対する・・

賛成意見:マルチスレッドでの変更ができない

反対意見:一切変更できない

Scalaは私たちに実利的であることも許しています。
immutableを推奨していますが、mutableなものを必要とすることを罰したりはしません。
これは「var」対「val」と似たような話です。
私たちは常にvalから始めてもし必要になったらvarに戻すのです。

私たちはimmutableなバージョンのコレクションから始めることを好みます。もしそれがパフォーマンスに影響するならmutableに変更しますが。
immutableなコレクションを使うということは、マルチスレッドでの意図しない共有オブジェクトの変更が起きないということを意味します。

Mutable

これまで話に出てきた全てのクラスはimmutableでした。
では、一般的によく使われるmutableなコレクションについて話しましょう。
HashMap
getOrElseUpdate、+=といったメソッドがあります。

scala> val numbers = collection.mutable.Map(1 -> 2)
numbers: scala.collection.mutable.Map[Int,Int] = Map((1,2))

scala> numbers.get(1)
res0: Option[Int] = Some(2)

scala> numbers.getOrElseUpdate(2, 3)
res54: Int = 3

scala> numbers
res55: scala.collection.mutable.Map[Int,Int] = Map((2,3), (1,2))

scala> numbers += (4 -> 1)
res56: numbers.type = Map((2,3), (4,1), (1,2))
ListBuffer and ArrayBuffer

(訳者追記)
見出しだけで特に何も書いていなかったので少し利用例を貼り付けてみました。

http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.ListBuffer

scala> import collection.mutable.ListBuffer
import collection.mutable.ListBuffer

scala> val lbuf = new ListBuffer[String]
lbuf: scala.collection.mutable.ListBuffer[String] = ListBuffer()

scala> lbuf.append("a","b","c")

scala> lbuf
res1: scala.collection.mutable.ListBuffer[String] = ListBuffer(a, b, c)

scala> lbuf.appendAll(List("d","e","f"))

scala> lbuf
res4: scala.collection.mutable.ListBuffer[String] = ListBuffer(a, b, c, d, e, f)

scala> val newOne: ListBuffer[String] = lbuf ++ List("g")
newOne: scala.collection.mutable.ListBuffer[String] = ListBuffer(a, b, c, d, e, f, g)

scala> lbuf += "g"
res7: lbuf.type = ListBuffer(a, b, c, d, e, f, g)

scala> lbuf ++= List("h")
res9: lbuf.type = ListBuffer(a, b, c, d, e, f, g, h)

scala> lbuf -= "h"
res10: lbuf.type = ListBuffer(a, b, c, d, e, f, g)

scala> lbuf --= List("f","g")
res11: lbuf.type = ListBuffer(a, b, c, d, e)

scala> lbuf --= List("f","g")
res12: lbuf.type = ListBuffer(a, b, c, d, e)

scala> lbuf foreach println
a
b
c
d
e

scala> lbuf filter ( _ != "b" )
res14: scala.collection.mutable.ListBuffer[String] = ListBuffer(a, c, d, e)

ArrayBufferとの違いを確認してみましょう。

http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.ArrayBuffer

LinkedList and DoublyLinkedList

(訳者追記)
見出しだけで特に何も書いていなかったので少し利用例を貼り付けてみました。

http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.LinkedList
http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.DoubleLinkedList

前者がSingly linked listで後者がDoubly linked listの実装になっています。

import collection.mutable.LinkedList
val l = LinkedList(1,2,3)
l head // 1
l tail // LinkedList(2, 3)
l foreach print(e => print(e + " "))
// 1 2 3
val ll = l ++ List(1,2,3)
val ll = l ++ LinkedList(1,2,3)
l foreach print(e => print(e + " "))
// 1 2 3
ll foreach print(e => print(e + " "))
// 1 2 3 4 5 6

import collection.mutable.DoubleLinkedList
val l = DoubleLinkedList(1,2,3)
val ll = l ++ List(4,5,6)
l head // 1
l tail // DoubleLinkedList(2, 3)
l foreach print(e => print(e + " "))
// 1 2 3
ll foreach print(e => print(e + " "))
// 1 2 3 4 5 6
PriorityQueue

(訳者追記)
見出しだけで特に何も書いていなかったので少し利用例を貼り付けてみました。

http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.PriorityQueue

import collection.mutable.PriorityQueue
val pq = PriorityQueue[Int]()
pq.enqueue(1)
pq.enqueue(5)
pq.enqueue(2)
pq.enqueue(3)
pq.enqueue(4)
pq.enqueue(2)
pq.enqueue(2)
pq.enqueue(3)
pq.enqueue(3)

scala> pq
res73: scala.collection.mutable.PriorityQueue[Int] = PriorityQueue(5, 4, 2, 3, 3, 2, 2, 1, 3)

pq.dequeue // 5
pq.dequeue // 4
pq.dequeue // 3
pq.dequeue // 3
pq.dequeue // 3
pq.dequeue // 2
pq.dequeue // 2
pq.dequeue // 2
pq.dequeue // 1
pq.isEmpty // true
pq.dequeue // java.util.NoSuchElementException:

PriorityQueueのコンストラクタは暗黙のパラメータでOrdering[T]を受け取っているので、差し替えて優先度のつけ方を変えることができます。

val pq = new PriorityQueue[Int]()(new Ordering[Int] { def compare(x: Int, y: Int) = if(x < y) 1 else -1 })
pq.enqueue(2)
pq.enqueue(1)
pq.enqueue(3)
pq.dequeue // 1
pq.dequeue // 2
pq.dequeue // 3
Stack and ArrayStack

(訳者追記)
見出しだけで特に何も書いていなかったので少し利用例を貼り付けてみました。

http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.Stack
http://www.scala-lang.org/api/current/index.html#scala.collection.mutable.ArrayStack

import collection.mutable.Stack
val stack = new Stack[Int]

stack.push(1)
stack.push(2)
stack.push(3)

stack.pop // 3
stack.pop // 2
stack.pop // 1
stack.isEmpty // true
stack.pop // java.util.NoSuchElementException: head of empty list
import collection.mutable.ArrayStack
val stack = new ArrayStack[Int]
StringBuilder
興味深いことにStringBUilderもコレクションなのです。

(訳者追記)

scala> val sb = new collection.mutable.StringBuilder
sb: StringBuilder =

scala> sb.append("sss").append("ttt")
res15: StringBuilder = sssttt

scala> sb foreach println
s
s
s
t
t
t

Life with Java

JavaConversionsパッケージに定義されている暗黙の型変換を使えば、簡単にJavaとScalaのコレクション型を行き来することができます。

import scala.collection.JavaConversions._
val sl = new scala.collection.mutable.ListBuffer[Int]
val jl : java.util.List[Int] = sl
val sl2 : scala.collection.mutable.Buffer[Int] = jl
assert(sl eq sl2)

双方向の変換があります。

scala.collection.Iterable <=> java.lang.Iterable
scala.collection.Iterable <=> java.util.Collection
scala.collection.Iterator <=> java.util.{ Iterator, Enumeration }
scala.collection.mutable.Buffer <=> java.util.List
scala.collection.mutable.Set <=> java.util.Set
scala.collection.mutable.Map <=> java.util.{ Map, Dictionary }
scala.collection.mutable.ConcurrentMap <=> java.util.concurrent.ConcurrentMap

さらに、以下のような一方向な変換も提供されています。

scala.collection.Seq => java.util.List
scala.collection.mutable.Seq => java.util.List
scala.collection.Set => java.util.Set
scala.collection.Map => java.util.Map