Scalaを使ってみる: (1) ファイルからの入力

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

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

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

scala.io.Source

ここでは,すべての処理を iterative に行うことを目指す.
なぜ iterative に処理をするかといえば,無駄なメモリ消費をしないためである.たとえば,入力ファイルの全体を一つのListにすると,入力ファイルサイズに比例するメモリ量を必要とするが, iterative に処理をすれば,必要に応じて要求駆動(on-demand)でファイルからデータを読み込まれ,不要となったデータはゴミ集め(garbage collection)で回収されることが期待できるため,無駄なメモリ消費を避けることが可能になる.
入力を iterative に処理するにはscala.io.Source を用いる.
以下のようにすると,入力ファイルの各文字に対するscala.collection.Iteratorが得られる.

  scala> io.Source.fromPath("hamlet.txt")
  scala.io.Source = non-empty iterator
  • 2010年11月29日追記: Scala 2.8.1 では io.Source.fromFile である.

文字毎のIteratorだと不便なので,getLinesを用いて行毎のIteratorにする.

  scala> io.Source.fromPath("hamlet.txt").getLines()
  Iterator[String] = non-empty iterator

sizeで行数がわかるし, take(n)で最初のn行を取り出して,toListでリストにできる.

  scala> io.Source.fromPath("hamlet.txt").getLines().size
  Int = 7067

  scala> io.Source.fromPath("hamlet.txt").getLines().take(2).toList
  List[String] = List(Project Gutenberg Etext of Hamlet by Shakespeare,
    PG has multiple editions of William Shakespeare's Complete Works)

filterとfilterNotメソッド

filterで述語を満たす行だけを抽出することができ, filterNotで述語を満たす行を取り除くことができる.
たとえば,filterNotの述語として「s => s.matches("\\s*")」あるいは「_.matches("\\s*")」を指定すれば,空行を取り除くことになる.

  scala> io.Source.fromPath("hamlet.txt").getLines().
       | filterNot(_.matches("\\s*")).size
  Int = 5537

以上をまとめて関数getLinesとして定義しておく.

  scala> def getLines(f: String) =
       | io.Source.fromPath(f).getLines().filterNot(_.matches("\\s*"))
  getLines: (f: String)Iterator[String]

  scala> getLines("hamlet.txt").take(2).toList
  List[String] = List(Project Gutenberg Etext of Hamlet by Shakespeare,
    PG has multiple editions of William Shakespeare's Complete Works)