2008-09-25 11 views
191

Ist es möglich, einen regulären Ausdruck zu schreiben, der einem verschachtelten Muster entspricht, das eine unbekannte Anzahl von Malen vorkommt? Kann ein regulärer Ausdruck beispielsweise einer öffnenden und schließenden geschweiften Klammer entsprechen, wenn in den äußeren geschweiften Klammern eine unbekannte Anzahl von öffnenden/schließenden geschweiften Klammern vorhanden ist?Können reguläre Ausdrücke verwendet werden, um verschachtelte Muster zu vergleichen?

Zum Beispiel:

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

    // More { } 
} // End 

Sollte entsprechen:

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

    // More { } 
} 
+21

Um diese Frage eindeutig zu beantworten, muss man zunächst den Begriff "regulärer Ausdruck" definieren. – ridgerunner

+3

@ridgerunner, Richard versucht nicht, die Frage zu beantworten, also ist dein Kommentar vielleicht besser an jemanden gerichtet, der so verlobt ist. – ProfK

+2

Aus den Büchern können * reguläre Ausdrücke * das nicht, aber * kontextfreie Ausdrücke * können.Von den Werkzeugen werden moderne Ausdrucksparser "regulären Ausdruck" etwas nennen, das einen externen Stapel verwendet, was in der Lage ist, rückzuspulen, dh rekursiv zu sein: das sind "kontextfreie Ausdrücke" in der Praxis und als solche können Sie es als one-liner mit Simili- [PCRE2] (http://www.pcre.org/) (PHP, Java, .NET, Perl, ...) oder [ICU] (http: //userguide.icu-project. org/strings/regexp) -konforme (Obj-C/Swift) Werkzeuge, oft mit der Syntax '(?> ...)' oder Alternativen wie die '(? R)' oder '(? 0)' -Syntaxen . –

Antwort

235

Nein. Es ist so einfach. Ein endlicher Automat (die Datenstruktur, die einem regulären Ausdruck zugrunde liegt) hat kein Gedächtnis außer dem Zustand, in dem er sich befindet, und wenn Sie eine beliebig tiefe Verschachtelung haben, benötigen Sie einen beliebig großen Automaten, der mit dem Begriff endliche kollidiert Automat.

Sie können verschachtelte/gepaarte Elemente bis zu einer festen Tiefe zuordnen, wobei die Tiefe nur durch Ihren Speicher begrenzt wird, da der Automat sehr groß wird. In der Praxis sollten Sie jedoch einen Push-Down-Automaten verwenden, d. H. Einen Parser für eine kontextfreie Grammatik, beispielsweise LL (von oben nach unten) oder LR (von unten nach oben). Sie müssen das schlechtere Laufzeitverhalten berücksichtigen: O (n^3) gegen O (n), mit n = Länge (Eingabe).

Es gibt viele Parser-Generatoren verfügbar, zum Beispiel ANTLR für Java. Eine vorhandene Grammatik für Java (oder C) zu finden, ist ebenfalls nicht schwierig.
Weiteren Hintergrund: Automata Theory bei Wikipedia

+49

Torsten ist in der Theorie korrekt. In der Praxis haben viele Implementierungen einige Tricks, um rekursive "reguläre Ausdrücke" ausführen zu können. Z.B. siehe das Kapitel "Rekursive Muster" in http://php.net/manual/en/regexp.reference.php – daremon

+2

Ich bin verwöhnt von meiner Erziehung in Natural Language Processing und der darin enthaltenen Automatentheorie. –

+4

Eine erfrischend klare Antwort. Bestes "warum nicht" ich je gesehen habe. –

-3

Nein. Sie benötigen einen vollständigen Parser für diese Art von Problem.

+9

... oder Perl5.10 oder höher –

31

Perl Lösung Wahrscheinlich arbeiten, wenn die Zeichenfolge in einer Zeile ist:

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

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

HTH

EDIT: Kontrolle:

Und noch eine Sache von Torsten Marek (die zu Recht darauf hingewiesen hatte, dass es kein Regex mehr):

+9

Yup. Perls "reguläre Ausdrücke" sind nicht (und waren schon sehr lange nicht mehr). Es sollte angemerkt werden, dass rekursive Regexes ein neues Feature in Perl 5.10 sind und dass, obwohl Sie dies tun können, Sie in den meisten Fällen, die üblicherweise auftreten sollten (z. B. das Analysieren von HTML), dies wahrscheinlich nicht tun sollten. –

+0

http://perldoc.perl.org/perlretut.html –

14

Die Pumping lemma for regular languages ist der Grund, warum Sie das nicht tun können.

Der erzeugte Automat wird eine endliche Anzahl von Zuständen haben, sagen wir k, so dass eine Kette von k + 1 öffnenden Klammern irgendwo wiederholt werden muss (da der Automat die Zeichen verarbeitet). Der Teil der Zeichenkette zwischen dem gleichen Zustand kann unendlich viele Male dupliziert werden und der Automat wird den Unterschied nicht kennen.

Insbesondere, wenn es k + 1 öffnende Klammern gefolgt von k + 1 schließenden Klammern akzeptiert (was es sollte), akzeptiert es auch die gepumpte Anzahl von öffnenden Klammern gefolgt von unveränderten k + 1 schließenden Klammern (was es nicht t).

12

Korrekte reguläre Ausdrücke könnten dies nicht tun, da Sie den Bereich regulärer Sprachen verlassen würden, um in den kontextfreien Sprachen zu landen.

Nichtsdestoweniger sind die Pakete "regulärer Ausdruck", die viele Sprachen anbieten, strenger.

Zum Beispiel haben Lua reguläre Ausdrücke den "" Erkenner, der mit ausgeglichenen Klammern übereinstimmt. In Ihrem Fall würden Sie „%b{}

Ein weiteres entwickeltes Werkzeug ähnlich wie sed verwenden ist gema, wo Sie ausgewogene geschweiften Klammern sehr leicht mit {#} übereinstimmen.

Also, abhängig von den Werkzeugen, die Sie zur Verfügung haben, kann Ihr "regulärer Ausdruck" (im weiteren Sinne) in der Lage sein, verschachtelte Klammern zu finden.

3

als Zsolt erwähnt, unterstützen einige Regex-Engines Rekursion - natürlich sind dies in der Regel diejenigen, die einen Backtracking-Algorithmus verwenden, so dass es nicht besonders effizient sein wird. Beispiel: /(?>[^{}]*){(?>[^{}]*)(?R)*(?>[^{}]*)}/sm

18

Ja, wenn es .NET RegEx-Engine ist. Die .Net-Engine unterstützt Finite-State-Maschinen, die mit einem externen Stack ausgestattet sind. siehe details

+8

Wie andere bereits erwähnt haben, ist .NET _nicht_ die einzige leistungsfähige Regex-Engine, die dies tut. –

0

Dies scheint zu funktionieren: /(\{(?:\{.*\}|[^\{])*\})/m

+1

Es scheint auch zu '{{}' zu passen, was es nicht sollte –

27

Verwenden von regulären Ausdrücken für verschachtelte Muster zu überprüfen, ist sehr einfach.

+2

Ich stimme zu. Ein Problem mit der "(?> ...)" atomischen Gruppensyntax (unter PHP 5.2) ist jedoch, dass der '?>' Teil als "end-of-script" interpretiert wird! Hier ist, wie ich es schreiben würde: '/ \ ((?: [^()] ++ | (? R)) * + \) /'. Dies ist ein wenig effizienter sowohl für das Matching als auch für das Non-Matching. In seiner minimalen Form '/ \ (([^()] | (? R)) * \) /' ist es wirklich eine schöne Sache! – ridgerunner

+1

Doppel +? Ich habe '(? 1)' verwendet, damit Kommentare innerhalb eines anderen Textes stehen (ich habe es gerippt und es vom regulären Ausdruck meiner E-Mail-Adresse vereinfacht). Und '(?>' Wurde verwendet, weil ich glaube, dass es schneller scheitert (falls erforderlich). Ist das nicht korrekt? – MichaelRushton

+5

Können Sie für jeden Teil der Regex eine Erklärung hinzufügen? – Dwayne

5

Das rekursive Matching in der PHP-Regex-Engine ist massiv schneller als das prozedurale Matching von Klammern. besonders bei längeren Saiten.

http://php.net/manual/en/regexp.reference.recursive.php

z.B.

$patt = '!\((?: (?: (?>[^()]+) | (?R))*) \)!x'; 

preg_match_all($patt, $str, $m); 

gegen

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; 
} 
0

Meine question+answer verwendet ist und ich mache einen Ausdruck und Meta-Ausdruck, die beliebig (endlich) Verschachtelungsebenen mithalten kann. Es ist ziemlich fug, aber was kann man sonst noch erwarten? Verwenden Sie Rückwärtsreferenzen in der Übereinstimmung, wenn Ihre Engine dies unterstützt.