1998/01/08
正規表現をε遷移を取り除いた NFA に変換して処理する際の、 不確定な要素の処理方法についての報告をする。 規模の大きな正規表現で大量のパターンマッチをする場合、 NFA を参照しながら DFA をシミュレートするよりも、 NFA を DFA に一括変換して DFA を参照しながらパターンマッチを行う方が、 変換のオーバヘッドがあっても効率が良いとされている。 ここでは、任意の一文字を表すメタキャラクタ「.」をε遷移を 取り除いた NFA の枠組みの中で、処理する方策を述べる。
正規表現を構文解析し NFA、さらに DFA へ変換 すると、NFA の不確定要素であるε遷移が無くなるので、DFA での遷 移は文字が与えられたら、一つだけ遷移先が決まる。ε遷移は、文字 を必要とせず、複数の遷移先を持つことが可能であるが(よって、遷移 先をすぐには決められない)、通常の遷移は一つの文字に対し、一つの 遷移先しか持てないためである。ここで、メタキャラクタ「.」を導入 し、「.」が含まれている
(a|.b)*a
という正規表現をε遷移を取り除いた NFA 遷移表に変換すると 以下のようになる。数字は、状態番号である。 左の状態から上の状態へと遷移することにする。 初期状態は 0、終了状態は 2 である。「.」は、 内部コード(1 文字 4 バイト)の使われていない領域に割り当て、 特殊な文字として扱うことにする。今回は 4 バイト文字を扱うため、 単純に任意の文字に展開して処理できないためである。
|0 1 2 3 -+------- 0| . a 1| b 2| . a 3| . a
繰り返すが、メタキャラクタ「.」は任意の一文字を表すので、 上記の正規表現では入力文字の先頭が「a」の場合、「a」と 「.b」のどちらに分岐したら良いか、即座に判別できない。 次の入力文字も判断材料にする必要が出てくる。このように、 即座に分岐を判断できない分岐箇所を、単に「分岐点」と呼ぶ ことにする(一般的な用語ではない、独自の用語である)。 言い方を変えると、上の表の横の列を見て「.」と通常の文字が、 並んでいる状態が分岐点である。状態 0, 2, 3 が分岐点であると言える。 分岐点は何ヵ所も出現する可能性がある。「.」の導入によって、 上述の「DFA での遷移は文字が与えられたら、 一つだけ遷移先が決まる」という前提が崩れてしまったことになるのである。 ε遷移を取り除いても、上の表は DFA とは言えないのである。
そこで、分岐点をスタックに覚えておき、分岐先をしらみつぶしに 探索する際に分岐した先が間違っていたら後戻り(バックトラック) するような実装を施してみた。実際には [^abc] なども実装して あるが、以下のアルゴリズムの自然な拡張になっている。
保存する状態は、「分岐点の状態番号( 分岐状態) 」、「文字へのポインタ」、 そして「通過フラグ」である。ある分岐点のすべての分岐先を探索し終えたら、 「通過済み」とする。保存する条件は、以下の通りである。 スタックが空でない時の条件は、無限ループを防ぐための条件である。
例えば、入力文字列が ababa の場合、0->1->(3)->1->(3)->2 の ように遷移する。状態 3 に括弧を付けたのは、同じ分岐点を 2 回 通過するのを強調するためである。2 回の通過とは、 スタック・トップに記録されている「通過済み」の同じ番号の状態に、 再び遷移してきたことを言う。 2 回目の通過時にも 1 回目と同じように探索できるように条件を 等しくする必要がある。
具体的には、スタックが空でない時に、スタック・トップの分岐 状態が現在の状態と同じであり、スタック・トップの通過フラグが 「通過済み」である場合に、スタック・トップの通過フラグを 「未通過」に書き換えることで実現できる。PUSH する時点で スタック・トップと現在の状態から判断して、PUSH 操作をこの 「書き換え」操作に置き換えるのである。これは上述の、 無限ループを防ぐ条件を満たしながら、探索を遂行するための工夫である。
スタックが空でないとき、以下の処理を行う。「通過済み」とは、 探索が終了している分岐点のことを表している。 「未通過」の分岐点にまで戻るか、スタックが空になるまで、 POP し続けることを意味している。
rollback()
{
for (;;){
if (スタック・トップの通過フラグが「通過済み」の場合){
POP();
if (スタックが空の場合){
return;
}
}else{
break;
}
}
スタック・トップの内容をコンテクストに反映;
POP();
return;
}
現状では、DFA に近い状態まで変換しているのに不確定な処理を しなければならないことを述べた。今後は、上記のような正規 表現を変換して、不確定要素である「. 」を消し去る方法を 考えていきたい。実現すればバックトラックが不必要になるので 本物の DFA になり、NFA から変換する利点が強調できるだろう。
動作の検証時には、伝法谷秀有氏に協力して頂きました。 また、ProgWin0 ML の皆さんからは、文章の誤りや理解困難な点をご指摘下さるなど、 多大な貢献がありました。御礼申し上げます。