web-dev-qa-db-ja.com

正規表現を使用して、ネストされたパターンを一致させることはできますか?

不明な回数発生するネストされたパターンに一致する正規表現を書くことは可能ですか?たとえば、外部ブレース内に入れ子になっているオープン/クローズブレースの数が不明な場合、正規表現を開きブレースと閉じブレースに一致させることはできますか?

例えば:

public MyMethod()
{
  if (test)
  {
    // More { }
  }

  // More { }
} // End

一致する必要があります:

{
  if (test)
  {
    // More { }
  }

  // More { }
}
222
Richard Dorman

いいえ。簡単です。有限オートマトン(正規表現の基礎となるデータ構造)には、その状態以外のメモリはありません。また、任意の深さのネストがある場合、任意の大きなオートマトンが必要です。これは有限の概念と衝突しますオートマトン。

オートマトンが非常に大きくなるため、ネストされた要素とペアの要素を固定の深さまで一致させることができます。深さはメモリによってのみ制限されます。ただし、実際には、プッシュダウンオートマトン、つまりLL(トップダウン)またはLR(ボトムアップ)などの文脈自由文法のパーサーを使用する必要があります。悪い実行時動作を考慮する必要があります:O(n ^ 3)対O(n)、n = length(input)。

たとえば、Javaの場合は ANTLR など、利用可能なパーサージェネレーターが多数あります。 Java(またはC)の既存の文法を見つけることも難しくありません。
詳細な背景: Automata Theory ウィキペディア

256
Torsten Marek

正規表現を使用してネストされたパターンを確認するのは非常に簡単です。

'/(\((?>[^()]+|(?1))*\))/'
36
MichaelRushton

文字列が1行にある場合、おそらく動作するPerlソリューション:

my $NesteD ;
$NesteD = qr/ \{( [^{}] | (??{ $NesteD }) )* \} /x ;

if ( $Stringy =~ m/\b( \w+$NesteD )/x ) {
    print "Found: $1\n" ;
  }

HTH

EDIT:チェック:

Torsten Marek (もう正しく正規表現ではないことを指摘していた)によるもう1つのこと:

33
Zsolt Botykai

はい、.NET RegEx-engineの場合。 .Netエンジンは、外部スタックで提供される有限状態マシンをサポートします。 詳細 を参照

19
Pavlush

通常の言語のポンピング補題 は、それができない理由です。

生成されたオートマトンは有限の状態、たとえばkを持つため、k + 1の開始ブレースの文字列は(オ​​ートマトンが文字を処理するときに)どこかで繰り返される状態にバインドされます。同じ状態の間の文字列の部分は何度も無限に複製される可能性があり、オートマトンはその違いを知りません。

特に、k + 1の開始中括弧に続いてk + 1の終了中括弧を受け入れる場合(これが必要)、ポンピングされた数の開始中括弧に続いて、変更されていないk + 1の終了中括弧(これは禁止)を受け入れます。

15
Rafał Dowgird

適切な正規表現では、正規言語の領域を離れてContext Free Languages領域に到達するため、それを行うことができません。

それにもかかわらず、多くの言語が提供する「正規表現」パッケージは厳密に強力です。

たとえば、 Lua 正規表現には、バランスのとれた括弧と一致する「%b()」認識機能があります。あなたの場合、「%b{}」を使用します

Sedに似たもう1つの洗練されたツールは gema で、バランスの取れた中括弧を{#}と非常に簡単に一致させます。

したがって、使用できるツールによっては、「広義の表現」で「正規表現」がネストされた括弧に一致する場合があります。

13
Remo.D

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;
}
5
Pete B

はい

...いくつかのネストの最大数があると仮定して、あなたが停止するのが幸せです。

説明させてください。


@ 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} = \{(?:[^{}])*\}

これらの正規表現を再帰的に生成するスクリプトを作成することもできますが、これは私がこれを必要としている範囲を超えています。 (これは読者の演習として残されています。????)

4
awwsmm

zsoltが述べたように、一部の正規表現エンジンは再帰をサポートしています-もちろん、これらは通常、バックトラッキングアルゴリズムを使用するものであるため、特に効率的ではありません。例:/(?>[^{}]*){(?>[^{}]*)(?R)*(?>[^{}]*)}/sm

いいえ、その時点で Context Free Grammars の領域に入ります。

2
Craig H

これはうまくいくようです:/(\{(?:\{.*\}|[^\{])*\})/m

0
Sean Huber