Ziffern fischen per RegEx
Nico R.
- regex
1 Raketenwilli0 Rolf B0 TS- php
- regex
0 Jonny 5
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
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;
}
}
}
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
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.
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
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...
preg_replace( '/[^0-9]/', '', $s )
passt also.
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
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;
}
}
}
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
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).
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
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.
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));
}
}
@@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.
🖖 Живіть довго і процвітайте
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
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
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
Hallo Nico R.,
was man auch klären müsste:
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
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
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
(?<name>...)
)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
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
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.
Sowas geht also mit den Stringfunktionen von PHP
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
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
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
Hallo Nico R.!
Das könnte man in PCRE (PHP) mit einem Regex z.B. so lösen:
(?:\G(?!^)|Projekt) *\K\d
Die Alternation innerhalb der (?:
non capturing group )
dient dazu den Startpunkt zu finden. Entweder matche Projekt
oder \G
(Anker) mach dort weiter, wo ein vorangegangenes Match geendet hat. Dass \G
nicht am Start matcht (default), wird durch einen negativen lookbehind (?!^)
vermieden.
*
matcht beliebig viele spaces (oder mit tabs: \h*
für horizontal space)
\d
matcht dann jede Ziffer als eigenes Match
Wenn du die Ziffern mit den Leerzeichen zusammen matchen möchtest, wird es einfacher:
Projekt *\K[\d ]*\d
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.