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 版で実現することにしよう.