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 AjaxやJavaScript Proxy API、Guice連携などかなり面白く楽に使えそうな機能が目白押しなので、それでなくとも試してみたいところ。
*1:追記:テストコードを一部含んだままだったので削除しました