再帰パターンに代わる .Net はスタックです。ここでの課題は、スタックに関する文法を表現する必要があることです。
これを行う1つの方法を次に示します。
文法のわずかに異なる表記法
- まず、文法規則が必要です (問題の
A
andQ
のように)。
- 1 つのスタックがあります。スタックにはルールのみを含めることができます。
- 各ステップで、スタックから現在の状態をポップし、照合する必要があるものを照合し、さらにルールをスタックにプッシュします。状態が終了したら、何もプッシュせずに前の状態に戻ります。
この表記法は、ルールをスタックにプッシュするCFGとプッシュダウン オートマトンの間のどこかにあります。
例:
a n b nという簡単な例から始めましょう。この言語の通常の文法は次のとおりです。
S -> aSb | ε
表記に合うように言い換えることができます。
# Start with <push S>
<pop S> -> "a" <push B> <push S> | ε
<pop B> -> "b"
言葉で:
- スタックから始め
S
ます。
- スタックからポップ
S
すると、次のいずれかを実行できます。
- 何も一致しないか、または...
- 「a」に一致しますが、状態
B
をスタックにプッシュする必要があります。これは、「b」に一致する約束です。次にS
、必要に応じて "a" を一致させ続けることができるように、プッシュも行います。
- 十分な数の "a" を一致させたら
B
、スタックから s をポップし始め、それぞれに "b" を一致させます。
- これが完了すると、偶数の「a」と「b」が一致しました。
または、より大まかに:
S の場合は、"a" に一致し、B を押してから S を押すか、何も一致しません。
ケース B の場合は、"b" に一致します。
いずれの場合も、現在の状態をスタックからポップします。
パターンを .Net 正規表現で記述する
何らかの形でさまざまな状態を表す必要があります。'1' '2' '3'または'a' 'b' 'c'を選択することはできません。これらは入力文字列で使用できない可能性があるためです。文字列に存在するもののみを照合できます。
1 つのオプションは、州に番号を付けることです (上記の例でS
は、州番号 0、B
州 1 です)。
状態 S の場合文字をスタックにプッシュできます。便宜上、文字列の先頭から最初の文字をプッシュします。繰り返しますが、これらの文字が何であるかは気にしません。何人いるかだけです。
プッシュ状態
.Net では、文字列の最初の 5 文字をスタックにプッシュする場合、次のように記述できます。
(?<=(?=(?<StateId>.{5}))\A.*)
これは少し複雑です:
(?<=…\A.*)
は、文字列の先頭までずっと続く後読みです。
- 開始時には、次のような見通しがあります
(?=…)
。これは、現在の位置を超えて一致できるようにするために行われます。位置2にいる場合、前に 5 文字はありません。だから私たちは振り返り、楽しみにしています。
(?<StateId>.{5})
5 文字をスタックにプッシュし、ある時点で状態 5 に一致させる必要があることを示します。
ポップ状態
私たちの表記法によれば、常にスタックから最上位の状態をポップします。それは簡単です: (?<-StateId>)
.
しかし、それを行う前に、それがどのような状態であったか、またはキャプチャされた文字数を知りたいと考えています。より具体的には、switch
/case
ブロックのように、各状態を明示的にチェックする必要があります。したがって、スタックが現在状態 5 を保持しているかどうかを確認するには、次のようにします。
(?<=(?=.{5}(?<=\A\k<StateId>))\A.*)
- 繰り返し
(?<=…\A.*)
ますが、最初からずっと行きます。
(?=.{5}…)
これで5文字進みます...
- そして、別の後読みを使用
(?<=\A\k<StateId>)
して、スタックに実際に 5 文字あることを確認します。
これには明らかな欠点があります。文字列が短すぎると、大きな状態の数を表すことができません。この問題には解決策があります:
- とにかく、言語の短い単語の数は最終的なものなので、手動で追加できます。
- 単一のスタックよりも複雑なものを使用します。それぞれが 0 または 1 文字の複数のスタックを使用して、効果的に状態のビットを表すことができます (最後に例があります)。
結果
a n b nのパターンは次のようになります。
\A
# Push State A, Index = 0
(?<StateId>)
(?:
(?:
(?:
# When In State A, Index = 0
(?<=(?=.{0}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
(?:
# Push State B, Index = 1
(?<=(?=(?<StateId>.{1}))\A.*)
a
# Push State A, Index = 0
(?<StateId>)
|
)
)
|
(?:
# When In State B, Index = 1
(?<=(?=.{1}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
b
)
|\Z
){2}
)+
\Z
# Assert state stack is empty
(?(StateId)(?!))
Regex Storm での作業例
ノート:
(?:(?:…){2})+
状態の量指定子は、(状態数)×(入力長) であることに注意してください。の代替も追加しました\Z
。それには立ち入りませんが、これは .Net エンジンの煩わしい最適化の回避策です。
- 同じことを次のように書くことができます
(?<A>a)+(?<-A>b)+(?(A)(?!))
- これは単なる演習です。
質問に答えるには
質問の文法は次のように書き換えることができます。
# Start with <push A>
<pop A> -> <push A> ( @"," | <push Q> ) | ε
<pop Q> -> \w
| "<" <push Q2Close> <push A>
| "[" <push Q1Close> <push QStar> <push Q1Comma> <push QStar> <push Q1Semicolon> <push A>
<pop Q2Close> -> ">"
<pop QStar> -> <push QStar> <push Q> | ε
<pop Q1Comma> -> ","?
<pop Q1Semicolon> -> ";"
<pop Q1Close> -> "]"
パターン:
\A
# Push State A, Index = 0
(?<StateId>)
(?:
(?:
(?:
# When In State A, Index = 0
(?<=(?=.{0}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
(?:
# Push State A, Index = 0
(?<StateId>)
(?:
,
|
# Push State Q, Index = 1
(?<=(?=(?<StateId>.{1}))\A.*)
)
|
)
)
|
(?:
# When In State Q, Index = 1
(?<=(?=.{1}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
(?:
\w
|
<
# Push State Q2Close, Index = 2
(?<=(?=(?<StateId>.{2}))\A.*)
# Push State A, Index = 0
(?<StateId>)
|
\[
# Push State Q1Close, Index = 6
(?<=(?=(?<StateId>.{6}))\A.*)
# Push State QStar, Index = 3
(?<=(?=(?<StateId>.{3}))\A.*)
# Push State Q1Comma, Index = 4
(?<=(?=(?<StateId>.{4}))\A.*)
# Push State QStar, Index = 3
(?<=(?=(?<StateId>.{3}))\A.*)
# Push State Q1Semicolon, Index = 5
(?<=(?=(?<StateId>.{5}))\A.*)
# Push State A, Index = 0
(?<StateId>)
)
)
|
(?:
# When In State Q2Close, Index = 2
(?<=(?=.{2}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
>
)
|
(?:
# When In State QStar, Index = 3
(?<=(?=.{3}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
(?:
# Push State QStar, Index = 3
(?<=(?=(?<StateId>.{3}))\A.*)
# Push State Q, Index = 1
(?<=(?=(?<StateId>.{1}))\A.*)
|
)
)
|
(?:
# When In State Q1Comma, Index = 4
(?<=(?=.{4}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
,?
)
|
(?:
# When In State Q1Semicolon, Index = 5
(?<=(?=.{5}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
;
)
|
(?:
# When In State Q1Close, Index = 6
(?<=(?=.{6}(?<=\A\k<StateId>))\A.*)
(?<-StateId>)
\]
)
|\Z
){7}
)+
\Z
# Assert state stack is empty
(?(StateId)(?!))
残念ながら、URL に収めるには長すぎるため、オンラインの例はありません。
1 文字または 0 文字の「バイナリ」スタックを使用すると、次のようになります: https://gist.github.com/kobi/8012361
以下は、すべてのテストに合格したパターンのスクリーンショットです: http://i.stack.imgur.com/IW2xr.png
ボーナス
A
.Net エンジンは、バランスを取るだけでなく、またはの各インスタンスをキャプチャすることもできますQ
。これには、パターンhttps://gist.github.com/kobi/8156968を少し変更する必要があります。
groups と をパターンに追加したことに注意してくださいStart
。A
ただしQ
、それらはフローの影響を受けず、純粋にキャプチャに使用されます。
結果: たとえば、 string の場合、次の s"[<a>b;<c,d>,<e,f>]"
を取得できます。Capture
Group A
(0-17) [<a>b;<c,d>,<e,f>]
(1-4) <a>b
(2-2) a
(7-9) c,d
(13-15) e,f
Group Q
(0-17) [<a>b;<c,d>,<e,f>]
(1-3) <a>
(2-2) a
(4-4) b
(6-10) <c,d>
(7-7) c
(9-9) d
(12-16) <e,f>
(13-13) e
(15-15) f
未解決の質問
- すべての文法を状態スタック表記に変換できますか?
- (状態数)×(入力長) のステップ数は、すべての単語に一致するのに十分ですか? 他にどのような式が機能しますか?
ソースコード
これらのパターンとすべてのテスト ケースを生成するために使用されるコードは、https://github.com/kobi/RecreationalRegexにあります。