Schneller regulärer Ausdruck, der die Häufigkeit prüft, in der ein Zeichen in einer Zeile in Vim angezeigt wird.

914
drapkin11

Angenommen, ich habe eine durch Pipe getrennte Textdatei. Ich vermute, dass eine der Spalten ein eingebettetes Pipe-Zeichen ('|') haben kann. Ich weiß, es gibt 8 Spalten in der Datei, und jede Zeile sollte haben8-1 = 7 Pipe-Zeichen enthalten. Daher muss ich alle Zeilen finden, die 8 oder mehr '|' enthalten. Zeichen.

Der folgende Regex sollte alle derartigen Fälle finden, aber es dauert zu lange, bis ich meine 200.000-Datensatzdatei wiedergegeben habe:

^\(.*|.*\)\$ 

Gibt es einen schnelleren Regex, den ich stattdessen verwenden sollte? Zu lange meine ich länger, als ich erwartet hätte - mindestens einige Minuten. Dies ist keine so große Datei (200K-Datensätze), daher gehe ich davon aus, dass der Regex selbst nicht effizient ist.


Einige Beispieldaten:

SAMPLE_ID|GROUPS|ADDRESSSTRING|LATITUDE|LONGITUDE|COUNTRYCODE|LANGUAGECODE|ISO_2_LTR_CODE 7304094||Rhein-Galerie;Baden-Württemberg|49.48334|8.45007|DEU|ger|DE 7303851||Steigenberger Insel;Baden-Württemberg|47.69005|9.18812|DEU|ger|DE 7303850||Si-Suites;Baden-Württemberg|48.72309|9.16138|DEU|ger|DE 

(Ich verwende gVim unter WinXP)

1
[Chris Johnsen] (http://superuser.com/questions/264213/fast-regular-expression-that-checks-the-number-of- times-a-character-appears-on-a/264292#264292) " s Regex gibt Übereinstimmungen in 1 Sekunde zurück. [celebdor] (http://superuser.com/questions/264213/fast-regular-expression-that-checks-the-umber-of-times-a-character-appears-on-a/264225#264225) 's Regex gibt Spiele in 1 Minute zurück. Mein ursprünglicher Regex kam * deutlich * später zurück. drapkin11 vor 13 Jahren 0

2 Antworten auf die Frage

2
Chris Johnsen

Ihr Regex ist anfällig für das Verhalten von O (N ^ 2) des Regex-Moduls "Backtracking", das in Vim (und vielen anderen Sprachen und Umgebungen) verwendet wird.

Glücklicherweise gibt es Möglichkeiten, äquivalente Ausdrücke zu schreiben, die kein übermäßiges Backtracking verursachen. Zum Beispiel:

/^\([^|]*|\)\.*$ 

Im Allgemeinen müssen Sie nicht auf "acht oder mehr" passen, denn wenn Sie bereits wissen, ist die Zeile problematisch, wenn sie acht hat (ob sie mehr hat oder nicht).

Wenn Sie tatsächlich die gesamte Zeile anpassen müssen (z. B. weil sie Teil einer :sOperation ist), müssen Sie den letzten Teil ( .*$) beibehalten. Wenn Sie nur den Regex verwenden, um die "acht oder mehr" Zeilen zu finden, können .*$Sie das Ende beenden.

Außerdem empfehle ich, nur eine Seite der Pipe innerhalb der Gruppe zu suchen, die Sie wiederholen. Dies vereinfacht sowohl das Nachdenken darüber, wie der Regex mit den Zeilen übereinstimmt, als auch, wie die Regex-Engine selbst ausgeführt wird (eine Rückverfolgungsquelle wird dadurch eliminiert).


Nun, um das bisschen über "Backtracking" zu erklären. Stellen Sie sich vor, Sie haben eine Zeile mit acht Pipe-Zeichen:

aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh 

In der folgenden Passage wird beschrieben, wie die Regex-Engine versucht, Ihren Ausdruck mit der obigen Zeile abzugleichen (Ich habe den Regex-Zeilen zusätzlichen Leerraum hinzugefügt, um (ungefähr) anzuzeigen, wo Teile des Regex mit den Zeichen der Zeile selbst übereinstimmen).

Der erste .*ist gierig und passt alles bis zum Ende der Zeile an, so dass das Pipe-Zeichen unübertroffen bleibt.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* | 

Der jüngste "schrumpfbare" Abgleich gibt einen Teil des Abgleichs auf und versucht den Rest des regulären Ausdrucks erneut. Dies geschieht in diesem Fall jeweils .einzeln (da jedes einzelne Zeichen übereinstimmt). Dieses Backtracking wird fortgesetzt, bis der Rest des Ausdrucks übereinstimmen kann (oder bis zum Anfang zurückgespult wird. Dies ist die einzige Möglichkeit, dass die Zeile dem Ausdruck nicht entspricht!).

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*| 

Der erste .*Schritt war also genug, um den Rest der Gruppe zu ermöglichen, aber es gab nichts, was der zweiten Gruppe entsprach. Zeit, etwas mehr zurückzuverfolgen.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*| 

Das Backtracking hat einen neuen „Haltepunkt“ gefunden, aber jetzt macht der zweite .*in der ersten Gruppe sein gieriges Matching. Die zweite Gruppe stimmt nicht überein. Die Rückverfolgung der Sekunde .*in der ersten Gruppe beginnt.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.* )(.*| 

Die zweite Gruppe hat eine Übereinstimmung gefunden, die dritte Gruppe jedoch nicht. Backtrack nochmal, beginnend mit dem aktuelleren Spiel. Die zweite .*der zweiten Gruppe geht zurück, um nichts zu erreichen. Die erste .*Gruppe der zweiten Gruppe geht zu nichts zurück. Die zweite .*Gruppe der ersten Gruppe geht auf nichts zurück. Die erste .*der ersten Gruppe zieht sich erfolgreich zurück.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*| 

Aber die zweite .*ist gierig, so dass nichts für die zweite Gruppe übrig bleibt.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.*)(.*|.* )(.*| 

Möglicherweise stimmen alle drei Gruppen überein, aber die vierte Instanz der Gruppe schlägt fehl. Beginnen Sie mit dem Backtracking.

 aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.* )(.*|.*)(.*|.* )(.*| aaaaaa|bbbbbb|cccccc|dddddd|eeeeee|ffffff| gg | gg |hhhhhh ^(.* |.*)(.*|.*)(.*|.*)(.*|.* )(.*| 

Sie können sehen, wie viel Zeit verbrennt (die Diagramme überspringen sogar die tatsächlich auftretende Zeichen-für-Zeichen-Rückverfolgung; oben werden nur die "hohen Punkte" angezeigt). Das Problem entsteht dadurch, dass ein früher Teil des Regex gierig mit etwas übereinstimmt, mit dem der spätere Teil des Regex eventuell übereinstimmen muss, um die richtige Anzahl von Wiederholungen der Gruppe zu erhalten.

In meinem Ausdruck [^|]*stimmt jede Wiederholung ( ) nie mit dem überein, auf das das folgende Element ( |) passen würde, daher ist das Backtracking rein linear. Sobald das Backtracking für jede "schrumpfbare" Übereinstimmung beginnt, wird es (in linearer Zeit) feststellen, dass es keine früheren Stellen gibt, an denen der folgende Ausdruck passen kann. Dies zwingt das Backtracking dazu, mit dem vorherigen "schrumpfbaren" Spiel fortzufahren, bis nichts übereinstimmt und die gesamte Linie als nicht übereinstimmend gilt.

Anstelle von "null oder mehr Nichtpipe, dann Pipe" ( [^|]*|) kann auch .eine explizite nicht-gierige Wiederholung verwendet werden ( \{-}in Vim variiert diese; andere Regex-Sprachen werden verwendet *?).

^\(.\{-}|\)\.*$ 
Dies ist eine ausgezeichnete Erklärung - danke! drapkin11 vor 13 Jahren 0
1
celebdor

In meinem Computer ist das schneller:

:%s/\(|.\{-}\)\//n 
Ja, das gleiche auf meinem Computer. Diese Regex macht eine phänomenal bessere Arbeit. drapkin11 vor 13 Jahren 0