Nico R.: Ziffern fischen per RegEx

Hallo allerseits,

ich habe einen String "Projekt 12 3", in dem ich alle Ziffern treffen möchte, die auf "Projekt" folgen und einzeln stehen oder von Leerzeichen begrenzt werden. Das Ergebnis soll also 123 sein. Hier mal meine Lösungsversuche:

(?<=Projekt )[0-9]+

=> 12. Da danach ein Leerzeichen kommt, gehts nicht weiter.

(?<=Projekt )[0-9 ]+

=> _12_3. Damit könnte ich leben und die Leerzeichen per str_replace() entfernen. Aber vielleicht gehts ja auch per RegEx...

(?<=Projekt )(?<=\s)?[0-9]+(?=\s)?

=> 12. Mein Gedanke war: Vor und nach einer oder mehrerer Ziffern darf ein Leerzeichen stehen. Leider findet er die 3 trotzdem nicht...

Bin ich denn auf der richtigen Spur?

Schöne Grüße

Nico

  1. Meinst Du sowas?

    <?php
    header( 'Content-Type:text/plain' );
    $strings = [
        'Foo bar Projekt 12 3Kram',
        'Foo bar Projekt 123Kram',
        'Foo bar Projekt 1 23Kram',
        'Foo bar Projekt12 3 Kram',
        'Foo bar Projekt123 Kram',
        'Foo bar Projekt1 23 Kram'
    ];
    $strings[] = implode( PHP_EOL, $strings );
    
    $pattern = '/Projekt *[0-9 ]+/i';
    
    $i = 0;
    foreach( $strings as $string ) {
    	$k = 0;
        preg_match_all( $pattern, $string, $arr ) . PHP_EOL;
        $i++;
        echo PHP_EOL . $i . ". String:\t\"" . $strings[$i-1] . '"' . PHP_EOL;
        foreach ( $arr as $a ) {
    		foreach ( $a as $s ) {
    			$k++;
    			echo "\t" . $k . ". Fund:\t" . str_replace( ' ', '', $s ) . PHP_EOL;
    		}
    	}
    }
    
    1. Hmm, nicht so ganz. "Projekt" soll nicht mit ausgegeben werden, sondern nur die Ziffern.

      Der Rest stimmt schon. Es funktioniert soweit auch schon.

      $projektnummer = $csv_array[0][0];
      
      preg_match("/(?<=Projekt )[0-9 ]+/", $projektnummer, $projektnummer_match);
      
      $projektnummer = $projektnummer_match[0];
      
      $projektnummer = str_replace(" ", "", $projektnummer);
      

      Aber ich hab mich gefragt, ob man das nicht ohne str_replace-Nachbearbeitung komplett im Regex ausdrücken könnte.

      Gruß Nico

      1. Hmm, nicht so ganz. "Projekt" soll nicht mit ausgegeben werden,

        Dann einfach so:

        echo "\t" . $k . ". Fund:\t" . preg_replace( '/[^0-9]/', '', $s ) . PHP_EOL;
        

        Zwei einfache reguläre Ausdrücke sind nicht teurer als ein heftiger mir Rückwärtskram.

        1. Dann einfach so:

          echo "\t" . $k . ". Fund:\t" . preg_replace( '/[^0-9]/', '', $s ) . PHP_EOL;
          

          So dann vermutlich, oder?

          preg_replace( '/[^0-9]+/', '', $s )
          

          Zwei einfache reguläre Ausdrücke sind nicht teurer als ein heftiger mir Rückwärtskram.

          Ja, das kann ich mir gut vorstellen. Das ist an der Stelle eher sportlicher Ehrgeiz bzw. das Bemühen, RegEx richtig zu verstehen.

          (?<=Projekt )(?<=\s)?[0-9]+(?=\s)?
          

          Ich hab jetzt zumindest verstanden, dass der String "Projekt 12 3" nur "12" liefert, weil nach der 2 ja ein Leerzeichen kommt und damit [0-9] nicht erfüllt ist. Das riecht danach als würde das als RegEx viel zu kompliziert werden. Ich belass es dann wohl bei der Lösung mit str_replace() :-)

          Trotzdem natürlich besten Dank

          Nico

          1. Dann einfach so:

            echo "\t" . $k . ". Fund:\t" . preg_replace( '/[^0-9]/', '', $s ) . PHP_EOL;
            

            So dann vermutlich, oder?

            preg_replace( '/[^0-9]+/', '', $s )
            

            Nein. Das Plus bringt nichts außer Mehrarbeit. Beschreibe einfach mal das Verfahren...

            • Jedes Zeichen, welches keine Ziffer ist, durch nichts ersetzen. Fertig
            • Jedes Zeichen, welches keine Ziffer ist, merken, nach dem Nachfolger schauern, ob der keine Ziffer ist, sodann nachschauen, ob dessen Nachfolger eine Ziffer ist und wenn doch oder wenn der String zuende ist, die gemerkte Gruppe löschen und den Zeiger n-1 Stellen zurücksetzen.
            preg_replace( '/[^0-9]/', '', $s )
            

            passt also.

            1. Hallo Raketenwilli,

              stimmt, mit preg_replace funktioniert das ja wunderbar. Also gibts doch eine Lösung per RegEx. Noch was gelernt :-)

              Nichtsdestotrotz habe mich für /(?<=Projekt )[0-9 ]+/ und str_replace() entschieden, weil ich so prüfen kann, dass vorne "Projekt " steht und im Falle von z.B. "Projekt 367 566 (04.04.2023)" nur "367566" extrahiert wird.

              Vielen Dank auf jeden Fall für die Hilfe!

              Schöne Grüße Nico

              1. Nichtsdestotrotz habe mich für /(?<=Projekt )[0-9 ]+/ und str_replace() entschieden, weil ich so prüfen kann, dass vorne "Projekt " steht und im Falle von z.B. "Projekt 367 566 (04.04.2023)" nur "367566" extrahiert wird.

                Klar. Übrigens zeigt das mein Vorschlag mit der kleinen Änderung nach Deiner genaueren Spezifierung „bocksturzfrei“ genau dieses Ergebnis.

                <?php
                header( 'Content-Type:text/plain' );
                $strings = [
                    'Projekt 367 566 (04.04.2023)',
                    'Foo bar Projekt 12 3Kram',
                	'Foo bar Projekt 123Kram',
                	'Foo bar Projekt 1 23Kram',
                    'Foo bar Projekt12 3 Kram',
                	'Foo bar Projekt123 Kram',
                	'Foo bar Projekt1 23 Kram'
                ];
                $strings[] = implode( PHP_EOL, $strings );
                
                $pattern = '/Projekt *[0-9 ]+/i';
                
                $i = 0;
                foreach( $strings as $string ) {
                	$k = 0;
                    preg_match_all( $pattern, $string, $arr ) . PHP_EOL;
                    $i++;
                    echo PHP_EOL . $i . ". String:\t\"" . $strings[$i-1] . '"' . PHP_EOL;
                    foreach ( $arr as $a ) {
                		foreach ( $a as $s ) {
                			$k++;
                			#echo "\t" . $k . ". Fund:\t" . str_replace( ' ', '', $s ) . PHP_EOL;
                			echo "\t" . $k . ". Fund:\t" . preg_replace( '/[^0-9]/', '', $s ) . PHP_EOL;
                		}
                	}
                }
                
                1. Hello J.,

                  Nico schrieb, dass die Daten aus Excel kommen würden.

                  Wie exportiert das neueste Excel eigentlich Text-Zellen, in denen ein Umbruch erlaubt/vorgegeben ist?

                  Ich benutze nur Uralt-Excel (2003) und Calc, kann es daher leider nicht selber überprüfen.

                  Glück Auf
                  Tom vom Berg

                  --
                  Es gibt soviel Sonne, nutzen wir sie.
                  www.Solar-Harz.de
                  S☼nnige Grüße aus dem Oberharz
                  1. Wie exportiert das neueste Excel eigentlich Text-Zellen, in denen ein Umbruch erlaubt/vorgegeben ist?

                    (Auch) Zu Deinem Glück hab ich ein Lappi, auf dem ich zwecks Demonstration und Erinnerung das Zeug (Win11 und Office365) drauf gelassen und nur die Partition verkleinert habe, damit ich ein funktionales Betriebssystem und Software installieren kann... Hab mal geschaut, ob das Zeug noch startet und funktioniert.

                    Als UTF-8-CSV?

                    Wenn ich nichts besonderes spezifiere: Der einzelne String wird mit doppelten Anführungszeichen getaggt, der darin enthaltene Umbruch ist laut Hex-Betrachter 0A, also nur das „\n“ (NewLine).

                    1. Hello,

                      Wie exportiert das neueste Excel eigentlich Text-Zellen, in denen ein Umbruch erlaubt/vorgegeben ist?

                      (Auch) Zu Deinem Glück hab ich ein Lappi, auf dem ich zwecks Demonstration und Erinnerung das Zeug (Win11 und Office365) drauf gelassen und nur die Partition verkleinert habe, damit ich ein funktionales Betriebssystem und Software installieren kann... Hab mal geschaut, ob das Zeug noch startet und funktioniert.

                      Als UTF-8-CSV?

                      Wenn ich nichts besonderes spezifiere: Der einzelne String wird mit doppelten Anführungszeichen getaggt, der darin enthaltene Umbruch ist laut Hex-Betrachter 0A, also nur das „\n“ (NewLine).

                      Danke.

                      Das ist dann vermutlich der harte Umbruch innerhalb der Zelle?
                      Wodurch wird denn ein Autoumbruch repräsentiert, oder fällt der beim Export unter den Tisch? Der hat ja eigentlich nur etwas mit der aktuellen Spaltenbreite (Darstellung) zu tun und nichts mit der Datenorganisation.

                      Das CSV-Entpacken nebst Kontrollen müsste Nico dann also auf jeden Fall noch vorschalten.

                      Glück Auf
                      Tom vom Berg

                      --
                      Es gibt soviel Sonne, nutzen wir sie.
                      www.Solar-Harz.de
                      S☼nnige Grüße aus dem Oberharz
                      1. Hello,

                        Wie exportiert das neueste Excel eigentlich Text-Zellen, in denen ein Umbruch erlaubt/vorgegeben ist?

                        (Auch) Zu Deinem Glück hab ich ein Lappi, auf dem ich zwecks Demonstration und Erinnerung das Zeug (Win11 und Office365) drauf gelassen und nur die Partition verkleinert habe, damit ich ein funktionales Betriebssystem und Software installieren kann... Hab mal geschaut, ob das Zeug noch startet und funktioniert.

                        Als UTF-8-CSV?

                        Wenn ich nichts besonderes spezifiere: Der einzelne String wird mit doppelten Anführungszeichen getaggt, der darin enthaltene Umbruch ist laut Hex-Betrachter 0A, also nur das „\n“ (NewLine).

                        Danke.

                        Das ist dann vermutlich der harte Umbruch innerhalb der Zelle?

                        Ja. Der und nur der.

                        Wodurch wird denn ein Autoumbruch repräsentiert, oder fällt der beim Export unter den Tisch? Der hat ja eigentlich nur etwas mit der aktuellen Spaltenbreite (Darstellung) zu tun und nichts mit der Datenorganisation.

                        Eben und deswegen wird der auch nicht exportiert. Als „reine Optik“ ist der ja auch vom Programm abhängig. Formeln und Zeug wie Schriftfarben wird ja auch nicht exportiert. Nur wie das bei Zahlenformaten ist - da weiß ich nicht, ob die angezeigte, formatierte „Zahl“ oder die in der Zelle enthaltene Zahl exportiert wird. Dazu müsste ich Windows nochmal booten.

                        Das CSV-Entpacken nebst Kontrollen müsste Nico dann also auf jeden Fall noch vorschalten.

                        Dafür hat PHP fertige Funktionen.

                        1. Dafür hat PHP fertige Funktionen.

                          Ja, zum Glück. Per fgetcsv()...

                          if($csv = fopen($csv_pfad, 'r')) {	
                          
                            $csv_array = array();
                          
                            while(! feof($csv)) {
                          	array_push($csv_array, fgetcsv($csv));
                            }
                          }
                          
              2. @@Nico R.

                stimmt, mit preg_replace funktioniert das ja wunderbar. Also gibts doch eine Lösung per RegEx. Noch was gelernt :-)

                Nö. Das würde dir aus „Projekt 12 3 Kram 45 6“ ja „123456“ extrahieren, und das willst du nicht.

                🖖 Живіть довго і процвітайте

                --
                „Ukončete, prosím, výstup a nástup, dveře se zavírají.“
                1. Hallo Gunnar Bittersmann,

                  Nö. Das würde dir aus „Projekt 12 3 Kram 45 6“ ja „123456“ extrahieren

                  Doch. Vor 45 steht kein "Projekt ", d.h. die lookbehind assertion ist nicht erfüllt.

                  (Hör ich da ein Oh! im Hintergrund?)

                  Ich hab ja schon von der lookbehind assertion abgeraten, jetzt habe ich nochmal bei regex101 geschaut. Mit Assertion: 31 Steps. Mit "Projekt " als Match und einer Klammergruppe um die Ziffern: 12 Steps.

                  Das würde ich als effizienter einschätzen. Ich habe natürlich keine Ahnung, ob die Stepzählung von regex101 mit der in PCRE identisch ist…

                  Rolf

                  --
                  sumpsi - posui - obstruxi
                  1. Hello,

                    Nö. Das würde dir aus „Projekt 12 3 Kram 45 6“ ja „123456“ extrahieren

                    Doch. Vor 45 steht kein "Projekt ", d.h. die lookbehind assertion ist nicht erfüllt.

                    (Hör ich da ein Oh! im Hintergrund?)

                    Ich hab ja schon von der lookbehind assertion abgeraten, jetzt habe ich nochmal bei regex101 geschaut. Mit Assertion: 31 Steps. Mit "Projekt " als Match und einer Klammergruppe um die Ziffern: 12 Steps.

                    Das würde ich als effizienter einschätzen. Ich habe natürlich keine Ahnung, ob die Stepzählung von regex101 mit der in PCRE identisch ist…

                    Das ist genau das Problem, das ich mit Dokumentationspflichten
                    gemeint habe ;-P

                    Da muss man nicht nur aufschreiben, was man (vermeintlich) programmiert hat, sondern insbesondere das, was man eigentlich programmieren wollte!

                    Glück Auf
                    Tom vom Berg

                    --
                    Es gibt soviel Sonne, nutzen wir sie.
                    www.Solar-Harz.de
                    S☼nnige Grüße aus dem Oberharz
                2. Hallo Gunnar,

                  Nö. Das würde dir aus „Projekt 12 3 Kram 45 6“ ja „123456“ extrahieren, und das willst du nicht.

                  Ich kanns jetzt ehrlich gesagt gar nicht mehr komplett nachvollziehen. In jedem Fall funktioniert aber diese letzte Version von Raketenwilli wie sie soll: Beitrag Raketenwilli

                  Schöne Grüße

                  Nico

  2. Hallo Nico R.,

    was man auch klären müsste:

    • welche Regex-Engine ist das? Javascript? PCRE (Perl oder PHP)? .net?
    • welche potenziellen Strings sind Kandidat? Kann hinter "Projekt" noch etwas anderes folgen als Ziffern und Whitespace - also der "Kram", den Raketenwilli andeutete?

    Eins ist aber klar: Du wirst aus "Projekt 1 2 3" keinesfalls per Regex den String 123 direkt isolieren können. Ich kenne kein Regex-Konstrukt, das das leistet.

    Lookahead und -behind ist, wie Raketenwilli schon sagte, aufwändig. Besser ist auf jeden Fall

    /Projekt([0-9 ]+)/

    Das liefert die Ziffern und Spaces hinter 'Projekt' als eine Matchgruppe. Je nach verwendeter Programmiersprache kannst Du die aus dem Match herausholen und daraus die Leerzeichen entfernen. Das dürfte am effizientesten sein.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      • welche Regex-Engine ist das? Javascript? PCRE (Perl oder PHP)? .net?
      • welche potenziellen Strings sind Kandidat? Kann hinter "Projekt" noch etwas anderes folgen als Ziffern und Whitespace - also der "Kram", den Raketenwilli andeutete?

      In dem Fall PHP. Es handelt sich um eine Excel-Tabelle, in der in der ersten Zelle zum Beispiel "Projekt 367 589" steht. Die Tabelle wird fortlaufend bearbeitet und dabei kann sich auch die Projektnummer ändern. Im Normalfall sollte hinter der Nummer kein weiterer Text kommen, und wenn, dann einfach ignoriert werden, auch alle Ziffern, die da eventuell folgen mögen... Das leistet der RegEx auch.

      Eins ist aber klar: Du wirst aus "Projekt 1 2 3" keinesfalls per Regex den String 123 direkt isolieren können. Ich kenne kein Regex-Konstrukt, das das leistet.

      Nach längerem Brüten darüber, scheint mir das logisch. Aber doch irgendwie verwunderlich, da ja RegEx so mächtig sind. Die Aufgabe erscheint ja eigentlich als recht banal. Na man lernt nicht aus :-)

      Beste Grüße

      Nico

      1. Hallo Nico R.,

        Aber doch irgendwie verwunderlich, da ja RegEx so mächtig sind. Die Aufgabe erscheint ja eigentlich als recht banal.

        Ich verstehe Dich ja. Aber es ist vollkommen egal, wieviele Features Du in ein Werkzeug einbaust. Nach 5 Minuten kommt jemand an und vermisst ein völlig banales, weiteres Feature.

        Und am Ende hast Du dann sowas. Oder das berühmt-berüchtigte Wenger 16999 (lies die 5-sterne Bewertungen…)

        Regexe können

        • finden
        • Fundstücke mit Namen versehen ((?<name>...))
        • auf Existenz prüfen (die lookahead/lookbehind Tests)
        • Kopfschmerzen bereiten
        • Die CPU zum grillen aufheizen

        Aber gefundene Dinge zusammenfügen können sie nicht. Sie können auch kein "Array aus Gruppen" bilden, d.h. wenn Du so eine Regexp hast:

        /Projekt(?<ziffer>\s*\d+)+/

        dann befindet sich in der Matchgruppe "ziffer" nur die letzte Ziffer, nicht alle Ziffern.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Und am Ende hast Du dann sowas. Oder das berühmt-berüchtigte Wenger 16999 (lies die 5-sterne Bewertungen…)

          Kreisch! Das will natürlich keiner :-)

          Aber am Ende ist das Werkzeug ja doch im RegEx-Kasten enthalten. Siehe Raketenwillis Lösung von gestern...

          Nachtrag: Eine Lösung allein per RegEx-Ausdruck ists ja doch nicht, weil ja noch preg_replace() zum Einsatz kommt. Aber im weitesten Sinne ;-)

          Schöne Grüße

          Nico

  3. Hello Nico,

    => _12_3. Damit könnte ich leben und die Leerzeichen per str_replace() entfernen. Aber vielleicht gehts ja auch per RegEx...

    Zwar spät, aber trotzdem noch Senf von mir zu deiner Wurst:

    Es geht selbstverständlich auch ohne Regular Expressions. Man sollte sogar ersteinmal die Regeln und Bedingungen ohne RegEx festlegen. Die Entscheidung, ob man dann später immer noch RegEx benutzt, ist erst der zweite Schritt.

    • es gibt einen String mit einem Aufbau, ähnlich
      " Projekt 123 456, (Müller) 0531/4708-0".
      **- Die Codierung ist UTF8 **
    • Alle Ziffern und Zeichen lt. Liste [1-9, ' ', '-'], die auf das Schlüsselwort "Prokekt" folgen, sollen gefunden werden ...
    • bis zum Auftreten eines Zeichens, das nicht in der Liste enthalten ist (Stopper).
      -Anschließend Außerdem soll die so extrahierte Zeichenkette von allen Nicht-Ziffernzeichen befreit werden

    Sowas geht also mit den Stringfunktionen von PHP

    • mb_strpos()
    • einer Schleife
    • Stringvergleichen (Ist das Zeichen an der Position in der Liste?)
    • Stringkonkatenation (Aufsammeln der gesuchten Ziffern im Ergebnis)

    So ähnlich müssen die Raketengedanken auch abgelaufen sein ;-)

    Der Vorteil einer explizit zusammengebauten Funktion ist sicherlich, dass man ihre Funktionsweise auch später noch ohne ausfühliche Dokumentation nachvollziehen kann. Bei Konstruktionen mit Regular Expressions würde ich mir die Gedankengänge hingegen ganz genau notieren.

    Dass gute RexExe in PHP schneller sind, als explizite Funktionen, insbesondere dann, wenn die RegEx-Maschine im selben Script mehrmals benutzt wird, haben wir neulich schon gehabt. Bei einmaliger Anwendung sind die Unterschiede allerdings kaum messbar.

    Glück Auf
    Tom vom Berg

    --
    Es gibt soviel Sonne, nutzen wir sie.
    www.Solar-Harz.de
    S☼nnige Grüße aus dem Oberharz
    1. Hallo Tom,

      danke für deinen Hinweis. Normalerweise ist das auch die Art, wie ich an so etwas herangehe. In dem Fall schien mir das aber zu frickelig. Der Ausdruck ist ja nicht besonders komplex und eigentlich gut zu durchschauen. Ich hab mir aber für alle Fälle im Script-Kommentar den Link zu diesem Forumsbeitrag reinkopiert ;-)

      Schönen Gruß

      Nico

      1. Hello Nico,

        danke für deinen Hinweis. Normalerweise ist das auch die Art, wie ich an so etwas herangehe. In dem Fall schien mir das aber zu frickelig. Der Ausdruck ist ja nicht besonders komplex und eigentlich gut zu durchschauen. Ich hab mir aber für alle Fälle im Script-Kommentar den Link zu diesem Forumsbeitrag reinkopiert ;-)

        Und dazu bitte auch der Aspect der Datenherkunft

        Glück Auf
        Tom vom Berg

        --
        Es gibt soviel Sonne, nutzen wir sie.
        www.Solar-Harz.de
        S☼nnige Grüße aus dem Oberharz
  4. Hallo Nico R.!

    Das könnte man in PCRE (PHP) mit einem Regex z.B. so lösen:

    (?:\G(?!^)|Projekt) *\K\d
    

    Siehe Regex101 Demo


    Wenn du die Ziffern mit den Leerzeichen zusammen matchen möchtest, wird es einfacher:

    Projekt *\K[\d ]*\d
    

    Siehe Regex101 Demo


    Lustig zum Regex üben, anders geht's natürlich ebenso. Wohl auch wichtig, dass man später noch weiß (andere verstehen), was man da einmal gemacht hat.