kostenloser Webspace werbefrei: lima-city


Regex Problem

lima-cityForumProgrammiersprachenPHP, MySQL & .htaccess

  1. Autor dieses Themas

    theuntouchables

    theuntouchables hat kostenlosen Webspace.

    Servus,

    bräuchte ma bisal hilfe bei einem Regex.
    Folgende zwei Möglichkeiten von strings soll er zerlegen:

    Buchstaben1 - Buchstaben2 */*, BuchstabeZahl (Zahl)
    Buchstaben1, BuchstabeZahl (Zahl)

    Buchstaben1 taucht immer auf. Buchstaben2 */* nicht immer, wobei * entweder eine Zahl oder ein * ist. Der "-" nur wenn Buchstaben2...
    BuchstabeZahl (Zahl) taucht auch nicht immer auf.


    Sop mein Regex dazu sieht wie folgt aus:

    (\w*)-?(\w*)([0-9\*])/?([0-9\*]),(\w*)\(?\(d?)\)?

    Nur der Matcht ned also wirklich auf garnichts.

    Was habe ich falsch gemacht?

    Danke für Hilfe!


    PS: Kann mir jemand alternativ ne Datenbank für Magic-Karten sagen (also keine onlineDB oder fertiges Prog das gibts alle scho).
  2. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

  3. Sollte
    \(?\(d?)\)?
    nicht eher so
    (\(\d\))?
    aussehen?

    Wenns das nicht war, poste doch einmal ein Beispiel, dann könnte man den Regex selber ausprobieren.
  4. Autor dieses Themas

    theuntouchables

    theuntouchables hat kostenlosen Webspace.

    Hier mal paar beispiele:

    Kreatur - Egel 1/3, W (1)
    Land,
    Hexerei, 2W (3)
    Kreatur - Legende */*, 4GGG (7)


    @metalmachine:
    des passt so schon, soll mir explizit nur die Zahl zwischen den Klammern rausholen.
  5. @theuntouchables
    des passt so schon, soll mir explizit nur die Zahl zwischen den Klammern rausholen.

    nach kuzem googeln kann man bald fündig werden: überall dort, wo eine verhältnismäßig einfache php lösung vorhanden ist, sollte man davon kontra regexp. gebrauch machen (performance). eine mögliche lösung:
    <?php
    /* provisional solution for data */
    $data = array(
      'Kreatur - Egel 1/3, W (1)',
      'Land',
      'Hexerei, 2W (3)',
      'Kreatur - Legende */*, 4GGG (7)'
    );
    
    /* extracting numbers */
    $cnt = 0;
    foreach ($data as $row => $text) {
      if (count($row_slash = explode('/', $text)) > 1) {
        $num[$cnt] = array(
          'slash_left' => end($expl = explode(' ', $row_slash[0])) + 0,
          'slash_right' => reset($expl) + 0
        );
      }
      if (count($row_parenthesis = explode('(', $text)) > 1) {
        $parenthesis_num = end($row_parenthesis) + 0;
        $num[$cnt]['parenthesis'] = $parenthesis_num;
      }
      $cnt++;
    
    /* test output 1 */
    echo $cnt . ' - ' . print_r($num[$cnt-1], true) . '<br />';
    
    }
    
    /* test output 2 */
    echo '<hr /><pre>' .
      print_r($num, true) .
      '</pre>';


    (am ende fehlt das stumpfsinnige '?>' nicht! in anderen fällen aber wohl! googeln? :)

    wenn du die zahlen links und rechts vom slash nicht haben willst kommentierst einfach das erste 'if' aus.

    lg

    Beitrag zuletzt geändert: 8.1.2011 19:48:54 von hemiolos
  6. Ich hatte mit folgendem Ausdruck Erfolg:
    ^(\w+)( - (\w+) ([\d\*])/([\d\*]))?(, (\w+) \((\d)\))?$

    Getestet habe ich das ganze mit einem kurzen PHP-Script:
    <html>
    <head></head>
    <body>
    <?php
    $res = array(
    	"Kreatur - Egel 1/3, W (1)",
    	"Land",
    	"Hexerei, 2W (3)",
    	"Kreatur - Legende */*, 4GGG (7)");
    	
    // Muster:
    //	Buchstaben1 - Buchstaben2 */*, BuchstabeZahl (Zahl)
    //	sowie
    //	Buchstaben1, BuchstabeZahl (Zahl)
    
    // rxa: vereinfacht -> sucht, filtert aber nichts heraus 
    $rxa = "#^\w+( - \w+ [\d\*]/[\d\*])?(, \w+ \(\d\))?$#";
    // rxb: komplett -> sucht, und filtert Ergebnisse heraus
    $rxb = "#^(\w+)( - (\w+) ([\d\*])/([\d\*]))?(, (\w+) \((\d)\))?$#";
    
    foreach ($res as $str)
    {
    	if ($funde = preg_match_all($rxb, $str, $erg))
    	{
    		echo "Funde: $funde<br>\n<pre>\n";
    		print_r($erg);
    		echo "\n</pre><hr>\n";
    	}
    	else echo "Kein Fund!<hr>\n";
    }
    /* Funde:
    	$erg[1][0] --> Buchstaben1
    	$erg[3][0] --> Buchstaben2
    	$erg[4][0] --> *
    	$erg[5][0] --> *
    	$erg[7][0] --> BuchstabeZahl
    	$erg[8][0] --> Zahl
    */
    ?>
    </body>
    </html>
    Dieses Beispiel sollte erklären, was ich mir im Einzelnen bei meinem regulären Ausdruck gedacht habe :wink: .

    Zu dem Beispiel von hemiolos möchte ich noch sagen, dass es zwar richtig ist, das reguläre Ausdrücke sehr leistungsfressend sind, bei einem so komplexen Beispiel letztenden Endes aber doch performanter (und viel übersichtlicher) als zig explode()-Aufrufe.

    Beitrag zuletzt geändert: 8.1.2011 19:50:47 von metalmachine
  7. metalmachine schrieb:
    Zu dem Beispiel von hemiolos möchte ich noch sagen, dass es zwar richtig ist, das reguläre Ausdrücke sehr Leistungsfressenden sind, bei einem so komplexen Beispiel letztenden Endes aber doch performanter (und viel übersichtlicher) als zig explode()-Aufrufe.

    ja da magst eventuell recht haben. ich habe keine zeitmessungen angestellt. wenn er daran interesse hat, sollte er das machen und sein urteil hier kund tun. könnte interessant sein.
    wegen zig explode! man schreibt oft in programmen so etwas (und das zeilenweise!!!!):
    echo '...';
    echo '... ';
    echo '...';
    echo '... ';
    echo '...';
    etc.
    na ja! (ich habe in allen meinen arbeiten eine einzige ausgabe, nämlich die ausgabe der fertigen seite.)

    lg
  8. @metalmachine: Ich würde den Code noch wie folgt abändern:
    ^(\w+)(?: - (\w+) ([\d*])/([\d*]))?(?:, (\w+) \((\d)\))?$

    Damit fallen die Matches 2 und 6 selbst weg, du musst sie nicht extra auslassen ;) Dafür sind die Non-Capturing-Groups ja da ^^
  9. Autor dieses Themas

    theuntouchables

    theuntouchables hat kostenlosen Webspace.

    Ui da is ja rege beteiligung!!! Das freut mich, dass ich so eine schöne Diskussion lostreten konnte.

    Testen werde ich die Regexe (und die Non-Regex Methode) beide gleich, weil mich der Performanceunterschied wirklich interessiert (und in nem Edit präsentieren). Für die Anwendung is es egal, da das Ganze auf nem localen System läuft und ne onetimegeschichte wird.

    Und für die Verfolger des Ganzen werde ich im Folgenden ma versuchen die Unterschiede der Regexe herauszuarbeiten.

    ^ wie $ also match auf Start und ende werden wegfallen, da das ganze in nem größeren Kontext steht.
    
     (\w*)    -?(\w*) ([0-9\*])/?([0-9 \*])     , (\w*) \(?\(d?) \)?
    ^(\w+)  ( - (\w+) ([\d \*])/ ([\d  \*]))?  (, (\w+) \(  (\d)\))?$
    ^(\w+)(?: - (\w+) ([\d  *])/ ([\d   *]))?(?:, (\w+) \(  (\d)\))?$
        ^                 ^     ^   ^  ^    ^  ^     ^    ^  ^     ^
        |                 |     |   |  |    |  |     |    |  |     |
        |                 |     |   |  |    |  |     |    |  |     Das erste ? ist der ")" gewidmet während die beiden Folgenden
        |                 |     |   |  |    |  |     |    |  |     dem Kompletten letzten Block ", (\w+) \(  (\d)\)" betreffen
        |                 |     |   |  |    |  |     |    |  Da hab ich den "\" wergessen 
        |                 |     |   |  |    |  |     |    |  und mein "?" ist nötig da es keinen Block gibt
        |                 |     |   |  |    |  |     |    Selbe Sache mit dem "?" wie oben
        |                 |     |   |  |    |  |     Ich brauche ein "*" aus dem selben wie oben
        |                 |     |   |  |    |  Einmal Blockdeklarierung und einmal "Nicht-in-Matches (?:)" zusätzlich
        |                 |     |   |  |    Selbes Thema mit dem Block
        |                 |     |   |  "*" wird in na Eckigen nicht escaped
        |                 |     |   "\d" is natürlich wesentlich eleganter
        |                 |     s.o.
        |                 s.o. gilt auch für das nachfolgende
        "+" is natürlich richtig da am Anfang immer was steht


    Dan bis glaich mit den Performance ergebnissen!


    EDIT:
    So, leider erstmal die ernüchternde nachricht:
    Er matcht zwar super aber nicht auf "Land, " was mir unverständlich is.
    Eine weitere art von Text habe ich noch gefunden, hier is das zweite "," das Problem "Kreatur - Barbar, Zombie 2/2, BR (2)"
    Rauskommen soll "Barbar, Zombie" die erweiterung des Regex um (\w+)(?: - ([\w,]+) ([\d*])/([\d*]))?,(?: (\w+) \((\d)\))? half leider nicht, da bräuchte ich nochma eure Hilfe.
    Die Explode-Methode ist hier natürlich problematischer mit zwei Seperators wobei einer keiner is...

    EDIT 2:
    Das Problem mit "Kreatur - Barbar, Zombie 2/2, BR (2)" ist gelöst es musste natürlich auchd as Whitespace mit einbezogen werden.
    Aktuelles regex: (\w+)(?: - ([\w\s,]+) ([\d*])/([\d*]))?,(?: (\w+) \((\d)\))?
    Das "Land, " Problembesteht immernoch.

    Und um die Performancefreunde zu befriedigen das ergebnis bei ca 350 Datensätzen über 10 durchgänge gemittelt lautet:
    0.02903429508202 s mit 62% CPU für die Regex
    0.08286313056945 s mit 89% CPU für Explode
    Interessant würde ich sagen!


    Beitrag zuletzt geändert: 9.1.2011 21:12:17 von theuntouchables
  10. Es sind immer die einfachen Fehler, die man ewig sucht - du hast bei deinem Regex den Beistrich quasi "statisch" eingebaut; ein Fragezeichen löste das Problem:
    (\w+)(?: - ([\w\s,]+) ([\d*])/([\d*]))?,?(?: (\w+) \((\d)\))?
    Damit wird nun schließlich auch das "Land" wieder gefunden.

    Danke nikic für den Hinweis auf die Non-Capturing-Groups, damit hab ich mal wieder was dazugelernt :smile: .

    Das mit den Performancetests find ich auch echt interessant - hätte nicht gedacht, dass man das dann wirklich so deutlich sieht :lol:
  11. Das mit der Performance kann ich bestätigen, ich hab das ganze mit jeweils 100.000 Durchläufen auf der Konsole getestet und folgendes Ergebnis erhalten:
    user@computer:~/www/php/lima-city$ php regex-performance-test.php 
    runtime (reines PHP): 0.98900604248047 sec/100000 runs
    user@computer:~/www/php/lima-city$ php regex-performance-test.php 
    runtime (RegExp): 0.24291586875916 sec/100000 runs
    Außerdem habe ich das dann nochmal mit Perl nachgetestet und da hat sich das Ergebnis bestätigt:
    user@computer:~/www/php/lima-city$ perl regex-performance-test.pl 
    runtime (reines Perl): 0.702342987060547 sec/100000 runs
    user@computer:~/www/php/lima-city$ perl regex-performance-test.pl 
    runtime (RegExp):  0.144210815429688 sec/100000 runs


    Abschließend sind hier noch mal die Snippets, damit ihr die Tests auch nachvollziehen könnt.
    PHP:
    <?php
    $regex = 0;
    /* provisional solution for data */
    $data = array(
        /* [...] 100.000 Einträge [...] */
    );
      
    $l = count($data);
      
    $s = microtime(true);
    /* extracting numbers */
    $cnt = 0;
    foreach ($data as $row => $text) {
        if($regex){
            preg_match("/(\w+)(?: -? (\w+) ([\d  *])\/ ([\d   *]))?(?:, (\w+) \(  (\d)\))?/",$data[$cnt]);
        }else{
            if (count($row_slash = explode('/', $text)) > 1) {
                $num[$cnt] = array(
                    'slash_left' => end($expl = explode(' ', $row_slash[0])) + 0,
                    'slash_right' => reset($expl) + 0
                );
            }
            if (count($row_parenthesis = explode('(', $text)) > 1) {
                $parenthesis_num = end($row_parenthesis) + 0;
                $num[$cnt]['parenthesis'] = $parenthesis_num;
            }
        }
        $cnt++;
    } 
      
    $e = microtime(true);
    echo 'runtime ('.($regex ? 'RegExp' : 'reines PHP').'): '.($e-$s)." sec/$l runs\n";

    Perl:
    #!/usr/bin/perla
    
    use strict;
    use Time::HiRes qw(gettimeofday);
    
    my $regex = 0;
    
    # provisional solution for data
    my @data = (
        # [...] 100.000 Einträge [...]
    );
    my $l = scalar @data;
    
    my $s = microtime(1);
    #  extracting numbers
    my $cnt = 0;
    my $text;
    my @num = ();
    foreach $text(@data) {
        if($regex){
            $text =~ m/(\w+)(?: -? (\w+) ([\d  *])\/ ([\d   *]))?(?:, (\w+) \(  (\d)\))?/;
        }else{
            my %hash = ();
            if((scalar (my @row_slash = split(/\//, $text))) > 1){
                my @expl = split(/ /, $row_slash[0]);
                %hash = (
                    'slash_left' => $expl[-1]+0,
                    'slash_right' => $expl[0]+0
                );
            }
            if((scalar (my @row_parenthesis = split(/\(/, $text))) > 1){
                $hash{'slash_left'} = ($row_parenthesis[-1]+0);
            }
            $num[$cnt] = %hash;
        }
        $cnt++;
    }
    
    my $e = microtime(1);
    print 'runtime ('.($regex ? 'RegExp' : 'reines Perl').'): '.($e-$s)." sec/$l runs\n";
    
    sub microtime{
        my $asFloat = 0;
        my $microtime = '';;
        if(@_){
            $asFloat = shift;
        }
        (my $epochseconds, my $microseconds) = gettimeofday;
        if($asFloat){
            while(length("$microseconds") < 6){
                $microseconds = "0$microseconds";
            }
            $microtime = "$epochseconds.$microseconds";
        }else{
            $microtime = "$epochseconds.$microseconds";
        }
        return $microtime;
    }
  12. Autor dieses Themas

    theuntouchables

    theuntouchables hat kostenlosen Webspace.

    Sehr schön wenn meine ergebnisse nochmal verifiziert wurden!!!
    Ich finds aber schon krass das Regex so performant sind, hätte ich echt nicht gedacht (interne Notiz: öfters Regex verwenden).

    Mal n kleines Update:
    Folgende neue Strings wurden entdeckt
    "Land, " GELÖST Ganz wichtig das Leerzeichen am Ende, das war das Problem
    "Irgendwas Land, " GELÖST Hier das Leerzeichen vor dem ","
    "Artefaktkreatur 4/4, 7 (7)" UNGELÖST Das ding fällt halt ma völlig ausm Muster (warum können sich die Blöden entwickler ned an eine Muster halten?! :mad:)

    Und der Regex sieht im Moment so aus:
    ([\w\s-]+)(?: - ([\w\s,]+) ([\d*])/([\d*]))?,\s?(?: (\w+) \((\d)\))?



    @metalmachine:
    Jop das "," ist und SOLL statisch eingebaut sein, da ja IMMER eins kommt...

    Beitrag zuletzt geändert: 10.1.2011 13:28:27 von theuntouchables
  13. Und wieder ist der kleine Regex ein Stück gewachsen:
    (\w+)(?:(?: - ([\w, ]+))? ([\d*])/([\d*]))?,(?: (\w+) \((\d)\))?
    Damit konnte ich folgende Zeichenketten erfolgreich zerlegen:
    "Kreatur - Egel 1/3, W (1)",
    "Land,",
    "Hexerei, 2W (3)",
    "Kreatur - Legende */*, 4GGG (7)",
    "Kreatur - Barbar, Zombie 2/2, BR (2)",
    "Artefaktkreatur 4/4, 7 (7)"

    Die Gruppe ([\w\s-]+) habe ich wieder vereinfacht, da mir das so nicht sehr zweckmäßig erscheint und in ([\w\s,]+) habe ich \s durch ein einfaches Leerzeichen ersetzt, da \s auch Zeilenumbrüche entahlten kann, was eher kontraproduktiev ist, wenn man in einem längerem Text sucht.

    @metalmachine:
    Jop das "," ist und SOLL statisch eingebaut sein, da ja IMMER eins kommt...
    Das hatte ich etwas missverstanden...
  14. Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!

    lima-city: Gratis werbefreier Webspace für deine eigene Homepage

Dir gefällt dieses Thema?

Über lima-city

Login zum Webhosting ohne Werbung!