Scalaを使ってみる: (4) Mapのメソッド

このエントリーをはてなブックマークに追加

Scalaを勉強している.勉強中の身ではあるが,以下を例題として,Scalaプログラムの作り方について説明してみる.

テキストファイル中に現れる英単語の出現回数を数えて,出現回数の多い語から表示する.
入力のテキストファイルとしては,Project Gutenberg 中のHalmet を用いる(ファイル名を hamlet.txt,改行はLFにした).
なお利用している環境は,Ubuntu 8.04 LTS上の Scala 2.8.0 RC2 (2010年5月10日リリース), Java 1.6.0である.

sizeメソッド

何種類の英単語があったかは,Map のキーを keys で取り出して, size で調べれば良い.

  scala> words.keys.size
  Int = 4906

単にMapに size を適用するのでも同じである.

  scala> words.size
  Int = 4906

filterメソッド

filterメソッドで一文字の英単語を数えてみると, 16種類あることがわかる.

  scala> words.keys.filter(w => w.length() == 1).size
  Int = 16

  scala> words.keys.filter(_.length() == 1).size
  Int = 16

なお,キーに対するfilterは filterKeys でも可能である.

  scala> words.filterKeys(_.length() == 1).size
  Int = 16

Map に直接 filter を適用する場合は,キーと値の対を引数とする関数を与える.以下は,1回だけ出現している英単語の種類を数えている.

  scala> words.filter(kv => kv._2 == 1).size
  Int = 2792

  scala> words.filter(_._2 == 1).size
  Int = 2792

ここで kv._2 は対の第2要素,すなわち出現回数を取り出している.
あるいは case のパターンマッチを用いた無名関数を利用しても良い.ここで case のまわりは ( ) ではなく { } でくくられていることに注意.

  scala> words.filter{case (w,c) => c == 1}.size
  Int = 2792

countメソッド

filter(f).size は count(f) と同一である.

  scala> words.count{case (w,c) => c == 1}
  Int = 2792

reduceRightとreduceLeftメソッド

要素が x1, x2, x3, ..., xn の時, reduceRight(f) は f(x1, f(x2, ..., f(xn-1, xn))) を求める.また reduceLeft(f) は f(...f(f(x1, x2), x3), ..., xn) を求める.
たとえば,List(1,2,3).reduceRight(_-_) は, 1-(2-3) = 2 である.

  scala> List(1,2,3).reduceRight(_-_)
  Int = 2

また,List(1,2,3).reduceLeft(_-_) は,(1-2)-3 = -4 である.

  scala> List(1,2,3).reduceLeft(_-_)
  Int = -4

したがって,英単語の総数を求めるには,以下のようにすれば良い.

  scala> words.values.reduceRight(_+_)
  Int = 34743

  scala> words.values.reduceLeft(_+_)
  Int = 34743

一文字の英単語の総出現回数は,以下のようにすればわかる.

  scala> words.filter(_._1.length == 1).values.reduceLeft(_+_)
  Int = 2062

直接的にsumメソッドを用いることもできる.

  scala> words.filter(_._1.length == 1).values.sum
  Int = 2062

以下のようにすれば,最長の英単語が15文字だとわかる.

  scala> words.map(_._1.length).reduceLeft(math.max(_,_))
  Int = 15

  scala> words.keys.filter(_.length == 15).toList
  List[String] = List(merchantability)

直接的にmaxメソッドを用いることもできる.

  scala> words.map(_._1.length).max
  Int = 15

foldRightとfoldLeftメソッド

要素が x1, x2, x3, ..., xn の時, foldRight(z)(f) は f(x1, f(x2, ..., f(xn, z))) を求める.また foldLeft(z)(f) は f(...f(f(z, x1), x2), ..., xn) を求める.

  scala> List(1,2,3).foldRight("z")("("+ _ + _ + ")")
  java.lang.String = (1(2(3z)))

  scala> List(1,2,3).foldLeft("z")("("+ _ + _ + ")")
  java.lang.String = (((z1)2)3)

foldRightとfoldLeftの処理は,それぞれ /: と :\ でも実現できる.

  scala> (List(1,2,3) :\ "z"){ "("+ _ + _ + ")" }
  java.lang.String = (1(2(3z)))

  scala> ("z" /: List(1,2,3)){ "("+ _ + _ + ")" }
  java.lang.String = (((z1)2)3)

以下は,toList と同様になる.

  scala> words.keys.foldRight[List[String]](Nil)(_ :: _)
  List[String] = List(concernings, tristful, ...)

  scala> (words.keys :\ List[String]()) { _ :: _ }
  List[String] = List(concernings, tristful, ...)

compose, andThen, orElseメソッド

Mapはscala.Function1(一変数関数)の一種である.したがって compose(g) によって関数を合成できる.
たとえば,以下は文字列を英小文字に変換してから wordsを適用する関数 f1 を定義している.

  scala> val f1 = words.compose[String](_.toLowerCase)
  f1: (String) => Int = <function1>

  scala> f1("Hamlet")
  Int = 118

一般に f = g compose h とすれば, f(x) = g(h(x)) となる関数fを定義できる.
一方 f = g andThen h とすれば, f(x) = h(g(x)) となる関数fを定義できる.

  scala> val f2 = words andThen (- _)
  f2: PartialFunction[String,Int] = <function1>
  
  scala> f2("hamlet")
  Int = -118

また,Mapはscala.PartialFunction(部分関数)の一種である. words.isDefinedAt(w) で w に対する値が定義されているかどうかを調べることができる(words.contains(w)と同様).

  scala> words.isDefinedAt("foo")
  Boolean = false

  scala> words.isDefinedAt("bar")
  Boolean = true

部分関数で値が定義されていない場合に,別の値を利用するには orElse を用いる.
以下は,words で定義されていない場合は, Map("foo" -> 1)を用いる部分関数 words2 を定義している.

  scala> val words2 = words orElse Map("foo" -> 1)
  words2: PartialFunction[String,Int] = <function1>

  scala> words2("foo")
  Int = 1

  scala> words2("bar")
  Int = 1