Blick hinter die Kulissen: die ganze Wut in Regex?

8

Viele Regex-Fragen haben in letzter Zeit irgendeine Art von Umgehungselement in der Abfrage, das für den Erfolg des Matches nicht notwendig erscheint. Gibt es eine Unterrichtsquelle, die sie fördert? Ich versuche herauszufinden, welche Arten von Fällen Sie besser mit einem positiven Blick nach vorne / hinten wäre. Die Hauptanwendung, die ich sehen kann, ist, wenn ich versuche, nicht mit einem Element übereinzustimmen. Aber, zum Beispiel, diese Abfrage von einer letzten Frage hat eine einfache Lösung für die Erfassung der .* , aber warum würden Sie einen Blick zurück verwenden?

%Vor%

Und dieser von einer anderen Frage:

%Vor%

Wann ist es wirklich besser, einen positiven Look-Around zu verwenden? Können Sie einige Beispiele nennen?

Mir ist klar, dass dies an eine meinungsbezogene Frage grenzt, aber ich denke, die Antworten wären wirklich lehrreich. Regex ist verwirrend genug, ohne die Dinge komplizierter zu machen ... Ich habe diese Seite gelesen und interessiere mich mehr für etwas Einfaches Richtlinien dafür, wann sie verwendet werden sollen und nicht wie sie funktionieren.

Danke für alle Antworten. Zusätzlich zu den unten genannten, empfehle ich m.buettners große Antwort hier.

    
beroe 30.09.2013, 22:58
quelle

8 Antworten

8
  1. Sie können überlappende Übereinstimmungen erfassen, und Sie können Übereinstimmungen finden, die in den Augenblicken anderer Übereinstimmungen liegen könnten.
  2. Sie können komplexe logische Assertions zu Ihrer Übereinstimmung ausdrücken (da viele Engines die Verwendung mehrerer Lookbehind- / Lookahead-Assertionen ermöglichen, die alle übereinstimmen müssen, damit die Übereinstimmung erfolgreich ist).
  3. Lookaround ist ein natürlicher Weg, um die allgemeine Einschränkung auszudrücken "passt X, wenn es von / gefolgt von Y gefolgt wird". Es ist (natürlich) weniger natürlich, zusätzliche "übereinstimmende" Teile hinzuzufügen, die durch Nachbearbeitung ausgeworfen werden müssen.

Negative Lookaround-Assertionen sind natürlich noch nützlicher. Kombiniert mit # 2 können Sie damit einige schöne Zaubertricks machen, die in der üblichen Programmlogik schwer auszudrücken sind.

Beispiele, auf vielfachen Wunsch:

  • Überlappende Übereinstimmungen: Angenommen, Sie möchten alle Kandidatengene in einer bestimmten genetischen Sequenz finden. Gene beginnen im Allgemeinen mit ATG und enden mit TAG, TAA oder TGA. Aber Kandidaten könnten sich überschneiden: Fehlstarts können existieren. So können Sie eine Regex wie folgt verwenden:

    %Vor%

    Dieser einfache Regex sucht nach dem ATG-Startcodon, gefolgt von einer Anzahl von Codons, gefolgt von einem Stopcodon. Es zieht alles heraus, was wie ein Gen aussieht (ohne Startcodon), und gibt Gene korrekt aus, auch wenn sie sich überlappen.

  • Zero-width matching: Angenommen, Sie möchten jedes tr mit einer bestimmten Klasse in einer computergenerierten HTML-Seite finden. Sie könnten so etwas tun:

    %Vor%

    Dies bezieht sich auf den Fall, in dem eine leere </tr> in der Zeile erscheint. (Natürlich ist im Allgemeinen ein HTML-Parser eine bessere Wahl, aber manchmal braucht man nur etwas, das schnell und schmutzig ist).

  • Mehrere Einschränkungen: Angenommen, Sie haben eine Datei mit Daten wie id:tag1,tag2,tag3,tag4 , mit Tags in beliebiger Reihenfolge, und Sie möchten alle Zeilen mit den Tags "green" und "egg" finden. Dies kann leicht mit zwei Lookaheads durchgeführt werden:

    %Vor%
nneonneo 30.09.2013, 23:02
quelle
4

Es gibt zwei großartige Dinge über Lookaround-Ausdrücke :

  • Sie sind Assertionen mit der Breite null. Sie müssen übereinstimmen, aber sie verbrauchen nichts von der Eingabezeichenfolge. Dadurch können Teile der Zeichenfolge beschrieben werden, die nicht in einem Übereinstimmungsergebnis enthalten sind. Durch die Verwendung von Erfassungsgruppen in Lookaround-Ausdrücken sind sie die einzige Möglichkeit, Teile der Eingabe mehrfach zu erfassen.
  • Sie vereinfachen viele Dinge. Während sie keine regulären Sprachen erweitern , erlauben sie leicht, mehrere Ausdrücke zu kombinieren (zu schneiden), um mit dem gleichen Teil eines Strings übereinzustimmen.
Bergi 30.09.2013 23:30
quelle
1

Nun, ein einfacher Fall, in dem sie nützlich sind, ist, wenn Sie das Muster am Anfang oder Ende einer Linie verankern und nur sicherstellen wollen, dass etwas entweder direkt vor oder hinter dem Muster liegt, das Sie zusammenpassen.

>     
Emil Davtyan 30.09.2013 23:40
quelle
1

Ich versuche, Ihre Punkte anzusprechen:

  • Irgendeine Art von Suchelement in der Abfrage, die mir erscheint, ist für den Erfolg der Übereinstimmung nicht notwendig

    Natürlich sind sie für das Spiel notwendig. Sobald eine Vorschau der Assertions fehlschlägt, gibt es keine Übereinstimmung. Sie können verwendet werden, um Bedingungen um das Muster herum sicherzustellen, die zusätzlich wahr sein müssen. Die ganze Regex passt nur, wenn:

    1. Das Muster passt zu und

    2. Die Lookaround-Assertions sind wahr.

    == & gt; Aber die zurückgegebene Übereinstimmung ist nur das Muster.

  • Wann ist es wirklich besser, einen positiven Umweg zu benutzen?

    Einfache Antwort: Wenn Sie möchten, dass etwas dabei ist, aber Sie wollen es nicht anpassen!

    Wie Bergi in seiner Antwort erwähnt , sind sie Behauptungen mit der Breite null, das heißt sie passen nicht zusammen eine Zeichenfolge, sie stellen nur sicher, dass es da ist. Damit die Zeichen in einem Lookaround-Ausdruck nicht "konsumiert" werden, wird die Regex-Engine nach dem letzten "konsumierten" Zeichen fortgesetzt.

  • Bezüglich Ihres ersten Beispiels:

    %Vor%

    Ich denke, es gibt ein Missverständnis auf Ihrer Seite, wenn Sie schreiben " hat eine einfache Lösung zum Erfassen der .* ". Das .* wird nicht "eingefangen", es ist das einzige, was der Ausdruck passt. Aber es werden nur die Charaktere gefunden, die vorher ein " <td><a href="\/xxx\.html\?n=[0-9]{0, 5}"> " und ein " <\/a><span " haben ( diese beiden sind nicht Teil des Matches! ).

    "Captured" ist nur etwas, das von einer Erfassungsgruppe abgeglichen wurde.

  • Das zweite Beispiel

    %Vor%

    Ist interessant. Es entspricht einer Ziffernfolge ( \d+ ) und nach der Sequenz überprüft die Lookbehind-Assertion, ob eine Ziffer mit "id /" davor steht. Bedeutet, dass es fehlschlägt, wenn es mehr als eine Ziffer gibt oder wenn der Text "id /" vor der Ziffer fehlt. Bedeutet, dass diese Regex nur eine Ziffer abgleicht, wenn vorher passender Text vorhanden ist.

  • Unterrichtsressourcen

stema 09.10.2013 08:48
quelle
1

Ich gehe davon aus, dass Sie die guten Verwendungen von Lookarounds verstehen und fragen, warum sie ohne ersichtlichen Grund verwendet werden.

Ich denke, es gibt vier Hauptkategorien, in denen Menschen reguläre Ausdrücke verwenden:

Validierung
Die Validierung erfolgt normalerweise an dem gesamten Text . Lookarounds wie Sie beschreiben sind nicht möglich.

Übereinstimmung
Extrahieren von ein Teil des Textes. Lookarounds werden hauptsächlich wegen der Faulheit der Entwickler verwendet: Vermeiden von Captures .
Zum Beispiel, wenn wir in einer Einstellungsdatei mit der Zeile Index=5 haben, können wir /^Index=(\d+)/ zusammenbringen und die erste Gruppe nehmen oder /(?<=^Index=)\d+/ zusammenbringen und alles übernehmen.
Wie andere Antworten sagten, manchmal brauchen Sie Überschneidungen zwischen den Übereinstimmungen, aber diese sind relativ selten.

Ersetzen
Dies ist vergleichbar mit einem Unterschied: Die ganze Übereinstimmung wird entfernt und wird durch eine neue Zeichenfolge (und einige erfasste Gruppen) ersetzt.
Beispiel: Wir möchten den Namen in "Hi, my name is Bob!" hervorheben.
Wir können /(name is )(\w+)/ durch <b></b> ersetzen aber es ist besser, /(?<=name is )\w+/ durch <b>$&</b> zu ersetzen - und überhaupt keine Captures.

Teilen
split nimmt den Text und zerlegt ihn in ein Token-Array, wobei das Muster das Trennzeichen ist. Dies geschieht durch:

  • Finde eine match . Alles vor diesem Match ist ein Token.
    • Der Inhalt der Übereinstimmung wird verworfen, aber:
    • In den meisten Varianten ist jede erfasste Gruppe in der Übereinstimmung auch ein Token (insbesondere nicht in Java).
  • Wenn es keine Übereinstimmungen mehr gibt, ist der Rest des Textes das letzte Token.

Hier sind Lookarounds entscheidend . Ein Zeichen zu finden bedeutet, es aus dem Ergebnis zu entfernen oder es zumindest von seinem Token zu trennen.
Beispiel: Wir haben eine durch Kommas getrennte Liste von Zeichenketten in Anführungszeichen: "Hello","Hi, I'm Jim."
Die Aufspaltung durch Komma /,/ ist falsch: { "Hello" , "Hi , I'm Jim." }
Wir können das Anführungszeichen /",/ nicht hinzufügen: { "Hello , "Hi, I'm Jim." }
Die einzige gute Option ist Lookbehind, /(?<="),/ : { "Hello" , "Hi, I'm Jim." }

Persönlich bevorzuge ich es, die Token zu vergleichen, anstatt sie durch das Trennzeichen aufzuteilen, wann immer das möglich ist.

Fazit

Um die Hauptfrage zu beantworten - diese Lookarounds werden verwendet, weil:

  • Manchmal können Sie nicht mit
  • übereinstimmenden Text übereinstimmen.
  • Entwickler sind flink.
Kobi 09.10.2013 12:31
quelle
1

Lookaround assertions kann auch verwendet werden, um backtracking zu reduzieren, was die Hauptursache für eine schlechte Leistung in Regexes sein kann.

Zum Beispiel: Die Regex ^[0-9A-Z]([-.\w]*[0-9A-Z])*@ (1) kann auch geschrieben werden ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@ (2) mit einem positiven Blick zurück (einfache Überprüfung des Benutzernamens in einer E-Mail-Adresse).

Regex (1) kann eine Menge Backtracking verursachen, weil [0-9A-Z] eine Untermenge von [-.\w] und die verschachtelten Quantifizierer ist. Regex (2) reduziert das übermäßige Backtracking, mehr Informationen finden Sie hier Backtracking , Abschnitt Kontrollieren von Backtracking & gt; Lookbehind Assertions .

Weitere Informationen zu backtracking

polkduran 10.10.2013 12:40
quelle
1

Ich tippte das vor einer Weile ein, war aber beschäftigt (immer noch, also könnte es eine Weile dauern, bis ich antwortete) und kam nicht dazu, es zu posten. Wenn Sie noch offen für Antworten sind ...

  

Gibt es eine Unterrichtsquelle, die sie fördert?

Ich denke nicht, es ist nur ein Zufall, glaube ich.

  

Aber, zum Beispiel, diese Abfrage von einer letzten Frage hat eine einfache Lösung zum Erfassen der .* , aber warum würden Sie einen Blick dahinter verwenden?

%Vor%

Dies ist höchstwahrscheinlich ein C # Regex, da Lookbacks mit variabler Breite von meinen vielen Regex-Engines nicht unterstützt werden. Nun, die Lookarounds könnten hier sicherlich vermieden werden, weil ich glaube, dass es wirklich einfacher ist, Capture-Gruppen zu haben (und .* faul machen, wenn wir gerade dabei sind):

%Vor%

Wenn es um einen Ersatz geht, oder

%Vor%

für ein Spiel. Obwohl ein HTML-Parser wäre hier auf jeden Fall ratsam.

Lookarounds in diesem Fall glaube ich sind langsamer. Weitere Informationen finden Sie unter regex101-Demo , wobei die Übereinstimmung für Capture-Gruppen 64 Schritte umfasst, für die Lookarounds jedoch 94 + 19 = 1-3 Schritte.

>
  

Wann ist es wirklich besser, einen positiven Look-Around zu verwenden? Können Sie einige Beispiele nennen?

Nun, Lookarounds haben die Eigenschaft, Assertionen mit einer Breite von Null zu sein, was bedeutet, dass sie nicht wirklich zu Übereinstimmungen beitragen, während sie dazu beitragen, zu entscheiden, was zutreffen soll und auch überlappende Übereinstimmungen zulässt.

Wenn ich ein wenig darüber nachdenke, denke ich auch, dass negative Blickwinkel viel häufiger verwendet werden, aber das macht positive Blicke nicht weniger nützlich!

Einige 'Exploits', die ich finden kann, indem ich einige meiner alten Antworten suche (die Links unten sind die Demos von regex101), folgen. Wenn / Wenn Sie etwas sehen, das Ihnen nicht bekannt ist, werde ich es hier wahrscheinlich nicht erklären, da sich die Frage auf positive Blicke konzentriert, aber Sie können immer die Demo-Links betrachten, die eine Beschreibung der Regex enthält, und wenn du noch eine Erklärung willst, lass es mich wissen und ich werde versuchen, so viel wie möglich zu erklären.

Um Übereinstimmungen zwischen bestimmten Zeichen zu erhalten:

Positive Lookahead machen es in manchen Matches einfacher, wo ein Lookahead gut läuft, oder wenn es nicht so praktisch ist, keine Lookarounds zu verwenden:

  

Hund seufzte. "Ich bin kein super Hund, kein besonderer Hund", sagte Hund, "ich bin ein gewöhnlicher Hund, lass mich jetzt in Ruhe!" Hund schubste ihn weg und machte sich auf den Weg zu dem anderen Hund.

Wir möchten, dass alle dog (unabhängig von der Groß- / Kleinschreibung) außerhalb von Anführungszeichen stehen. Mit einer positiven Vorausschau können wir dies tun:

%Vor%

, um sicherzustellen, dass es eine bestimmte Anzahl von Anführungszeichen gibt. Bei einem negativen Lookahead würde es wie das aussehen:

%Vor%

, um sicherzustellen, dass keine ungerade Anzahl von Anführungszeichen vorhanden ist. Oder verwenden Sie etwas wie dies , wenn Sie kein Lookahead möchten, aber Sie müssen die Übereinstimmungen der Gruppe 1 extrahieren:

%Vor%

Okay, sagen wir jetzt, wir wollen das Gegenteil; Finde 'Hund' innerhalb die Zitate. Die Regex mit den Lookarounds müssen nur das Zeichen invertiert haben, zuerst und Sekunde :

%Vor%

Aber ohne die Lookaheads ist es nicht möglich. Am nächsten kommt vielleicht das :

%Vor%

Aber das bringt nicht alle Treffer, oder Sie können das verwenden:

%Vor%

Aber es ist einfach nicht praktisch für mehr Vorkommen von dog und Sie erhalten die Ergebnisse in Variablen mit steigenden Zahlen ... Und das ist in der Tat einfacher mit Umblicken, weil sie Zusicherungen der Breite Null sind, müssen Sie sich keine Sorgen machen über den Ausdruck innerhalb des Lookarounds, um dog zu entsprechen oder nicht, oder der Regex hätte nicht alle Vorkommen von dog in den Anführungszeichen erhalten.

Natürlich kann diese Logik nun auch auf Gruppen von Zeichen erweitert werden, beispielsweise um bestimmte Muster zwischen Wörtern wie start und end zu erhalten.

überlappende Übereinstimmungen

Wenn Sie eine Zeichenfolge wie:

haben %Vor%

Und Sie möchten alle drei aufeinanderfolgenden möglichen Zeichen im Inneren extrahieren, können Sie das verwenden:

%Vor%

Wenn Sie etwas wie:

haben %Vor%

Und möchte diese extrahieren, wissend, dass jede Zeile mit #A Line# beginnt (wobei # eine Zahl ist):

%Vor%

Sie könnten dies ausprobieren, was wegen der Gierigkeit fehlschlägt ...

%Vor%

Oder das , was bei Faulheit nicht mehr funktioniert ...

%Vor%

Aber mit einem positiven Lookahead erhalten Sie dies :

%Vor%

Und extrahiert entsprechend, was benötigt wird.

Eine andere mögliche Situation ist eine, in der Sie so etwas haben:

%Vor%

Was Sie in drei Variablenpaare konvertieren möchten (das erste Paar ist ein # und einige Hexadezimalwerte (6) und die folgenden Zeichen):

%Vor%

Wenn es keine Hashes in den 'Worten' gäbe, wäre es ausreichend gewesen, (#[0-9a-f]{6})([^#]+) zu verwenden, aber leider ist das nicht der Fall, und Sie müssen zu .*? statt zu [^#]+ greifen, was nicht funktioniert. t das Problem der Streushashes noch nicht gelöst. Positive Lookaheads machen dies jedoch möglich :

%Vor%


Validierung & amp; Formatierung

Nicht empfohlen, aber Sie können positive Lookaheads für schnelle Validierungen verwenden. Die folgende Regex beispielsweise ermöglicht die Eingabe eines Strings, der mindestens 1 Ziffer und 1 Kleinbuchstaben enthält.

%Vor%

Dies kann nützlich sein, wenn Sie nach Zeichenlänge suchen, aber Muster mit unterschiedlicher Länge in der a-Zeichenfolge haben, z. B. eine 4 Zeichen lange Zeichenfolge mit gültigen Formaten, wobei # eine Ziffer und den Bindestrich / Bindestrich angibt. minus - muss in der Mitte sein:

%Vor%

Eine Regex wie dies macht den Trick:

%Vor%

Wo sonst würden Sie ^(?:[0-9]{2}-[0-9]|[0-9]-[0-9]{2})$ machen und sich jetzt vorstellen, dass die maximale Länge 15 ist; die Anzahl der Änderungen, die Sie benötigen.

Wenn Sie eine schnelle und schmutzige Möglichkeit haben möchten, einige Daten im "durcheinander geratenen" Format mmm-yyyy und yyyy-mm in ein einheitlicheres Format mmm-yyyy umzuordnen, können Sie das :

%Vor%

Eingabe:

%Vor%

Ausgabe:

%Vor%

Eine Alternative könnte darin bestehen, eine Regex (normale Übereinstimmung) zu verwenden und alle nichtkonformen Formate separat zu verarbeiten.

Etwas anderes, auf das ich bei SO gestoßen bin, war das indische Währungsformat , welches ##,##,###.### (3 Ziffern zum links von der Dezimalzahl und alle anderen Ziffern paarweise gruppiert). Wenn Sie eine Eingabe von 122123123456.764244 haben, erwarten Sie 1,22,12,31,23,456.764244 und wenn Sie eine Regex verwenden möchten, tut dies diese :

%Vor%

(Das (?:\G|^) im Link wird nur verwendet, weil \G nur am Anfang des Strings und nach einem Match übereinstimmt) und ich denke nicht, dass dies ohne den positiven Lookahead funktionieren könnte, da es ohne nach vorne schaut Verschieben des Ersatzpunktes.)

Trimmen

Angenommen, Sie haben:

%Vor%

Und möchte alle Räume mit einer einzigen Regex trimmen. Sie könnten versucht sein, einen generellen Platzhalter zu ersetzen:

%Vor%

Aber das ergibt thisisasentence . Nun, vielleicht durch ein einzelnes Leerzeichen ersetzen? Es ergibt nun "das ist ein Satz" (doppelte Anführungszeichen, da Backticks Leerzeichen verbrauchen). Etwas, das Sie jedoch tun können, ist das :

%Vor%

Was sicherstellt, dass man ein Leerzeichen zurücklässt, damit man nichts ersetzen kann und "das ist ein Satz".

Teilen

Nun, wo positive Lookarounds nützlich sein können, ist wo, sagen wir, Sie haben eine Zeichenkette ABC12DE3456FGHI789 und möchten die Buchstaben + Ziffern auseinander halten, das heißt, Sie wollen ABC12 , DE3456 und% co_de bekommen %. Sie können einfach die Regex verwenden:

%Vor%

Wenn Sie FGHI789 verwenden (d. h. die erfassten Gruppen werden in die resultierende Liste / array / etc zurückgelegt, erhalten Sie auch leere Elemente.

Beachten Sie, dass dies auch mit einer Übereinstimmung mit ([A-Z]+[0-9]+)

geschehen könnte

Wenn ich negative Blickwinkel erwähnen müsste, wäre dieser Beitrag noch länger gewesen:)

    
Jerry 28.10.2013 07:43
quelle
0

Beachten Sie, dass ein positiver / negativer Lookaround für eine Regex-Engine derselbe ist. Das Ziel von Lookarounds ist es, irgendwo in Ihrem "regulären Ausdruck" einen Check durchzuführen.

Eines der Hauptinteressen besteht darin, etwas zu erfassen, ohne Klammern zu verwenden (das ganze Muster einzufangen), Beispiel:

string: aaabbbccc

regex: (?<=aaa)bbb(?=ccc)

(Sie erhalten das Ergebnis mit dem gesamten Muster)

anstelle von: aaa(bbb)ccc

(Sie erhalten das Ergebnis mit der einfangenden Gruppe.)

    
Casimir et Hippolyte 30.09.2013 23:34
quelle