Forum: PC-Programmierung RegEx für erste Zahl im String


von Thomas W. (thomaswi)


Lesenswert?

Hi,

ich habe einen String in dem beliebig Zahlen vorkommen können, z.B.:
* X001_Y1
* 001X1
* 001X1Y
* X_1_Y

Ziel ist es Zahlen davon nach bestimmten Kriterien zu finden und zu 
inkrementieren.

Kriterien können z.B. sein:
* Alle Zahlen (pattern = @"\d+")
* Zahl am Anfang vom String
* Zahl am Ende vom String
* Erste Zahl im String
* Letzte Zahl im String (pattern = @"\d+(?!.*\d)")
* Zahl zwischen bestimmten Zeichen (pattern = 
@"(?<=[_])[0-9]+(?=[_]|$)";)

Einige Kriterien möchte ich gerne als Vorauswahl anbieten.
Der Poweruser soll aber auch eigene Patterns definieren können.

Im Code sieht das dann so aus:
1
string newName = Regex.Replace(oldName, pattern, m => (int.Parse(m.Value) + 1).ToString(new string('0', m.Value.Length)));

Nun scheitere ich aber schon am Pattern für die Erste Zahl im String.
Ich finde nur Lösungen mit Gruppen, wie: pattern = @"^[\D]*(\d+)"
Das findet aber auch im ersten Beispiel X001, was natürlich nicht 
funktioniert.

Weiß jemand Rat?

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Thomas W. schrieb:

> Nun scheitere ich aber schon am Pattern für die Erste Zahl im String.
[...]
> Weiß jemand Rat?

Lerne selber zu denken! Lerne Doku zu lesen (im konkreten Fall: die Doku 
zu den verwendendeten REs)

von Steve van de Grens (roehrmond)


Lesenswert?

Ich benutze immer diese Seite zum Testen: https://regex101.com/

von Philipp K. (philipp_k59)


Lesenswert?

Also normalerweise wäre das

Non capturing group ?:
^(?:[\D])(\d+)

Wenn das nicht mit Replace funktioniert ist das weil das Ergebnis in der 
Hauptgruppe trotzdem angezeigt wird.

Dann müsste man das mit einem Workaround angehen.

von Thomas W. (thomaswi)


Lesenswert?

Ja, diese Seite habe ich auch genutzt und habe mit dem Pattern wie oben 
beschrieben ein Match "X001" und eine Group "001".
Ich möchte aber nur die Zahl haben.

von Thomas W. (thomaswi)


Lesenswert?

^(?:[\D])(\d+) liefert das selbe Ergebnis wie ^[\D]*(\d+).
Dann geht es vermutlich nur mit Änderung im Code, also foreach über alle 
Matches...?

von Philipp K. (philipp_k59)


Lesenswert?

Ja zum Beispiel, in welcher Sprache ist das? In manchen kann man 
Submatches für Replace nutzen.

von Sebastian W. (wangnick)


Lesenswert?

Thomas W. schrieb:
> ein Match "X001" und eine Group "001".
> Ich möchte aber nur die Zahl haben.

Ich kenne es von einigen Sprachen so, dass dort Match und Search 
unterschieden wird, wobei Match immer den gesamten String matchen muss, 
Search dagegen matches von Teilstrings sucht und findet.

Aber selbst wenn es ein Search in deiner unbekannten Umgebung nicht 
gibt: Warum nutzt du nicht den match der Group?

Ansonsten hilft vielleicht ein non-greedy skip (".*?")?

LG, Sebastian

: Bearbeitet durch User
von Thomas W. (thomaswi)


Lesenswert?

Ich nutze C#.
Danke für den Input (Submatches, Search, non-greedy), ich werde mich da 
mal einlesen.
Am Ende müssen halt alle Patterns mit dem selben Code funktionieren.
Mit try{} catch{} fange ich fehlerhafte Patterns vom User ab. Dann 
passiert halt einfach gar nichts.

: Bearbeitet durch User
von Philipp K. (philipp_k59)


Lesenswert?

Das mit den Submatches ist hier gut erklärt. Ich kenne c# nicht.

https://stackoverflow.com/questions/6005609/replace-only-some-groups-with-regex

Es entsteht erst ein Problem in den User Eingaben, man weiß ja vorher 
nicht welche Gruppen matchen oder nicht matchen sollen.

von Mario P. (mario_71950c)


Lesenswert?

Auf https://regex101.com/ wird mit \d+ ohne einen modifier/RegexFlag (m 
und g entfernen) genau die erste Zahl gefunden.

: Bearbeitet durch User
von Thomas W. (thomaswi)


Lesenswert?

Ja, das ist mir auch schon aufgefallen.
Anscheinend kann man in .NET diese Modifikatoren aber nur als Parameter 
übergeben und nicht mit im Pattern einbauen. Dann wäre das in der Tat 
eine gute Lösung.
\d+ alleine soll ja auch schon alle Nummern matchen.

Beitrag #7543147 wurde vom Autor gelöscht.
von Thomas W. (thomaswi)


Lesenswert?

Ok, auch in .NET lassen sich manche Modifier im Pattern übergeben, aber 
leider keiner für "Global":
https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expression-options

Das Problem ist, dass Lookbehind keine dynamischen Pattern erlaubt.
Lookahead kann das, und das nutze ich dann so für die letzte Nummer im 
String:
\d+(?!.*\d)
Für die erste Nummer wäre das entsprechend:
(?<!\d.*)\d+
Das geht aber nicht, weil Das Lookbehind Pattern durch das "*" dynamisch 
wird.
Also bleiben nur noch kompliziertere Pattern mit Gruppen, die aber 
spezifische Outputs generieren welche ich nicht generisch auswerten 
kann.
Es muss aber generisch bleiben, da ja auch alle anderen oben genannten 
Kriterien funktionieren müssen.
Und darüber hinaus alle möglichen anwendugsspezifischen Pattern der 
User.

Meine Lösung ist nun wie folgt.
* Es sind nur Patterns ohne Gruppen erlaubt
* Dafür gibt es eine weitere Variable "max" mit der Anzahl der Matches 
die maximal ersetzt werden sollen

Der Code dazu schaut dann so aus:
1
Regex reg = new Regex(pattern);
2
string result = reg.Replace(input, delegate (Match m) {
3
    try
4
    {
5
        return (int.Parse(m.Value) + 1).ToString(new string('0', m.Value.Length));
6
    }
7
    catch
8
    {
9
        return (m.Value);
10
    }
11
}, max);

Nur die erste Zahl inkrementiert man dann so:
* pattern = @"\d+"
* max = 1

Alle Zahlen so:
* pattern = @"\d+"
* max = -1

Und die letzte Zahl so:
* pattern = @"\d+(?!.*\d)"
* max = -1

Auch alle anderen oben gelisteten Kriterien lassen sich so abdecken.

von Joe (Gast)


Lesenswert?

Thomas W. schrieb:
> Weiß jemand Rat?

Ja. Sowas programmiert man selbst. Mit RegEx suchst du dir den Wolf in 
der Doku und musst dauernd ausprobieren. Spätestens nach einer Woche, 
hast du den kryptischen Kram größtensteils wieder vergessen.

Für sowas nehme ich gerne Pascal und Structorizer für die 
Nassi-Shneiderman Diagramme. Änderung im Diagramm verändert Code und 
umgekehrt. Das geht ratzfatz und du blickst auch noch nach Jahren durch, 
kannst es sogar in andere Sprachen übersetzen. Da kommt RegEx nicht mit.

von Thomas W. (thomaswi)


Lesenswert?

Verstehe ich nicht.
Soll ich nun eine eigene RegEx Syntax für meine User designen und die 
passende Engine dafür aus Nassi-Shneiderman Diagrammen generieren 
lassen?

Ich gebe dir ja in soweit Recht, dass ich RegEx jedes Mal wenn ich es 
benötige vorher erneut lernen muss, weil ich den kryptischen Kram nach 
spätestens einer Woche größtenteils wieder vergessen habe.

Aber RegEx ist standardisiert und in jeder Sprache verfügbar.

von Michi S. (mista_s)


Lesenswert?

Thomas W. schrieb:
> Aber RegEx ist standardisiert und

Wenn dem wirklich so wäre, dann bräuchte regex101.com wohl kaum 8 
Flavours zur Auswahl anbieten, oder?

> in jeder Sprache verfügbar.

Aber jede Implementierung bringt eben leider ihre eigenen Macken und 
Sonderlocken mit, auch wenn die Gemeinsamkeiten natürlich dominieren.


Thomas W. schrieb:
> Soll ich nun eine eigene RegEx Syntax für meine User designen

Nö, das ist eher keine gute Idee, außer Du machst es aus Spaß am 
Syntax-Design und Doku dafür schreiben; vor allem letzteres ist dabei 
ein nicht zu unterschätzender Aufwand und wird trotzdem zwangsläufig 
mickrig bleiben im Vergleich zur bereits online Verfügbaren Breite an 
Doku zu .NET RegEx.

Was Du aber ggf. machen könntest, wäre den passenden syntactic-sugar für 
Deine konkrete Anwendung zu schaffen um z.B. Deinen max-Parameter direkt 
in die RegEx schreiben zu können.

von Thomas W. (thomaswi)


Angehängte Dateien:

Lesenswert?

>> Aber RegEx ist standardisiert und
> Wenn dem wirklich so wäre, dann bräuchte regex101.com wohl kaum 8
> Flavours zur Auswahl anbieten, oder?
Vermutlich weil es 8 Standards gibt ;)

> Was Du aber ggf. machen könntest, wäre den passenden syntactic-sugar für
> Deine konkrete Anwendung zu schaffen um z.B. Deinen max-Parameter direkt
> in die RegEx schreiben zu können.
Hatte ich auch kurz drüber nachgedacht und schnell wieder verworfen.
Ich möchte nicht den 9ten Standard schaffen... ;)

Mit der aktuellen Lösung bin ich sehr zufrieden.
So wie Screenshot ist es voreingestellt.
Der versierte User kann das beliebig anpassen.
Die aktive Methode wird dann per Drop-Down im Ribbon ausgewählt.

von Michi S. (mista_s)


Lesenswert?

Thomas W. schrieb:
> Für die erste Nummer wäre das entsprechend:
> (?<!\d.*)\d+
> Das geht aber nicht,

Hmm... also auf regex101.com funktioniert das bestens.

> weil Das Lookbehind Pattern durch das "*" dynamisch wird.

In der von Dir verlinkten Doku finde ich keinen Hinweis darauf, daß es 
für Lookbehind-Pattern andere Einschränkungen als für Lookahead-Pattern 
geben würde; im Gegenteil, in allen vier Varianten findet sich die 
Aussage:
1
Here, subexpression is any regular expression pattern.


Bei meinen eigenen Versuchen habe ich erstmal folgendes Pattern 
gebastelt, das wunderbar geklappt hat:
1
(?<=^\D*)\d+

Erst dann ist mir aufgefallen, daß hier ebenfalls der * im 
Lookbehind-Pattern vorkommt, was lt. Deiner Aussage nicht sein dürfte.

Jedenfalls liefern beide Pattern bei jedem Deiner vier Teststrings 
jeweils die erste Zahl als einzigen Match; zumindest auf regex101.com

von Steve van de Grens (roehrmond)


Lesenswert?

Thomas W. schrieb:
>>> Aber RegEx ist standardisiert und
>> Wenn dem wirklich so wäre, dann bräuchte regex101.com wohl kaum 8
>> Flavours zur Auswahl anbieten, oder?
> Vermutlich weil es 8 Standards gibt

Mehr sogar. Ich weiß gar nicht, wie oft wir schon mit unserem 
Projekleiter darüber diskutieren mussten.

Ganz ätzend sind regex patterns in OpenApi Schemata, wenn z.B. eine Java 
EE Anwendung Dienste bereit stellt, die fünf andere Sprachen/Frameworks 
konsumieren, mit drei Test Tools manuell aufgerufen werden und ein 
Swagger Validator via Ci/Cd Pipeline überprüft. Ein herrliches Chaos ist 
das.

Dann diskutiere mal mit dem Projektleiter darüber, welches Pattern nun 
das "offizielle" für Email Adressen sein soll. Oder für Benutzernamen 
mit Unicode.

: Bearbeitet durch User
von Thomas W. (thomaswi)


Lesenswert?

Michi S. schrieb:
> Hmm... also auf regex101.com funktioniert das bestens.

Oh mann... das ist jetzt natürlich peinlich.
Bei all meinen Versuchen auf regex101 bin ich nie auf die Idee gekommen 
den Flavor umstellen.
Und in der Tat, in der .NET Implementierung geht es.
Bei den meisten anderen (und auch bei der Vorauswahl) geht es aus 
besagtem Grund nicht.
Gerade auch in meiner Anwendung getestet: Funktioniert dort natürlich 
auch.

Damit hätte ich mir den kompletten Thread hier sparen können, denn 
dieses Pattern mit Lookbehind war auch mein erster Versuch.

Jetzt überlege ich, ob ich die MaxMatches Eigenschaft wieder raus werfe 
und auf den ursprünglichen Ansatz zurück gehe.
Vermutlich mache ich das.

Danke!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.