正規表現
文章の中から決まった形の文字列を探す書き方です。同じパターンでも、照合の方式で手間が大きく変わります。
オプション
例
照合の手順
| 方式 | 文字の比較 | 後戻り | 同時に追跡した状態の最大 | DFA の状態 | 一致 | 使われている所 |
|---|
- 文字の比較
- 0
- 一致
- 0 件
- 後戻り
- 0
- スタックの深さ
- 0
動作原理と式
正規表現と有限オートマトン
正規表現は文字列の集合(言語)を表します。連接・選択・繰り返しの 3 つで組める言語を正規言語と呼びます。
Kleene の定理によると、正規表現で書ける言語と有限オートマトンが受理する言語は一致します [1]。有限オートマトンには有限個の状態があり、1 文字読むごとに状態を移ります。同じ文字で行き先が複数ある NFA も、行き先が 1 つに決まる DFA も、受理できる言語は同じです [2]。
パターンから NFA を組む
Thompson の構成では、パターンの部品ごとに入口と出口が 1 つずつの小さな NFA を作り、つなぎ合わせます [3]。ε は文字を読まずに進む矢印です。
- 文字 a: 2 つの状態を a の矢印でつなぐ
- 連接 rs: r の出口を s の入口につなぐ
- 選択 r|s: 新しい入口から r と s へ ε で分かれ、両方の出口を新しい出口へまとめる
- 繰り返し r*: 入口から r と出口へ分かれ、r の出口から入口へ戻る
- r+ は rr*、r? は r|ε、r{2,3} は rr(r|ε) と同じ形
1 つの部品で増える状態は 2 個までなので、パターンの長さを とすると状態は 個以下です。{'{m,n}'} は部品を複製するので、そのぶん長くなります。
NFA の同時シミュレーション
今いる状態の集合 を持ち、文字 を読むたびに全部の状態を同時に進めます [3]。 は ε の矢印だけでたどれる状態を足す操作です。
集合の大きさは 以下で、文章は 1 回しか読みません。文章の長さを とすると手間は O(𝑚𝑛) で、後戻りはありません [4]。
Pike VM は集合の各状態にキャプチャの位置を持たせます [5]。集合を優先順に並べ、同じ状態に 2 本たどり着いたら先のほうだけ残します。これでバックトラックと同じく、左に書いた選択肢を優先した一致が得られます。
NFA から DFA へ
部分集合構成では、NFA の状態の集合 1 つを DFA の状態 1 つにします [2]。DFA は 1 文字につき表を 1 回引くだけで進みます。ただし集合の数だけ状態ができうるので、最悪で指数個になります。
(a|b)*a(a|b){k} は最後の 文字を覚えておく必要があり、a と b だけの文章でも 個の状態が要ります。
遅延 DFA は文章で実際にたどった状態だけを作り、キャッシュに残します。1 回の検索で新しくできる状態は 個までです。RE2 はキャッシュがあふれると全部捨てて作り直し、それが続くと NFA に切り替えます [6]。
バックトラックの手間
バックトラックは分かれ道で 1 つを選んで進み、失敗すると後戻りして次を試します [4]。(a+)+b を a が 個の文章に当てると、外側の + が a の並びをいくつに分けるかを全部試します。隣り合う a の間ごとに「切る・切らない」があるので、分け方は次の数です。
b がないのでどれも失敗し、開始位置を 1 つずらして同じことを繰り返します。右の式が開始位置すべての合計です。a が 12 個では文字の比較が約 24,547 回になります。メモ化と NFA は同じ (状態, 位置) を 1 回しか調べないので約 60 回です。
破滅的バックトラック(ReDoS)への対策
手間が指数的に増えるパターンに長い文章を渡すと、サーバーが長く応答しなくなります。これが ReDoS で、公開のパッケージにも多く見つかっています [7]。
- Ruby 3.2: 分かれ道で (命令, 位置) の組を覚え、同じ組を 2 度調べない [8][9]
- Ruby 3.2 のメモ化の範囲: 後方参照・先読み・後読みを含むパターンでは使わない。それ以外の備えとして Regexp.timeout を足した [8]
- RE2・Rust の regex: 後方参照と先読み・後読みを捨て、文章の長さに比例する時間を保証する [10][11]
- .NET: 照合に時間の上限を付けられ、超えると RegexMatchTimeoutException を出す。既定は上限なし [12]
- .NET 7 以降: RegexOptions.NonBacktracking で線形時間の照合を選べる。後方参照・先読み・後読み・アトミックグループは使えない [13]
- V8: 試験的な線形時間のエンジンがあり、フラグを付けたときだけ動く。後戻りが 50,000 回を超えたら切り替える設定もある [14]
メモ化の表は命令の数と位置の数の積で、時間もメモリも O(𝑚𝑛) です。
後方参照が NFA・DFA で扱えない理由
後方参照 \1 は、キャプチャした文字列と同じものをもう一度求めます。^(a*)b\1$ に一致する文章は次の集合です。
b の前の a の数を覚えるには無限個の状態が要るので、この言語は正規言語ではありません。有限個の状態しか持たない NFA と DFA では表せません。後方参照つきの照合は NP 完全で [15]、効率のよい方法は知られていません。
計算量
は文章の長さ、 はパターンの長さです。照合は文章を 1 回調べる最悪の手間、前処理は照合の前に作るものの大きさです。
| 照合 | 前処理 | 後方参照 | 使われている所 | 出典 | |
|---|---|---|---|---|---|
| バックトラック | O(2𝑛) など指数 | O(𝑚) | 使える | JavaScript・Python・Perl・Java・PCRE | [4] |
| バックトラック+メモ化 | O(𝑚𝑛)、メモリも O(𝑚𝑛) | O(𝑚) | あるとメモ化を止める | Ruby 3.2 以降 | [8][9] |
| NFA シミュレーション | O(𝑚𝑛) | O(𝑚) | 使えない | Thompson 1968・Pike VM(RE2・Go) | [3][5] |
| 遅延 DFA | O(𝑚𝑛)、状態がそろえば O(𝑛) | O(𝑚) | 使えない | RE2・Rust の regex・GNU grep | [6][11] |
| DFA | O(𝑛) | O(2𝑚) | 使えない | lex・flex | [2] |
出典
- 正規表現と有限オートマトンの対応S. C. Kleene, “Representation of Events in Nerve Nets and Finite Automata,” in C. E. Shannon, J. McCarthy (eds.), Automata Studies, Annals of Mathematics Studies 34, Princeton University Press, pp. 3–42, 1956
- NFA と部分集合構成M. O. Rabin, D. Scott, “Finite Automata and Their Decision Problems,” IBM Journal of Research and Development, vol. 3, no. 2, pp. 114–125, 1959
- NFA の組み方と同時シミュレーションK. Thompson, “Programming Techniques: Regular expression search algorithm,” Communications of the ACM, vol. 11, no. 6, pp. 419–422, 1968
- バックトラックと NFA の比較、DFA の状態のキャッシュR. Cox, Regular Expression Matching Can Be Simple And Fast, 2007
- Pike VM とキャプチャR. Cox, Regular Expression Matching: the Virtual Machine Approach, 2009
- RE2 の DFA のキャッシュR. Cox, Regular Expression Matching in the Wild, 2010
- ReDoS の実態J. C. Davis, C. A. Coghlan, F. Servant, D. Lee, “The impact of regular expression denial of service (ReDoS) in practice: an empirical study at the ecosystem scale,” ESEC/FSE 2018, pp. 246–256
- Ruby 3.2 のメモ化とタイムアウトRuby, Ruby 3.2.0 Released, ruby-lang.org, 2022
- メモ化で ReDoS を防ぐJ. C. Davis, F. Servant, D. Lee, “Using Selective Memoization to Defeat Regular Expression Denial of Service (ReDoS),” 2021 IEEE Symposium on Security and Privacy
- RE2Google, RE2, README, GitHub
- Rust の regexregex crate documentation, docs.rs
- .NET のタイムアウトMicrosoft, Best Practices for Regular Expressions in .NET, Microsoft Learn
- .NET の NonBacktrackingMicrosoft, Options for regular expression, Microsoft Learn
- V8 の線形時間のエンジンM. Bidlingmaier, An additional non-backtracking RegExp engine, V8 blog, 2021
- 後方参照つきの照合は NP 完全A. V. Aho, “Algorithms for Finding Patterns in Strings,” in J. van Leeuwen (ed.), Handbook of Theoretical Computer Science, Vol. A: Algorithms and Complexity, Elsevier, pp. 255–300, 1990