不明な回数発生するネストされたパターンに一致する正規表現を書くことは可能ですか?たとえば、外部ブレース内に入れ子になっているオープン/クローズブレースの数が不明な場合、正規表現を開きブレースと閉じブレースに一致させることはできますか?
例えば:
public MyMethod()
{
if (test)
{
// More { }
}
// More { }
} // End
一致する必要があります:
{
if (test)
{
// More { }
}
// More { }
}
いいえ。簡単です。有限オートマトン(正規表現の基礎となるデータ構造)には、その状態以外のメモリはありません。また、任意の深さのネストがある場合、任意の大きなオートマトンが必要です。これは有限の概念と衝突しますオートマトン。
オートマトンが非常に大きくなるため、ネストされた要素とペアの要素を固定の深さまで一致させることができます。深さはメモリによってのみ制限されます。ただし、実際には、プッシュダウンオートマトン、つまりLL(トップダウン)またはLR(ボトムアップ)などの文脈自由文法のパーサーを使用する必要があります。悪い実行時動作を考慮する必要があります:O(n ^ 3)対O(n)、n = length(input)。
たとえば、Javaの場合は ANTLR など、利用可能なパーサージェネレーターが多数あります。 Java(またはC)の既存の文法を見つけることも難しくありません。
詳細な背景: Automata Theory ウィキペディア
正規表現を使用してネストされたパターンを確認するのは非常に簡単です。
'/(\((?>[^()]+|(?1))*\))/'
文字列が1行にある場合、おそらく動作するPerlソリューション:
my $NesteD ;
$NesteD = qr/ \{( [^{}] | (??{ $NesteD }) )* \} /x ;
if ( $Stringy =~ m/\b( \w+$NesteD )/x ) {
print "Found: $1\n" ;
}
HTH
EDIT:チェック:
Torsten Marek (もう正しく正規表現ではないことを指摘していた)によるもう1つのこと:
はい、.NET RegEx-engineの場合。 .Netエンジンは、外部スタックで提供される有限状態マシンをサポートします。 詳細 を参照
通常の言語のポンピング補題 は、それができない理由です。
生成されたオートマトンは有限の状態、たとえばkを持つため、k + 1の開始ブレースの文字列は(オートマトンが文字を処理するときに)どこかで繰り返される状態にバインドされます。同じ状態の間の文字列の部分は何度も無限に複製される可能性があり、オートマトンはその違いを知りません。
特に、k + 1の開始中括弧に続いてk + 1の終了中括弧を受け入れる場合(これが必要)、ポンピングされた数の開始中括弧に続いて、変更されていないk + 1の終了中括弧(これは禁止)を受け入れます。
PHP正規表現エンジンで再帰マッチングを使用することは、ブラケットの手続き型マッチングよりも非常に高速です。特に長い文字列の場合。
http://php.net/manual/en/regexp.reference.recursive.php
例えば.
$patt = '!\( (?: (?: (?>[^()]+) | (?R) )* ) \)!x';
preg_match_all( $patt, $str, $m );
vs.
matchBrackets( $str );
function matchBrackets ( $str, $offset = 0 ) {
$matches = array();
list( $opener, $closer ) = array( '(', ')' );
// Return early if there's no match
if ( false === ( $first_offset = strpos( $str, $opener, $offset ) ) ) {
return $matches;
}
// Step through the string one character at a time storing offsets
$paren_score = -1;
$inside_paren = false;
$match_start = 0;
$offsets = array();
for ( $index = $first_offset; $index < strlen( $str ); $index++ ) {
$char = $str[ $index ];
if ( $opener === $char ) {
if ( ! $inside_paren ) {
$paren_score = 1;
$match_start = $index;
}
else {
$paren_score++;
}
$inside_paren = true;
}
elseif ( $closer === $char ) {
$paren_score--;
}
if ( 0 === $paren_score ) {
$inside_paren = false;
$paren_score = -1;
$offsets[] = array( $match_start, $index + 1 );
}
}
while ( $offset = array_shift( $offsets ) ) {
list( $start, $finish ) = $offset;
$match = substr( $str, $start, $finish - $start );
$matches[] = $match;
}
return $matches;
}
...いくつかのネストの最大数があると仮定して、あなたが停止するのが幸せです。
説明させてください。
@ torsten-marek が正しいことは、正規表現がこのようなネストされたパターンをチェックできないことです。しかしは可能ですdefineネストされた正規表現パターンを使用すると、こののようなネスト構造を最大の深さまでキャプチャできます。 EBNF-style コメント( ここで試してみてください )をキャプチャするために作成しました:
(* This is a comment (* this is nested inside (* another level! *) hey *) yo *)
正規表現(単一の深さのコメント用)は次のとおりです。
m{1} = \(+\*+(?:[^*(]|(?:\*+[^)*])|(?:\(+[^*(]))*\*+\)+
\(+\*+
と\*+\)+
を{
と}
に置き換え、その間のすべてを単純な[^{}]
に置き換えることにより、これを目的に簡単に適合させることができます。
p{1} = \{(?:[^{}])*\}
( リンクはこちら 試してみてください。)
ネストするには、ブロック内でこのパターンを許可します。
p{2} = \{(?:(?:p{1})|(?:[^{}]))*\}
...or...
p{2} = \{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\}
三重にネストされたブロックを見つけるには、次を使用します。
p{3} = \{(?:(?:p{2})|(?:[^{}]))*\}
...or...
p{3} = \{(?:(?:\{(?:(?:\{(?:[^{}])*\})|(?:[^{}]))*\})|(?:[^{}]))*\}
明確なパターンが現れました。 N
の深さにネストされたコメントを見つけるには、単に正規表現を使用します。
p{N} = \{(?:(?:p{N-1})|(?:[^{}]))*\}
where N > 1 and
p{1} = \{(?:[^{}])*\}
これらの正規表現を再帰的に生成するスクリプトを作成することもできますが、これは私がこれを必要としている範囲を超えています。 (これは読者の演習として残されています。????)
zsoltが述べたように、一部の正規表現エンジンは再帰をサポートしています-もちろん、これらは通常、バックトラッキングアルゴリズムを使用するものであるため、特に効率的ではありません。例:/(?>[^{}]*){(?>[^{}]*)(?R)*(?>[^{}]*)}/sm
いいえ、その時点で Context Free Grammars の領域に入ります。
これはうまくいくようです:/(\{(?:\{.*\}|[^\{])*\})/m