辞書を片手に~PatternResponderの作成(7) | Pythonでなんか作ってみる

辞書を片手に~PatternResponderの作成(7)

この章も随分長くなってきた。そろそろ何がやりたいのか分からなくなってきたので、スピードを上げよう。


ToDoリスト

・正規表現を使ってパターンに反応するResponder(以下の仕様を満たす)

・パターン辞書の先頭行からパターンマッチを行い、マッチした行の応答例をもとに応答メッセージを作る

・1つのパターンに対して応答例は「|」で区切って複数設定でき、いずれかがランダムに選択される

・マッチするパターンがなかったときは、ランダム辞書からランダムに選択した応答を返す

・応答例の中に「%match」という文字列があれば、パターンにマッチした文字列と置き換えられる

・パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める

・PatternResponderがRandomResponderの機能を利用している(メソッドの上位クラスへの移動に関するリファクタリングが出来そうである)

・パターン辞書に応答例が無い場合のイリーガルケース


まずは、「パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める」を行う。


今更ながら、PatternResponderがResponderを継承していないことに気づいたので、継承させる。

その上で、response()メソッドを使えばいいだろう。


public void testResponse() throws FileNotFoundException {
PatternResponder resp = new PatternResponder("pattern", new FileReader("dics/pattern.txt"));
assertEquals("さむくないよ",resp.response("今日はさむいね"));
assertEquals("pattern",resp.name());
}

PatternResponderのコンストラクタに名前を指定するように変更している。

テスト実行する。予定通りRed(テスト失敗)。

response()をオーバーライドしよう。


/**
* パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める
*
* @param string 問い掛けのメッセージ
* @return 対応する応答
*/
public String response(String msg) {
for (int index=0;index < patterns.length; index++) {
Matcher m = patterns[index].matcher(msg);
if (m.find()) {
return responses[index];
}
}
return "";
}

中身の実装は、「辞書を片手に~人工無能のための正規表現(3)」のmatch()メソッドから持ってきた。

テスト実行-Green。この実装だとマッチするパターンが無い場合は空文字列を返す。

これだと「マッチするパターンがなかったときは、ランダム辞書からランダムに選択した応答を返す」に反しているわけだ。次はこれに取り掛かるべきだろう。


しかし、ランダムはテストするのが難しい。毎回結果が変わるというのがランダムであると定義すると、適切な出力結果と比較するのが不可能なためだ。

そろそろランダムを(元々擬似ランダムだというのはおいといたとしても)擬似る仕組みを入れた方がいいだろう。


その前に細かいToDoを片付けてしまおう。

まず、「PatternResponderがRandomResponderの機能を利用している」はloadDictionary()をResponderに移動させてしまう。その際。PatternResponder.loadDictionary()が被ってしまっているので、loadDictionaryPattern()にリネームしておく。


次に、「パターン辞書に応答例が無い場合のイリーガルケース」だが、この場合は、応答例として一つの空文字列が登録されていると考えるのがいいだろう。

そのテストを追加する。random.txtの形式を読み込ませることで、タブ無し状態をテストする。


public void testLoadDictionaryResponseIlliegal() throws FileNotFoundException {
PatternResponder responder = new PatternResponder("pattern", new FileReader("dics/random.txt"));

assertEquals(new String[]{"","",""},
responder
.loadDictionaryResponse(new FileReader("dics/pattern.txt")));
}

ここで気づいたが、loadDictionaryPattern()とloadDictionaryResponse()の引数はもう使用していないな。この後削除することにして、テストを実行する。

Red。以下でjava.lang.ArrayIndexOutOfBoundsException: 1が発生した。


responses[index] = patternAndResponses[1];

tabが無いから、patternAndResponsed[1]も当然存在しないわけだ。ここは単純にlengthを見るだけでいいだろう。以下のように修正する。


if (patternAndResponses.length > 1) {
responses[index] = patternAndResponses[1];
}else{
responses[index] = "";
}

tabの後に、文字列が無い場合はどうなるんだろう?不安に思ったのでテストを追加する。


public void testLoadDictionaryResponseIlliegal2() throws FileNotFoundException {
PatternResponder responder = new PatternResponder("pattern", new StringReader("test1\ttest1\ntest2\t\ntest3\ttest3"));

assertEquals(new String[]{"test1","","test3"},
responder
.loadDictionaryResponse(new FileReader("dics/pattern.txt")));
}

いちいちファイルを作るほどのことは無いかと思ったので、StringReaderで代用させる。PatternResponderのコンストラクタが引数にFileReaderを指定しているので、Readerに変更する。これは、Responder.loadDictionary()にそのまま渡している引数であるので、そっちも変更する。

また、ついでにloadDictionaryPattern()とloadDictoinaryResponse()の引数も削除。


テストを実行する。Green。今の実装で大丈夫。


この時点でのToDoリストは以下である。

・正規表現を使ってパターンに反応するResponder(以下の仕様を満たす)

・パターン辞書の先頭行からパターンマッチを行い、マッチした行の応答例をもとに応答メッセージを作る

・1つのパターンに対して応答例は「|」で区切って複数設定でき、いずれかがランダムに選択される

・マッチするパターンがなかったときは、ランダム辞書からランダムに選択した応答を返す

・応答例の中に「%match」という文字列があれば、パターンにマッチした文字列と置き換えられる

・パターンオブジェクトのグループに対して順番に入力文字列がマッチするか確認し、対応する応答例を求める

・PatternResponderがRandomResponderの機能を利用している(メソッドの上位クラスへの移動に関するリファクタリングが出来そうである)

・パターン辞書に応答例が無い場合のイリーガルケース