Scalaを使ってみる: (6) MultiSetを定義する (mutable版)
Scalaを勉強している.勉強中の身ではあるが,以下を例題として,Scalaプログラムの作り方について説明してみる.
テキストファイル中に現れる英単語の出現回数を数えて,出現回数の多い語から表示する.入力のテキストファイルとしては,Project Gutenberg 中のHalmet を用いる(ファイル名を hamlet.txt,改行はLFにした).
なお利用している環境は,Ubuntu 8.04 LTS上の Scala 2.8.0 RC2 (2010年5月10日リリース), Java 1.6.0である.
マルチ集合 (MultiSet)
英単語の出現回数を数えている words: Map[String,Int] は,数学的にはマルチ集合(multiset)とみなせる.
通常の集合(set)は,要素の重複した出現を許さないが,マルチ集合では要素が重複して出現することを許す.
ここでは,mutableなマルチ集合である MMS[A] を定義してみる.
以下のように動作するのが目標である.
case class
MMS[A] の実装にはscala.collection.mutable.Mapを用いるのが良さそうだ.
以下のように MMS[A] を case class として定義したファイル MMS.scala を作成する.
import scala.collection.mutable.Map case class MMS[A](private val amap: Map[A,Int])
case classとして定義すれば,new なしでオブジェクトを生成でき, equals, hashCode, toString などが自動で定義されるので便利だ.
変数 amap に対する private val の宣言は, amap が外部からアクセスできないこと,また amap に対して代入を行わないことを表す.
以下のようにして実行してみる.
scala> :load MMS.scala scala> MMS(Map("a"->1, "b"->2)) MMS[java.lang.String] = MMS(Map(a -> 1, b -> 2))
companion object
このままだと MMS オブジェクトの生成には,必ず Map を与えなければならない.
MMS がマルチ集合であることを考えれば, MMS("a", "b", "b") のように要素を並べてオブジェクトを生成したい.
そのために companion object および apply メソッドを定義する.
import scala.collection.mutable.Map case class MMS[A](private val amap: Map[A,Int]) object MMS { def apply[A](elems: A*) = new MMS(Map[A,Int](elems.map((_,1)): _*)) }
MMS("a", "b", "b") は MMS オブジェクトに対する apply メソッドの適用としてコンパイルされる.
引数の列は,scala.collection.Seq の一種であるscala.collection.mutalbe.WrappedArrayとして elems に束縛される. Seq を引数の列(sequence argument)として処理するには,型注釈(type annotation)として _* を指定する.
scala> def f(xs: Int*) = println(xs) f: (xs: Int*)Unit scala> f(1,2,3) WrappedArray(1, 2, 3) scala> f(List(1,2,3): _*) List(1, 2, 3)
ただ上のように定義すると MMS(Map("a"->1)) などは, Map("a"->1) 自体が要素として取り扱われるので, new MMS(Map("a"->1)) としなければならない.
apply と + の定義
要素数を返す apply と要素を追加する += を定義してみる.また,ついでに元の Map を返す toMap を追加する.
import scala.collection.mutable.Map case class MMS[A](private val amap: Map[A,Int]) { def apply(elem: A) = amap.getOrElse(elem, 0) def += (elem: A) = { amap += elem -> (this(elem)+1); this } def toMap = amap } object MMS { def apply[A](elems: A*) = new MMS(Map[A,Int](elems.map((_,1)): _*)) def apply[A](amap: Map[A,Int]) = new MMS(amap) }
実行してみよう.
scala> val mms = MMS[String]() mms: MMS[String] = MMS(Map()) scala> mms += "a" MMS[String] = MMS(Map(a -> 1)) scala> mms += "a" MMS[String] = MMS(Map(a -> 2)) scala> mms("a") Int = 2
動いているようだ.
こうなると,色々定義してみたくなるが,それらは immutable 版で実現することにしよう.
「Scalaを使ってみる」の目次
- (1) ファイルからの入力
- (2) 英単語の抽出
- (3) 出現回数を数える (mutable版)
- (4) Mapのメソッド
- (5) プログラム作成 (mutable版)
- (6) MultiSetを定義する (mutable版)
- (7) immutable MultiSetを定義する
- (8) immutable MultiSetのメソッドを定義する
- (9) immutable MultiSetのメソッドを高速化する
- (10) immutable MultiSetのメソッドを高速化する (続き)
- (11) immutable MultiSetのメソッドをリファクタリング
- (12) Martin Oderskyによるオンライン授業
- (13) Martin Oderskyによるオンライン授業 (第2回)