Was ist der beste Weg, um einen Textkörper gegen mehr zu analysieren (15+) in jeder Zeile Regexes?

StackOverflow https://stackoverflow.com/questions/303830

Frage

Ich habe einen Körper von Text, den ich scannen und jede Zeile enthält mindestens 2 und manchmal vier Teile von Informationen. Das Problem ist, dass jede Zeile 1 von 15-20 verschiedenen Aktionen sein kann.

in rubin den aktuellen Code sieht etwas wie folgt aus:

text.split("\n").each do |line|  #around 20 times..

..............

      expressions['actions'].each do |pat, reg| #around 20 times

.................

Dies ist offensichtlich 'Das Problem'. Ich habe es geschafft, es schneller zu machen (in C ++ mit einer 50% Marge) durch alle regexen in einer Kombination, aber das ist noch nicht die Geschwindigkeit Ich brauche - ich brauche Tausende dieser Dateien zu analysieren FAST

Im Moment habe ich passe sie mit regulären Ausdrücken - aber dies unerträglich langsam. Ich begann mit Rubin und sprang über auf C ++, in der Hoffnung, dass ich einen Geschwindigkeitsschub bekommen würde und es ist einfach nicht passiert.

Ich habe auf PEGs und Grammatik basiert Parsing beiläufig gelesen, aber es sieht etwas schwierig zu implementieren. Ist das die Richtung, die ich fahren soll oder gibt es verschiedene Routen?

im Grunde ich Parsen Poker Hand Histories und jede Zeile der Hand Geschichte enthält in der Regel 2-3 Bits an Informationen, die ich sammeln müssen: die der Spieler war, wie viel Geld oder welche Karten die Aktion zur Folge .. etc ..

Beispieltext, die analysiert werden muss:

buriedtens posts $5
The button is in seat #4
*** HOLE CARDS ***
Dealt to Mayhem 31337 [8s Ad]
Sherwin7 folds
OneMiKeee folds
syhg99 calls $5
buriedtens raises to $10

Nachdem ich diese Informationen zu sammeln jede Aktion in einen XML-Knoten gedreht.

Im Moment meiner Ruby-Implementierung dieser ist viel schneller als meine C ++ ein, aber das ist prob. Nur weil ich nicht für gut über 4-5 Jahre in C-Code geschrieben

UPDATE: Ich möchte nicht den ganzen Code hier posten, aber so weit meine Hände / Sekunde wie folgt aussehen:

588 hands/second -- boost::spirit in c++
60 hands/second -- 1 very long and complicated regex in c++ (all the regexen put together)
33 hands/second -- normal regex style in ruby

Ich teste derzeit antlr zu sehen, ob wir weiter gehen können, aber von Rechts wegen jetzt bin ich sehr, sehr zufrieden mit dem Geist der Ergebnisse.

Verwandte Frage: Effizientes eine Saite gegen mehrere reguläre Ausdrücke Abfrage .

War es hilfreich?

Lösung

Ich würde vorschlagen,

Viel Glück

Andere Tipps

Boost.Spirit ist eine fantastische Bibliothek, die Sie detaillierte Parser Analyse machen können, und da der Parser erzeugt wird und direkt in Ihr Code kompiliert, sollte viel schneller als eine dynamisch berechnete Lösung sein. Die Syntax ist meist mit Ausdrucksvorlagen (eine originelle Bezeichnung für viele überladene Operatoren) durchgeführt, was bedeutet, Sie tatsächlich schreiben direkt in Ihren Code.

Hier ist eine Möglichkeit, es zu tun, wenn Sie Perl verwendet haben.
von perldoc perlfaq6

while (<>) {
    chomp;
    PARSER: {
        m/ \G( \d+\b    )/gcx   && do { print "number: $1\n";  redo; };
        m/ \G( \w+      )/gcx   && do { print "word:   $1\n";  redo; };
        m/ \G( \s+      )/gcx   && do { print "space:  $1\n";  redo; };
        m/ \G( [^\w\d]+ )/gcx   && do { print "other:  $1\n";  redo; };
    }
}

Für jede Zeile, die PARSER Schleife versucht zunächst, eine Reihe von Ziffern, die von einer Wortgrenze gefolgt übereinstimmen. Das Spiel ist an der Stelle des letzte Spiel aufgehört hat (oder den Anfang der Zeichenfolge auf dem ersten Spiel) zu starten. Da m/ \G( \d+\b )/gcx die c Flag verwendet, wenn die Zeichenfolge nicht, dass die regulären Ausdruck entspricht, perl nicht zurückgesetzt pos() und das nächste Spiel beginnt an der gleichen Stelle ein anderes Muster zu versuchen.

Siehe Regular Expression Matching einfach sein kann und schnell (Ist aber langsam in Java, Perl, PHP, Python, Ruby, ...) . Je nach Umfang Ihrer Daten und wie komplex Ihre regex sind, kann es nur schneller sein, eigene Parsing-Logik zu schreiben.

  

Ich habe auf PEGs und Grammatik basiert Parsing beiläufig gelesen, aber es sieht etwas schwierig zu implementieren. Ist das die Richtung, die ich fahren soll oder gibt es verschiedene Routen?

Persönlich ich habe die Liebe PEGs gewachsen. Es wird vielleicht ein bisschen nehmen mit ihnen bequem zu machen, aber ich denke, sie so viel besser verwaltbar sind, dass es ein klarer Sieg ist. Ich finde Code Parsen die Quelle einer Menge unerwarteter Fehler ist, wie Sie neue Grenzfälle in Eingängen finden. Deklarative Grammatiken mit nonterminals sind für mich einfacher zu aktualisieren, wenn diese in einer Schleife und Bedingung schwerer regex Code verglichen passiert. Naming ist mächtig.

In Ruby gibt es Treetop , die einen Parser-Generator ist, die PEGs verwendet. Vor kurzem fand ich angenehm es ganz in einen regulären Ausdruck schwere Hand geschrieben Parser mit einer kurzen Grammatik zu ersetzen.

Sie der reguläre Ausdruck überhaupt überlappen? Das heißt, wenn zwei oder mehr reguläre Ausdrücke die gleiche Linie entsprechen, tun immer sie verschiedene Teile der Linie (keine Überlappung) überein?

Wenn die Spiele nicht überlappen, führen Sie Ihre Suche regulären Ausdruck, der die 15 Regexes verbindet Sie haben jetzt:

regex1|regex2|regex3|...|regex15

Verwenden Sie einfangende Gruppen, wenn Sie, welche der 15 Regexes angepasst Lage sein müssen, zu bestimmen.

Suchen Sie Ihre Daten einmal für eine lange regex wird schneller sein als die Suche es 15-mal. Wie viel schneller ist abhängig von der Regex-Engine Sie verwenden und die Komplexität Ihrer regulären Ausdrücken.

einen einfachen Test in Perl Versuchen. Lesen Sie über die „Studie“ -Funktion. Was ich versuchen könnte, ist:

  • Lesen Sie die gesamte Datei oder eine große Anzahl von Zeilen, wenn diese Dateien sehr groß, um in einem einzigen String sind
  • eine Zeilennummer an den Anfang jeder Zeile hinzufügen, wie Sie gehen.
  • "Studie" der String. Dies baut eine Lookup-Tabelle von Zeichen, kann groß sein.
  • Ausführen regulärer Ausdruck auf der Saite, begrenzt durch Zeilenumbrüche (verwenden Sie den m und s regex Modifikatoren). Der Ausdruck sollte die Zeilennummer zusammen mit den Daten extrahiert.
  • ein Array-Element durch die Zeilennummer auf dieser Linie gefunden auf die Daten indiziert Set oder etwas tun, noch intelligenter.
  • Schließlich können Sie die Daten in dem Array gespeichert verarbeiten.

Ich habe es nicht ausprobiert, aber es könnte interessant sein.

Eine andere Idee, wenn Sie eine spiffy Quad oder Oktober Core-Server für diese zu verwenden.

Erstellen Sie eine Verarbeitungspipeline, die die Arbeit teilt aus. Stage One könnte schneiden Dateien in ein Spiel oder Hand jedem, dann schreiben Sie jeder zu einem der acht Stufe Zwei Rohre, die die Daten zu lesen, zu verarbeiten und produzieren Ausgabe irgendwie, wahrscheinlich mit einer Datenbank auf einem anderen Rechner.

Nach meiner Erfahrung dieser Rohre basierten Multi-Prozess-Designs sind fast so schnell und viel einfacher zu debuggen als Multi-Threading-Design. Es wäre auch einen Cluster von Maschinen einfach sein, Netzwerk-Sockets anstelle von Rohren unter Verwendung einzurichten.

OK, macht diese Dinge klarer (Poker Hand Histories). Ich denke, dass Sie ein Statistik-Tool machen (Aggressionsfaktor ging, zum Showdown freiwillig $ in dem Topf usw. setzen). Ich bin nicht sicher, warum Sie überhöhte Geschwindigkeit dafür brauchen; auch wenn Sie mit 16 Tabellen Multitabling, sollten nur die Hände mit einer moderaten Geschwindigkeit kitzeln in.

Ich weiß nicht, Rubin, aber in Perl würde ich eine wenig switch-Anweisung, zur gleichen Zeit wie immer die wesentliche Teile in $ 1, $ 2 usw. tun .. Meiner Erfahrung nach ist dies nicht langsamer als String Vergleiche und dann Spaltung der Linie mit anderen Mitteln.

HAND_LINE: for ($Line)
    { /^\*\*\* ([A-Z ]+)/ and do 
        { # parse the string that is captured in $1
          last HAND_LINE; };
      /^Dealt to (.+) \[(.. ..)\]$/ and do
        { # $1 contains the name, $2 contains the cards as string
          last HAND_LINE; };
      /(.+) folds$/ and do
        { # you get the drift
          last HAND_LINE; }; };

ich nicht denken, dass Sie es wirklich schneller machen kann. Setzen Sie die Kontrollen für die Linien, die am meisten an einer ersten Position auftritt (wahrscheinlich die Falten Aussagen) und solche, die nur spärlich zuletzt auftreten (beginnend neue Hand, "*** NEXT PHASE ***").

Wenn Sie herausfinden, dass die eigentliche Datei Lesen ein Engpass ist, können Sie vielleicht einen Blick auf, welche Module Sie große Dateien adressieren können; für Perl, kommt Tie::File in dem Sinne.

Stellen Sie sicher, dass Sie jede Hand lesen nur einmal. Nicht alle Daten wieder nach jeder Hand lesen, anstatt halten z.B. eine Hash-Tabelle der Hand IDs bereits analysiert.

Für ein Problem wie dieses, würde ich nur meine Augen schließen und einen Lexer + Parser-Generator verwenden. Sie können sich wahrscheinlich, dass mit der Hand-Optimierung schlagen, aber es ist viel einfacher, einen Generator zu verwenden. Außerdem ist es Art und Weise flexibler, wenn der Eingang plötzlich ändert.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top