2011年11月9日水曜日

Scalaで文字列中の各出現文字をカウントする

Mapを使ったソースを書いてみようと思ってネタを考えてウダウダした結果、タイトルのようなソースが出来上がりました。
ついでなので関数の部分適用なども使ってみました。

下記サンプルソースに盛り込まれている要素は、
  • Map(mutable)
  • 関数の部分適用
  • 関数のオーバロード
になります。

では早速サンプルソースです。


object CharacterCount {     // scala.collection.mutableをインポートしておけば、mutable.Mapの宣言が短くできる     // インポート無 → cala.collection.mutable.Map[Char, Int]()     // インポート有 → mutableMap[Char, Int]()     import scala.collection.mutable
    /**      * 指定された文字列中に出現する各文字をカウントし、Mapに格納する      * @param text 出現する文字をカウントしたい文字列      * @param limit 出現回数がlimit回以上の文字のみ返す      * @return text中、limit回以上出現した文字と出現回数を格納したMap      */     def getMapWithLimit(text:String, limit:Int):mutable.Map[Char,Int] = {         count(text).filter( (kv) => kv._2 >= limit )     }     /**      * getMapWithLimitの部分適用      * getMapWithLimit(text, 1)と指定したのと同じ      * @param text 出現する文字をカウントしたい文字列      * @return text中、1回以上出現した文字と出現回数を格納したMap      */     def getMap = getMapWithLimit(_:String, 1)     /**      * 部分適用したgetMapをオーバロードしたもの      * getMapWithLimitと全く同じ      * @param text 出現する文字をカウントしたい文字列      * @param limit 出現回数がlimit回以上の文字のみ返す      * @return text中、limit回以上出現した文字と出現回数を格納したMap      */     def getMap(text:String, limit:Int):mutable.Map[Char,Int] = {         getMapWithLimit(text, limit)     }     /**      * 指定された文字列に出現する各文字をカウントし、Mapに格納して返す      * @param text 出現する文字をカウントしたい文字列      * @return text中の各文字と出現回数を格納したMap      */     private def count(text:String):mutable.Map[Char, Int] = {         val map = mutable.Map[Char, Int]()         text.foreach ( (char) => {             // MapのgetOrElseは、第1引数に指定したキーが存在しない場合に、第2引数の値を返します             val count = map.getOrElse( char, 0 )             map.update( char, count+1 )         })         map     }     // 関数の部分適用部分は、以下のようにしても結果は同じ     //def getMap(text:String) = { getMapWithLimit(text, 1) } }

getMap関数(メソッド?)を使うと、文字列中の各文字をキーとし、値に出現回数が格納されたmutable.Mapのインスタンスが返されます。
REPLから試すと以下のとおりです。


scala> val text = "となりのきゃくはよくかきくうきゃくだ。"
text: java.lang.String = となりのきゃくはよくかきくうきゃくだ。

// CharacterCountって打つのが面倒くさいのでccという変数に格納
scala> val cc = CharacterCount
cc: CharacterCount.type = CharacterCount$@2e5facbd

// 出現回数か1回以上の文字を取得(つまり全文字)
scala> cc.getMap(text)
res0: scala.collection.mutable.Map[Char,Int] = Map(の -> 1, か -> 1, う -> 1, く -> 4, ゃ -> 2, と -> 1, き -> 3, な -> 1, は -> 1, よ -> 1, 。 -> 1, り -> 1, だ -> 1)

// 出現回数が3回以上の文字のみ取得
scala> cc.getMap(text, 3)
res1: scala.collection.mutable.Map[Char,Int] = Map(く -> 4, き -> 3)


map.getOrElseに関して、これがScalaの目玉機能の一つであるOption型(Some型とNone型がある)に直結する機能です。JavaであればExceptionが発生しそうなところ、Noneが返されるので結果的に処理が無視される or 指定した処理が実行されることになります。 このOptionに関してはまだ勉強中なのでまた今度サンプルソースを書いてみたいと思っています。

これぐらいのプログラムであれば態々戻り値の型を書かなくても把握できるしScalaが理解できているならコメントもいらないくらいだと思います。(私は理解できていないのでコメントを書きました)
コメントと型の記述を省いて型推論に頼るとこんなにスッキリしました。


object CharacterCount2 {

    def getMapWithLimit(text:String, limit:Int) = {
        count(text).filter( (kv) => kv._2 >= limit )
    }

    def getMap = getMapWithLimit(_:String, 1)

    def getMap(text:String, limit:Int) = {
        getMapWithLimit(text, limit)
    }

    private def count(text:String) = {
        val map = scala.collection.mutable.Map[Char, Int]()
        text.foreach ( (char) => {
            map.update( char, map.getOrElse(char,0)+1 )
        })
        map
    }
}


今回は各文字でカウントアップしましたが、ちょっと工夫すれば単語単位でカウントすることも可能だと思います。
英語やドイツ語であればスペースで文章を区切ってしまえば自動的に単語単位になるので、うまいこと考えれば今年生まれた新語一覧なんかも作れるのかな~と思ったりしてます。

0 件のコメント: