Micha: Geschwindigkeitsproblem durch regulären Ausdruck?

Hallo,

ich lese Dateien ein und prüfe die einzelnen Zeilen mit zwei regulären Ausdrücken.

Eine Zeile kann folgenden Aufbau haben:

<String><String><Double><Double><Double><Double>

wobei der letzte Double-Wert optional ist (und nun wohl das Problem ist).

Meine Strategie war nun, prüfe die Zeile auf den vollständigen Inhalt, wenn dies fehlschlägt, prüfe, ob der optionale Wert nicht dabei ist.

  
  
// Strecke mit Standardabweichung  
String withRMSRegExp = new String("(\\S+).+?(\\S+).+?(-{0,1}\\d+\\.\\d+).+?(-{0,1}\\d+\\.\\d+).+?(-{0,1}\\d+\\.\\d+).+?(-{0,1}\\d+\\.\\d+)(.*)");  
  
// Strecke ohne Standardabweichung  
String withOutRMSRegExp = new String("(\\S+).+?(\\S+).+?(-{0,1}\\d+\\.\\d+).+?(-{0,1}\\d+\\.\\d+).+?(-{0,1}\\d+\\.\\d+)(.*)");  
  
// Pruefe String auf Vollstaendigkeit  
if (str.matches( withRMSRegExp )){  
  Pattern p = Pattern.compile( withRMSRegExp );  
  Matcher matcher = p.matcher( str );  
  
  if (matcher.find() && matcher.groupCount() == 7){  
    try {  
      String piId = matcher.group(1);  
      String pkId = matcher.group(2);  
      double hi = Double.parseDouble(matcher.group(3));  
      double hk = Double.parseDouble(matcher.group(4));  
      double sd = Double.parseDouble(matcher.group(5));  
      double err= Double.parseDouble(matcher.group(6));  
    }  
    catch(NumberFormatException err) {  
    // keine Strecke; uninteressant, was es sonst  
    // sein koennte, da in jedem Fall unbrauchbar  
    }  
  }  
}  
  
// pruefe ohne opt. Parameter  
else if (str.matches( withOutRMSRegExp )){  
  Pattern p = Pattern.compile( withOutRMSRegExp );  
  Matcher matcher = p.matcher( str );  
  
  if (matcher.find() && matcher.groupCount() == 6){  
    // try-catch wie oben  
  }  
}  

Ich lese nun eine Datei mit 150 Zeilen ein. Alle Zeilen entsprechen dem Typ 2 (ohne opt. Parameter) Das ging eigentlich recht fix. Mit meiner "tollen" Prüfung dauert es aber nun ewig. Ich habe dann mal nur den zweiten Ausdruck stehen lassen, was die Zeit spürbar reduziert.

Meine Frage ist nun, sind reguläre Ausdrücke hier eher ungeeignet oder was bremst den Code so aus?

Ich hatte vorher die str.matches() Bedingungen nicht drin, da ich noch annahm, dass die Bedingung if (matcher.find() && matcher.groupCount() == <int>) zeitintensiver wäre. Scheint aber nicht so zu sein. Eine der beiden Bedingungen ist daher sicher überflüssig. Lässt sich sagen, welche die etwas flottere ist oder gibts andere Optimierungsmöglichkeiten zB mein Prüfausdruck?

Mit freundlichem Gruß
Micha

  1. n'abend,

    Meine Strategie war nun, prüfe die Zeile auf den vollständigen Inhalt, wenn dies fehlschlägt, prüfe, ob der optionale Wert nicht dabei ist.

    Du machst dadurch also pro Zeile 2 RegExp-Vergleiche, wo einer eigentlich ausreichen sollte?

    »(\S+).+?(\S+).+?« sollte man auch mit »((\S+).+?){2}« angeben können. Dementsprechend für die Zahlen »{(-{0,1}\d+\.\d+).+?){3,4}«, wobei das Konstrukt 3, maximal 4 Mal auftauchen darf.

    »((\S+).+?){2}{(-{0,1}\d+\.\d+).+?){3,4}« ist also eine Repräsentation deines RegExp, der zum Ersten beide Fälle betrachtet und zum Zweiten kürzer und verständlicher ist. Wenn du weisst was an den stellen ».+?« konkret auftauchen darf, kannst du das auch im RegExp angeben, dürfte sich nur positiv auf die Abarbeitungszeit auswirken.

    Der Absatz Groups and capturing von JavaDoc - Regular Expressions erklärt, dass es möglich ist Gruppen zu quantifizieren (der Kern des oben resultierenden RegExp).

    Vielleicht macht es auch Sinn den RegExp nur einmal kompilieren zu lassen und mit dem Matcher zu arbeiten, statt String.matches( "someRegExp" ) zu benutzen. Letzteres würde den RegExp schliesslich neu kompilieren müssen und ist vermutlich nichts weiter als ein Wrapper für

    Pattern p = Pattern.compile( "derRegExp" );  
    Matcher m = p.matcher( "deineZuMatchendeEingabe" );  
    return m.matches();  
    
    

    Ich habe mit RegExp in Java bisher nicht viel am Hut gehabt. Auch habe ich die gelieferten RegExps nicht getestet. Das ist also lediglich als Denkanstoß anzusehen.

    weiterhin schönen abend...

    --
    Freundlich wie man war, hat man mir Großbuchstaben geschenkt.
    sh:( fo:# ch:# rl:| br:> n4:& ie:{ mo:} va:) de:] zu:} fl:( ss:? ls:[ js:|
    1. Hallo globe,

      danke für Deine Anregungen. Ich habe es wie folgt formuliert:
      final static Pattern regExp = Pattern.compile( "((\\S+).+?){2,2}((-{0,1}\\d+\\.\\d+).+?){3,4}(.*)" );

      Ein primäres Probleme habe ich.
      Wie komme ich nun an meine Daten? Das group-Array (oder was es ist) liefert mir nicht mehr alle Einträge.

      Eine Verständnisfrage habe ich auch: Warum muß ich am Ende ein (.*) anfügen? Das musste ich schon bei meinem "alten" Muster, damit es funktioniert. Meine Daten sehen zB so aus:

      Alle Werte:
      300  3002401902   0.000 0.000  121.9840744655   0.0003

      Ohne den Optionalwert
      300  3002401903   0.000 0.000  129.9405567198

      Mit freundlichem Gruß
      Micha

      1. n'abend,

        danke für Deine Anregungen. Ich habe es wie folgt formuliert:
        final static Pattern regExp = Pattern.compile( "((\\S+).+?){2,2}((-{0,1}\\d+\\.\\d+).+?){3,4}(.*)" );

        das {2,2} müsste man auch durch {2} repräsentieren können.

        Wie komme ich nun an meine Daten? Das group-Array (oder was es ist) liefert mir nicht mehr alle Einträge.

        Ich hab das mal kurz getestet. In der Tat führt die von mir vorgeschlagene Notation dazu, dass mit den CaptureGroups nicht mehr ordnungsgemäß (also so, wie ich das von anderen Sprachen gewohnt bin ;) arbeiten kann. »This just *must* be java«™.

        Hier der Test:

        import java.util.regex.MatchResult;  
        import java.util.regex.Matcher;  
        import java.util.regex.Pattern;  
          
        public class RegExp  
        {  
         public static void main( String[] args )  
         {  
          // Diese Notation scheint nur für stumpfes matchen brauchbar zu sein?!  
          //Pattern p = Pattern.compile( "((\\S+)\\s+){2}(\\s*(-?\\d+\\.\\d+)){3,4}" );  
          Pattern p = Pattern.compile( "(\\S+)\\s+(\\S+)\\s+(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)\\s+(-?\\d+\\.\\d+)\\s*(-?\\d+\\.\\d+)?" );  
          
          
          String s = "300  3002401902   0.000 0.000  121.9840744655  0.0003" + "\n"  
            + "300  3002401903   0.000 0.000  129.9405567198";  
          
          Matcher m = p.matcher( s );  
          
          int c = 0;  
          while( m.find() )  
          {  
           // wo stehen wir denn?  
           System.out.println( "find() "+c+":" );  
           // was haben wir denn?  
           System.out.println( "group(): " + m.group() );  
           // wie viele gruppen haben wir?  
           System.out.println( "groupCount(): " + m.groupCount() );  
           // welche gruppen haben wir?  
           for( int i=0; i <= m.groupCount(); i++ )  
            System.out.println( "group("+i+"): " + m.group(i) );  
          
          
           System.out.println( "-----------------------------------------" );  
           c++;  
          }  
         }  
        }  
        
        

        Warum splittest du eigentlich nicht nach Spaces? Dadurch bekämst du ein String Array, mit welchem sich auch wunderbar weiterarbeiten ließe...

        Eine Verständnisfrage habe ich auch: Warum muß ich am Ende ein (.*) anfügen? Das musste ich schon bei meinem "alten" Muster, damit es funktioniert. Meine Daten sehen zB so aus:

        Ich tippe auf das LineFeed (das /(\r\n|\r|\n)/, welches die Zeile umbricht), oder auf die erzwungenen Spaces am Ende der letzten CaptureGroup.

        Alle Werte:
        300  3002401902   0.000 0.000  121.9840744655   0.0003

        Ich sehe da Leerzeichen (respektive irgendwelche Spaces). Du solltest dein RegExp dahingehend anpassen, um den "." zu eliminieren. bspw. »((\S+)\s+){2,2}«, dann brauchst du auch das "ungreedy" (+?) nicht mehr. Ich weiss zwar nicht, ob das tatsächlich Auswirkungen auf die Performance hat, aber testen kannst du es ja mal.

        * Sind die ersten beiden Werte immer Ganzzahlen? Warum scannst du dann nicht auch danach?
         * Floats müssen keinen Punkt haben. Der RegExp kann das berücksichtigen: ((-?\d+(\.\d+)?)\s+){3,4}

        weiterhin schönen abend...

        --
        Freundlich wie man war, hat man mir Großbuchstaben geschenkt.
        sh:( fo:# ch:# rl:| br:> n4:& ie:{ mo:} va:) de:] zu:} fl:( ss:? ls:[ js:|
        1. Guten morgen globe,

          das {2,2} müsste man auch durch {2} repräsentieren können.

          Ja, es war in Deinem letzten Posting ein kleiner Klammernfehler drin. Da habe ich ein wenig rum probiert und bin auf die min,max-Methode gekommen.

          Dein Code funktioniert, vielen Dank!

          Dadurch bekämst du ein String Array, mit welchem sich auch wunderbar weiterarbeiten ließe...

          Das Programm, welches die Daten verarbeitet, kommt von mir. Ich wollte dabei "maximale" Flexibilität erreichen. Eine Zeile soll nicht auf ein Trenner festgelegt werden, drum habe ich auch "beliebiges Zeichen" gewählt.

          Eine Verständnisfrage habe ich auch: Warum muß ich am Ende ein (.*) anfügen?

          Die Antwort kam in Deinem Code:

          i <= m.groupCount();
          ---^

          Ich war die ganze Zeit auf ein kleiner aus. Wenn ich hinten nun noch eine Gruppe definiert habe, kam ich so an den "letzten" Wert ran, den ich suchte. Anfänger pech ;-)

          Ich sehe da Leerzeichen (respektive irgendwelche Spaces).

          Es können auch Kommas, Semikolons oder Klammern oder ?!? sein. Den Bereich kann man aber sinnvoll eingrenzen, ja - vielleicht so:
          [\\[\\]\\(\\),;\\s\\|#]+. Der Punkt ist nur so verdammt übersichtlich ;-)

          * Sind die ersten beiden Werte immer Ganzzahlen? Warum scannst du dann nicht auch danach?

          Es sind Zeichenketten, die nur in meinem wohl eher schlecht gewählten Beispiel zufällig Ziffern sind. Eine Zeile ist eine Streckenmessung:

          <PunktnummerStandpunkt><PunktnummerZielpunkt><HöheStd.pkt><HöheZielpkt><SchrägStrecke><(opt)Standardabweichung>

          Die Punktnummer kann ich auf ([0-9A-Za-z-_]+) begrenzen.

          * Floats müssen keinen Punkt haben. Der RegExp kann das berücksichtigen: ((-?\d+(\.\d+)?)\s+){3,4}

          Ein sehr guter Hinweis, werde ich berücksichtigen!

          Darf ich fragen, warum Du am Ende des Ausdrucks noch ein ? gestezt hast?

          Mit freundlichem Gruß
          Micha

          1. Hallo,

            Darf ich fragen, warum Du am Ende des Ausdrucks noch ein ? gestezt hast?

            ... damit der Ausdruck optional ist, wie auch bei den Vorzeichen.

            Mit freundlichem Gruß
            Micha