後方参照付き正規表現が表す言語は必ずしも正規言語ではない

正規表現という言い方もどうかと思う

バイト先の勉強会で何か発表してよと言われたので、オートマトンの基礎(有限オートマトンと正規言語)について扱ったのだが、僕がオートマトンと形式言語の授業を取ったのは学部 2 年の頃なので、細かい部分についてはかなり忘れていた1。スライド作りのために計算理論の基礎 [原著第 3 版] 1.オートマトンと言語 を引っ張り出して勉強し直しているので、しばらくはこの分野で面白いトピックがあれば、紹介しようと思う2。 今回は、有名な事実ではあるが、後方参照を認める正規表現が表す言語は正規言語とは限らないことを示す。

後方参照

後方参照とは、キャプチャグループにマッチした文字列を、後続のパターンの中で再び参照する機能である。後方参照を含む正規表現の例を以下に示す。

^(.)o\1$

キャプチャグループ(.)は任意の 1 文字にマッチし、その文字をグループ 1 としてキャプチャする。次に文字oがマッチし、その後にグループ 1 でキャプチャされた文字列が\1によって再びマッチする。すなわち、この正規表現は同じ文字がoの前後にある文字列にマッチする。 例えば、sos3coc4bob5などの文字列が該当する。

具体的な使い方として、簡易的に HTML タグの対応のチェックが考えられる。

<([A-Za-z][A-Za-z0-9]*)\b[^>]*>.*?</\1>

もちろん厳密な仕様に沿った HTML タグの検査はさらに複雑だが、最低限、開始タグと終了タグが同じであることを確認できる。

正規言語

正規言語とは、ある有限オートマトンで認識できる言語である。たとえば、以下の有限オートマトンは 0 と 1 からなるアルファベット上の文字列のうち、すべての文字が 0 である非空文字列、またはすべての文字が 1 である非空文字列を受理する。

0 1 1 0 0 1 0,1 0,1

したがって、そのような文字列の集合(= 言語)は正規言語であると言える。このような正規言語を表現するための手法として、正規表現がある。これは、\cup(和集合)、*(スター演算)、\circ(連結)によって正規言語を表現することができる6。たとえば、上の有限オートマトンと等価な正規表現を以下に示す。

00110 \circ 0^* \cup 1 \circ 1^*

正規表現 RR で表現できる言語 LL は常に正規言語である。すなわち、後方参照付き正規表現が表現できる言語に、正規言語でないものが含まれていればタイトルの命題を証明することができる。

まず、最初に示した後方参照を含む正規表現を見てみよう。

^(.)o\1$

これはアルファベットの個数によって状態数は増えていくものの、有限オートマトンで表現できることがわかる。たとえば、アルファベット Σ={o,p}\Sigma = \{o, p\} であるとすると、以下の有限オートマトンで表現できる。

o,p o,p o p p o p o o,p o p p o

一方、次のような後方参照付きの正規表現は有限オートマトンでは表現できない。

^(a*)b\1$

直感的には 1 つ目のキャプチャグループでキャプチャされる文字列長を保存しておく手段が有限オートマトンには存在せず、有限状態に落とせないからである。 しかし、残念ながら直感的に無限状態が必要そうに見えても有限オートマトンで表現できる言語も存在する。たとえば、「文字列中に登場する 0101 の個数と 1010 の個数が一致する文字列」による言語である。これを有限オートマトンで表現する方法は読者への課題とするが、直感的に正規言語であるかどうかを区別することは難しい。

ある言語が正規言語であるかどうかを証明する道具として、ポンピング補題がよく使われる。

ポンピング補題

正規言語 LL に属する文字列 s=s1s2s3sns = s_1s_2s_3\dots s_n を考える。LL を認識する有限オートマトン AA の状態数を pp として、LL 上の文字列長 nnpp 以上であると仮定する。 すると、文字列 ssAA 上を遷移するときの状態列は以下のようになる。

q1s1q2s2q3s3snqpq_1 \overset{s_1}\to q_2 \overset{s_2}\to q_3 \overset{s_3}\to \dots \overset{s_n}\to q_p

状態列の長さは n+1n+1 であるが、npn \ge p より、n+1>pn + 1 \gt p である。 したがって、鳩の巣原理より状態列の中に必ず同じ状態が最低でも 2 回は現れる。たとえば n=p=4n = p = 4 とすると、以下のような状態列が考えられる。

q1s1q2s2q3s3q2s4q4q_1 \overset{s_1}\to q_2 \overset{s_2}\to q_3 \overset{s_3}\to q_2 \overset{s_4}\to q_4

ここで、ss を 3 つの文字列 x,y,zx, y, z に分割する。まず、xx は重複する状態に到達するまでの文字列、すなわちここでは x=s1x = s_1 である。次に、yy は重複する状態に再度到達するまでの文字列、すなわち y=s2s3y = s_2s_3 である。最後に、zz は残りの文字列、z=s4z = s_4 である。このように分割すると、以下のような擬似的な有限オートマトンを考えることができる。

x z y

すると、この有限オートマトンは s=xyzs = xyz だけでなく、xzxzxyyzxyyzxyyyyyzxyyyyyz のような、yy を 0 回以上繰り返した文字列 xyiz    (i0)xy^i z \;\; (i \ge 0) も受理することがわかる。このように、任意の正規言語は、ある長さ pp が存在し、長さが pp 以上のある任意の文字列 ss を、任意の i0i \ge 0 について xyizxy^i z を受理するように、s=xyzs = xyz に分割できる。 また、重複する状態を最初の pp 文字を読むまでの範囲から選んでいるため、xyp|xy| \le p である。さらに、2 回現れる状態は異なる位置で現れるので、y>0|y| > 0 である。

これを主張する補題がポンピング補題である。

証明

では、最後に後方参照付き正規表現が表す言語が必ずしも正規言語でないことを、ポンピング補題を用いて証明する。

AA を言語 {anbann0}\{a^n b a^n \mid n \ge 0\} とする。AA が正規言語であると仮定する。

pp をポンピング長とする。また、文字列 s=apbaps = a^p b a^p をとる。ssAA の要素で長さは pp 以上であるから、ポンピング補題により ss は 3 つの部分文字列 s=xyzs = xyz に分割できる。 加えて、任意の i0i \ge 0 について文字列 xyizxy^i zAA に属し、y>0|y| \gt 0xyp|xy| \le p を満たす。

ここで、xyp|xy| \le p であるから、xxyys=apbaps = a^p b a^p の最初の apa^p に含まれる。さらに、y>0|y| > 0 より、ある j>0j > 0 を用いて、y=ajy = a^j と書ける。

  1. y=ajy = a^j のとき、文字列 xyyz=ap+jbapxyyz = a^{p+j} b a^p となり、p+jpp+j \neq p から AA の要素ではない。よってポンピング補題の 1 つ目の条件を満たさず、矛盾。

AA を正規言語と仮定すると矛盾するので、AA は正規言語ではない。

\Box

おすすめの曲 紹介コーナー

Footnotes

  1. 覚えていたのは NFA、DFA、CFG、ポンピング補題の”存在”くらいで、もっと長期にわたって記憶していたいなあと思う。いつも

  2. せっかくブログを作ったし

  3. 遭難信号

  4. https://github.com/neoclide/coc.nvim

  5. Alice にメッセージを送られることで有名

  6. これら 3 つの演算が正規言語に閉じていることは個別に証明できる