Pythonのスペル修正プログラムをJavaに移植してみました

オレンジニュースで紹介されていたGoogleのPeter Norvig氏による"スペル修正プログラムはどう書くか"(原文)を読んで、ちょっと試してみたかったので深く考えずにJavaに移植しました。Pythonの文法を全く知らなかったのですが、元々とても短い上にコードだけでなく説明もあったので何とか最後まで到達。
という事で、メインのコードを貼り付けます*1。テストコードを含んだ完全版(?)はこちら。一応名前等はできるだけ合わせました。

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * originated from : http://norvig.com/spell.py
 */
public class SpellCorrect {
  static final class CountHolder {
    int count = 2;
  }
  
  private static Matcher words(String text) {
    Pattern re = Pattern.compile("[a-z]+");
    return re.matcher(text.toLowerCase());
  }
  
  private static Map<String, CountHolder> train(Matcher matcher) {
    Map<String, CountHolder> model = new HashMap<String, CountHolder>();
    while(matcher.find()) {
      CountHolder newCount = new CountHolder();
      CountHolder exists = model.put(matcher.group(), newCount);
      if(exists != null) {
        newCount.count = exists.count + 1;
      }
    }
    return model;
  }
  
  private static final Map<String, CountHolder> NWORDS =
      train(words(readFile("e:/dev/python/big.txt")));
  
  private static final char[] alphabet = "abcdefghijklmnopqrstuvwxyz".toCharArray();
  
  private static Set<String> edits1(String word) {
    int n = word.length();
    Set<String> set = new HashSet<String>();
    for(int i = 0; i < n; i++) {
      set.add(word.substring(0, i) + word.substring(i + 1));
    }
    for(int i = 0; i < n-1; i++) {
      set.add(word.substring(0, i) + word.charAt(i + 1) + word.charAt(i) + word.substring(i + 2));
    }
    for(int i = 0; i < n; i++) {
      for(char c : alphabet) {
        set.add(word.substring(0, i) + c + word.substring(i + 1));
      }
    }
    for(int i = 0; i < n+1; i++) {
      for(char c : alphabet) {
        set.add(word.substring(0, i) + c + word.substring(i));
      }
    }
    return set;
  }
  
  private static Set<String> known_edits2(String word) {
    Set<String> set = new HashSet<String>();
    for(String e1 : edits1(word)) {
      for(String e2 : edits1(e1)) {
        if(NWORDS.containsKey(e2)) {
          set.add(e2);
        }
      }
    }
    return set;
  }
  
  private static Set<String> known(Set<String> words) {
    Set<String> knownWords = new HashSet<String>();
    for(String w : words) {
      if(NWORDS.containsKey(w)) {
        knownWords.add(w);
      }
    }
    return knownWords;
  }
  
  public static String correct(String word) {
    Set<String> group;
    Set<String> input = new HashSet<String>(Arrays.asList(new String[] { word }));
    group = known(input);
    if(group.isEmpty()) {
      group = known(edits1(word));
      if(group.isEmpty()) {
        group = known_edits2(word);
        if(group.isEmpty()) {
          group = input;
        }
      }
    }
    return Collections.max(group, new Comparator<String>() {
      public int compare(String w1, String w2) {
        return NWORDS.get(w1).count - NWORDS.get(w2).count;
      }
    });
    
  }
  
  private static String readFile(String fileName) {
    BufferedInputStream in = null; 
    try {
      in = new BufferedInputStream(new FileInputStream(fileName));
      byte[] buf = new byte[in.available()];
      in.read(buf);
      return new String(buf, "ISO-8859-1");
    } catch (IOException e) {
      throw new IllegalStateException(e);
    } finally {
      if(in != null) { try { in.close(); } catch(Exception ex) {} }
    }
  }
}

ちょっと調べた感じ、巷にJavaのスペル修正のライブラリ等もあるようですが、ざっくりアルゴリズムを学ぶには良いと思います。間違いなど見つけられた方はコメントでご指摘頂けると幸いです。
ちなみに初めてPythonのコードを読んでみた感想としては、分かり易いのか分かり難いのか微妙なところでした。Javaと比べて直感的(Javaで書いてて「こう書けたらなー」と思うような事が実現できている)な部分もありつつ、「これパッと読むと間違うのでは?」的なところもありますので。何れにせよ、Rubyに入門中の身としてはなかなか面白い体験でした。
想像していたよりコーパスの処理も軽いようなので、monstar.fmでもアーティストや楽曲の検索で使いたいと思ってます。

追記

最初Rubyで書いてみようと思ったのですが、LL系は誰かすぐにやるだろうと思っていたら、やはりid:k12uさんがPerlで書かれていました。
それPerlで書けるよ(当たり前だ)
小飼さんはCPANのライブラリを使って更にAPI化されてます。
JavaでやるならDWRかな?2.0も出て、Reverse AjaxJavaScript Proxy APIGuice連携などかなり面白く楽に使えそうな機能が目白押しなので、それでなくとも試してみたいところ。

*1:追記:テストコードを一部含んだままだったので削除しました