2010-01-24 16 views
6

Ich bin ziemlich anständig mit regulären Ausdrücken, und jetzt versuche ich noch einmal Lookahead und Lookbehind Behauptungen zu verstehen. Sie machen meistens Sinn, aber ich bin nicht ganz sicher, wie sich die Reihenfolge auf das Ergebnis auswirkt. Ich habe mir this site angesehen, die Lookbehinds vor den Ausdruck stellt und nach dem Ausdruck nachschaut. Meine Frage ist, ändert das irgendetwas? Eine jüngere Antwort hier auf SO hat den Lookahead vor den Ausdruck gestellt, der zu meiner Verwirrung führt.Regex Lookahead Bestellung

Antwort

9

Wenn Tutorials lookarounds vorstellen, neigen sie dazu, die einfachsten Anwendungsfall für jeden zu wählen. Sie verwenden also Beispiele wie (?<!a)b ('b' nicht vorangestellt 'a') oder q(?=u) ('q' gefolgt von 'u'). Es dient nur dazu, die Erklärung nicht mit ablenkenden Details zu überfrachten, aber es tendiert dazu, den Eindruck zu erzeugen (oder zu verstärken), dass Lookbehinds und Lookaheads in einer bestimmten Reihenfolge erscheinen sollen. Es hat eine Weile gedauert, bis ich über diese Idee hinweggekommen bin, und ich habe auch andere Betroffene gesehen.

Versuchen Sie, einige realistischere Beispiele zu betrachten. Eine Frage, die oft aufkommt, ist die Validierung von Passwörtern. Stellen Sie beispielsweise sicher, dass ein neues Passwort mindestens sechs Zeichen lang ist und mindestens einen Buchstaben und eine Ziffer enthält. Eine Möglichkeit, das zu tun wäre:

^(?=.*[A-Za-z])(?=.*\d)[A-Za-z0-9]{6,}$ 

Die Zeichenklasse [A-Za-z0-9]{6,} konnte alle Buchstaben oder alle Ziffern entsprechen, so verwenden Sie die Lookaheads, um sicherzustellen, dass es zumindest eine von jedem. In diesem Fall müssen Sie die Lookaheads zuerst tun, weil die späteren Teile der Regex in der Lage sein müssen, die gesamte Zeichenfolge zu untersuchen.

Angenommen, Sie müssen alle Vorkommen des Worts "there" finden, sofern nicht ein Anführungszeichen vorangestellt ist. Der offensichtliche Regex dafür ist (?<!")[Tt]here\b, aber wenn Sie ein großes Korpus suchen, könnte das ein Leistungsproblem verursachen. Wie geschrieben, wird diese Regex den negativen Lookhind an jeder Position im Text ausführen, und nur wenn das gelingt, wird der Rest der Regex überprüft.

Jede Regex-Engine hat ihre eigenen Stärken und Schwächen, aber eine Sache, die für alle gilt, ist, dass sie schneller feste Sequenzen von literalen Zeichen als alles andere finden - je länger die Sequenz, desto besser. Das bedeutet, kann es dramatisch schneller sein, das Lookbehind letzte zu tun, auch wenn es bedeutet, das Wort passenden zweimal:

[Tt]here\b(?<!"[Tt]here) 

So ist die Regel die Platzierung von lookarounds regeln ist, dass es keine Regel gibt; Sie legen sie dort hin, wo sie am sinnvollsten sind.

1

1(?=ABC) bedeutet - nach 1 suchen, und passen (aber nicht erfassen) ABC danach.
(?<=ABC)1 bedeutet - übereinstimmen (aber nicht erfassen) ABC vor dem aktuellen Standort und weiterhin übereinstimmen 1.
Normalerweise platzieren Sie den Lookahead nach dem Ausdruck und dem Lookbehind davor.

Wenn wir hinter den Ausdruck einen Lookbehind setzen, sind wir und überprüfen die Zeichenfolge, die wir bereits gefunden haben. Dies ist üblich, wenn Sie komplexe Bedingungen haben (Sie können darüber nachdenken wie die AND von Regexs). Nehmen wir zum Beispiel einen Blick auf dieser letzten Antwort von Daniel Brückner:

.&.(?<! &) 

Zuerst Sie ein kaufmännisches zwischen zwei Zeichen erfassen. Als nächstes überprüfen Sie, dass beide nicht Leerzeichen waren (\S&\S würde hier nicht funktionieren, das OP wollte 1&_ erfassen).

+0

'[^] & [^]' ist wahrscheinlich einfacher zu verstehen als '. &. (? Gumbo

+2

Das passt nicht "this & that", während die Lookbehind-Version. Ein gültiges Äquivalent wäre gewesen: "\ S & | & \ S" –

4

Es ist einfacher, in einem Beispiel zu zeigen als erklären, denke ich. Lassen Sie uns diese regex nehmen:

(?<=\d)(?=(.)\1)(?!p)\w(?<!q) 

Was dies bedeutet, ist:

  1. (?<=\d) - sicher machen, was vor der Position eine Ziffer Spiel kommt.
  2. (?=(.)\1) - vergewissern Sie sich, dass dem Zeichen, das wir an dieser (gleichen) Position finden, eine Kopie von sich selbst folgt (durch die Rückreferenz).
  3. (?!p) - stellen Sie sicher, was folgt ist kein .
  4. \w - mit einem Buchstaben, einer Ziffer oder einem Unterstrich übereinstimmen. Beachten Sie, dass dies das erste Mal ist, dass wir den Charakter wirklich zusammenbringen und konsumieren.
  5. (?<!q) - stellen Sie sicher, was wir bis jetzt nicht mit einem q übereinstimmen übereinstimmen.

All dies wird Strings wie abc5ddx oder 9xx passen, aber nicht 5d oder 6qq oder asd6pp oder add. Beachten Sie, dass jede Assertion unabhängig arbeitet. Es hält einfach an, schaut sich um und wenn alles in Ordnung ist, kann das Matching fortgesetzt werden.

Beachten Sie auch, dass Lookbehinds in den meisten (wahrscheinlich allen) Implementierungen die Beschränkung haben, dass sie eine feste Länge haben. Sie können keine Wiederholungs-/Optionsoperatoren wie ?, * und + darin verwenden. Dies liegt daran, dass wir, um ein Muster zu finden, einen Startpunkt benötigen - andernfalls müssten wir versuchen, jeden Lookbehind von jedem Punkt in der Zeichenfolge abzugleichen.

  1. Text Cursorposition:

    Ein Probelauf dieser regex auf der Saite a3b5ddx wie folgt ist 0.

    1. Versuchen Sie, die erste Lookbehind an Position übereinstimmen -1 (seit \d immer Spiele 1 Zeichen). Wir können bei negativen Indizes nicht zusammenpassen, also scheitern und den Cursor vorwärts bewegen.
  2. Text Cursor-Position: 1.
    1. Versuchen Sie, die erste Lookbehind an Position passen 0 a nicht \d so passt den Cursor wieder fehlschlagen und fördern.
  3. Text Cursorposition: 2.
    1. Versuchen Sie, die erste Lookbehind an Position übereinstimmen 1. 3 passt \d halten so den Cursor intakt und Anpassung fortzusetzen.
    2. Versuchen Sie, den ersten Lookahead auf Position 2 zu finden. b entspricht (.) und wird erfasst. 5 stimmt nicht mit \1 überein (welches die erfasste b ist). Daher fehlschlagen und den Cursor vorrücken.
  4. Text Cursorposition: 3.
    1. Versuchen Sie, die erste Lookbehind an Position passen 2. b nicht \d so passt den Cursor wieder fehlschlagen und fördern.
  5. Text Cursorposition: 4.
    1. Versuchen Sie, die erste Lookbehind an Position übereinstimmen 3. 5 passt \d halten so den Cursor intakt und Anpassung fortzusetzen.
    2. Versuchen Sie, den ersten Lookahead auf Position 4 zu finden. d entspricht (.) und wird erfasst. Die zweite d entspricht \1 (die erste d gefangen). Lassen Sie das Matching von dort weiterlaufen, wo wir aufgehört haben.
    3. Versuchen Sie, den zweiten Lookahead zu finden. b an Position 4 passt nicht , und da dies ein negativer Lookahead ist, das ist was wir wollen; Erlaube dem Matching fortzufahren.
    4. Versuchen Sie, \w an Position 4 zu entsprechen. b entspricht. Bewegen Sie den Cursor, da wir ein Zeichen verbraucht haben und fortfahren. Markieren Sie dies auch als Beginn des Spiels.
  6. Text Cursorposition: 5.
    1. versuchen, die zweite an Position Lookbehind passen 4 (seit q immer 1 Zeichen übereinstimmt). d entspricht nicht q was wir von einem negativen Lookbehind wollen.
    2. Stellen Sie fest, dass wir uns am Ende der Regex befinden und melden Sie den Erfolg, indem Sie den Teilstring vom Anfang des Treffers an die aktuelle Position (4 bis 5) zurückgeben, also d.