Laden...

Nur ein Vorkommnis mit Regex.Replace ersetzen

Erstellt von alimurat12 vor 8 Jahren Letzter Beitrag vor 8 Jahren 3.857 Views
A
alimurat12 Themenstarter:in
1 Beiträge seit 2016
vor 8 Jahren
Nur ein Vorkommnis mit Regex.Replace ersetzen

Hallo,

gegeben ist folgender Inhalt als Beispiel einer Datei, die ich einlesen


Abschnitt der Datei
  Zeile 1
  Zeile 2
Abschnitt zu Ende

Abschnitt der Datei
  Zeile 1
  Zeile 2
Abschnitt zu Ende

Mit Regex.Replace möchte ich nur nur das erste Vorkommnis bzgl. "Abschnitt der Datei" mit "ein anderer Text" ersetzten. Das zweite Vorkommnis soll jedoch davon unberührt bleiben.


string result = Regex.Replace(dateiInhalt, "Abschnitt der Datei", "ein anderer Text", RegexOptions.Singleline);

Also das Ergebnis soll so aussehen:


ein anderer Text
  Zeile 1
  Zeile 2
Abschnitt zu Ende

Abschnitt der Datei
  Zeile 1
  Zeile 2
Abschnitt zu Ende

Soweit ich weiß, ersetzt Regex.Replace auf einem Schlag alle treffer. Ich hätte nicht mal die Chance mit einer while Schleife eine Abfrage zu definieren, dass er bei dem ersten Treffer abbricht.
Gibt es einen Trick/Workaround um nur ein Vorkommnis mit Regex.Replace zu ersetzen?

Vielen Dank

656 Beiträge seit 2008
vor 8 Jahren

Grundsätzlich ist fraglich, ob du Plain-Text per Regex.Replace (statt string.Replace) machen solltest...außer dein "Abschnitt der Datei" ist nur ein Platzhalter für einen tatsächlichen Regulären Ausdruck.

Ich persönlich würde einen Blick auf den Regex.Replace Overload mit MatchEvaluator werfen.

49.485 Beiträge seit 2005
vor 8 Jahren

Hallo alimurat12,

ein Blick in die Doku hätte dir gezeigt, dass es auch Überladungen von Regex.Replace mit dem Parameter count gibt, der bestimmt, wie viele Ersetzungen durchgeführt werden sollen. In deinem Fall würdest du für count 1 übergeben.

Hallo BhaaL,

... solche Überladungen gibt es für String.Replace wohl nicht. Ich habe jedenfalls in der Doku keine gefunden. Weshalb String.Replace für den konkreten Anwendungsfall wohl nicht hilft.

herbivore

656 Beiträge seit 2008
vor 8 Jahren

... solche Überladungen gibt es für String.Replace wohl nicht. Ich habe jedenfalls in der Doku keine gefunden. Weshalb String.Replace für den konkreten Anwendungsfall wohl nicht hilft.

Schon klar, aber Regex für ein einfaches String-Replace kommt mir vor wie mit Kanonen auf Spatzen 😃
Und da könnte man zb. auch auf string.Split mit Limit ausweichen, wenns eh ein statischer String ist. Aber wie du schon sagtest, das ist nichts was man mit einem Blick in die Doku und etwas Kreativität nicht selber rausfinden könnte.

49.485 Beiträge seit 2005
vor 8 Jahren

Hallo BhaaL,

möglicherweise sind es sehr effiziente Kanonen. 😃

Soweit ich weiß ist Regex.Replace sehr effizient implementiert, wenn der Pattern aus einem statischen String (mit mindestens fünf Zeichen) besteht (oder mit einem solchen statischen String beginnt). Dann wird - wenn mich recht erinnere - der String nicht Zeichen für Zeichen durchsucht, sondern es können bestimmte Stellen übersprungen werden, von denen man aufgrund der trickreichen Implementierung weiß, dass dort der Pattern nicht passen kann.

Nehmen wir an, der statische String ist 10 Zeichen lang. Dann vergleicht man zuerst das 10 Zeichen des Textes mit dem 10 Zeichen des Strings. Wenn das nicht passt, dann kann der String nicht am Anfang des Textes passen. Wenn dieses Zeichen nun aber an keiner anderen Stelle im String vorkommt (was man mit etwas Vorarbeit natürlich effizienter prüfen kann, als den statischen String nach diesem Zeichen zu durchsuchen), dann kann der Pattern aber auch an keiner anderen der ersten 10 Stellen im Text passen. Man kann also gleich 10 Zeichen weiter springen und das Spiel von neuem beginnen. So muss man (oder eben Regex.Replace) im Idealfall nur alle 10 Zeichen prüfen. Je länger der statische String, desto größer die (möglichen) Sprünge.

Wie gesagt, ich gehe davon aus, dass Regex.Replace so oder so ähnlich implementiert ist.

Mag natürlich sein, dass String.Replace ebenso trickreich vorgeht. Aber das ist ungewiss (mit dem Reflector sieht man nur, dass eine interne Methode aufgerufen wird) und so ist es nicht per se gesagt, dass Regex.Replace schlechter oder langsamer ist, als String.Replace, möglicherweise sogar deutlich schneller. (Was aber sowieso erst bei sehr, sehr langen Strings einen spürbaren Unterschied machen wird oder machen kann.)

Bei Regex.Replace muss man natürlich aufpassen, dass der String keine Zeichen enthält, die eine Sonderbedeutung haben, also nicht für sich selbst stehen. Oder falls solche Zeichen vorkommen, muss man Regex.Escape verwenden oder selber passend escapen.

herbivore

3.003 Beiträge seit 2006
vor 8 Jahren

Bei allen (mir bekannten) ernstzunehmenden Vergleichen war string.replace etwa 40% schneller als RegEx.Replace, trotz dessen Optimierungen.

Beispiel:
blog.msdn.com String.Replace vs. StringBuilder.Replace vs. RegEx.Replace()

RegEx hat seine Stärken, aber die sind sicher nicht im Bereich reiner Zeichenkettenoperationen zu suchen.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

49.485 Beiträge seit 2005
vor 8 Jahren

Hallo LaTino,

in dem Link wird - wenn ich das richtige verstanden habe - "\n" durch "" ersetzt. Also ein einzelnes Zeichen durch nichts. Da kann die von mir beschriebene Optimierung natürlich nicht greifen. Außerdem wird der Pattern als String und nicht als Regex-Objekt übergeben, so dass der (wenn auch kurze) Pattern jedes Mal neu geparst bzw. aus dem Pattern-Cache gesucht werden muss. Und es wird wohl nicht RegexOptions.Compiled verwendet. Alles Faktoren, die je nach konkreter Ersetzungsaufgabe relevant sein könnten.

Und String.Replace scheidet hier wie gesagt sowieso aus, weil man es nicht auf das Ersetzen des ersten Vorkommens beschränken kann.

Selbst wenn der String nur einmal vorkommen würde und damit String.Replace und Regex.Replace dasselbe Ergebnis liefern würden, wäre bei sehr, sehr langen Texten ein Regex.Replace mit count=1, das somit Abbrechen würde, sobald der String gefunden (und ersetzt ist) möglicherweise um Faktoren schneller als ein vielleicht im Normalfall schnelleres String.Replace. Je nachdem wie lang der Text tatsächlich ist und wie früh der String darin vorkommt. [EDIT] Ok, bei Replace muss der String als nonmutable Objekt natürlich immer bis zum Ende kopiert werden, was dieses Argument für diesen Fall vermutlich ad absurdum führt. [/EDIT]

Ich will gar nicht sagen, dass Regex.Replace immer die beste Lösung ist. Ich will nur sagen, es kommt immer darauf an. Und es kommt natürlich darauf an, welche Kriterien man für "besser" hat, also auf welche Aspekte man abhebt.

Hinzukommt, dass es bei einer einmaligen Ersetzung in einem einzelnen überschaubar langen Text sowieso fast immer völlig egal ist, ob der Aufruf ein paar Mikro- oder vielleicht auch Millisekunden mehr oder weniger dauert. 😃

herbivore

P
1.090 Beiträge seit 2011
vor 8 Jahren

Ich muss da jetzt grade an was denken was ein Kollege gemacht hat. Bei unseren Logger hat der das String.Format in den Logger verschoben um vorher zu Prüfen ob geloggt wird und String.Format nur aufzurufen wenn es gebraucht wird. Umstellen alle Einträge hat grob 1nen Tage gedauert, da wir im allgemeinen nur bei Exceptions Loggen, sparen wir bei einer Exception 0,004 Millisekunden (Habs mal auf meinem Rechner gemessen). Hat sich echt gelohnt.

used to say (when asked about optimization)

Don't.  
Don't Yet (for experts only). 

WikiWikiWeb:Rules Of Optimization

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

3.003 Beiträge seit 2006
vor 8 Jahren

Ich will gar nicht sagen, dass Regex.Replace immer die beste Lösung ist. Ich will nur sagen, es kommt immer darauf an. Und es kommt natürlich darauf an, welche Kriterien man für "besser" hat, also auf welche Aspekte man abhebt.

Da hast du natürlich recht. RegEx ist prima, um mit Mustern zu arbeiten (ersetze das erste und dritte Vorkommen von zwei Kleinbuchstaben, gefolgt von einigen Zahlen, gefolgt von drei Großbuchstaben und einem Whitespace). Für so simple Operationen allerdings ist String.Replace() die bessere Wahl, gefolgt vom StringBuilder (den ich, als ich mich damit das erste mal beschäftigt habe, als Mittel der Wahl gesehen hätte, eben wegen der immutable-Geschichte). Faustregel ist seitdem bei mir string>stringbuilder>regex, und da dürfte ich auch bei großen Zeichenketten in 95% der Fälle richtig liegen oder wenigstens nicht völlig falsch liegen. Ich habe noch mal geschaut, ob's irgendwo für den internen Aufruf von string.replace eine Quelle gibt, aber leider auch nichts gefunden. Nur einige Quellen, die die vergleichsweise außerordentlich gute Performance von Replace(), Format() (<- @Palin) und insbesondere string.join() hervorheben. Palin's Einwand ist auch nicht von der Hand zu weisen, auch wenn ihr da versucht habt, an einer Stelle zu optimieren, die eh schon performant war und wohl auch keine Probleme gemacht hat 😉.

(Code-Optimierung im Nachgang ist auch so ein Reflex, den man bei sich bewusst unterdrücken muss...)

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

49.485 Beiträge seit 2005
vor 8 Jahren

Hallo LaTino,

du bist leider nicht darauf eingegangen, dass String-Replace mangels passender Überladung nicht dafür taugt, wie in diesem Thread gefragt, nur das erste Vorkommen zu ersetzen. Da führt deine Regel string>stringbuilder>regex m.E. trotzdem zu Regex. Meine Regel sieht ohnehin anders aus, nämlich regex>string>stringbuilder, und zwar aus dem Grund, dass für mich Lesbarkeit des Codes und Flexibilität desselben wichtiger sind, solange die Performance keine Probleme macht (*). Ich finde es unpassend, für die konkrete Aufgabe mit String.Replace oder String.Split und zusätzlichem Code rumzubasteln, um es irgendwie hinzubekommen, wenn es bei Regex.Replace reicht, einen zusätzlichen Parameter (count) anzugeben, um das Ziel zu erreichen.(**)

Nun kann man vielleicht noch einwenden, dass Regex eine Sprache ist, die nicht besonders gut lesbar ist und daher meiner eigenen Regel widerspricht. In der Tat ist es einfacher einen Regex-Pattern zu schreiben als ihn zu lesen. Man muss sich allerdings die Alternative vor Augen halten. So komplexe Regex-Pattern, die man nicht mehr einfach erfassen kann, würden mit String- oder StringBuilder-Operationen ausprogrammiert typischerweise solche eine Code-Menge und -Komplexität erfordern, dass dieser Code eben auch nicht mehr gut lesbarer wäre, meistens schlechter. Und weniger flexibel ist der entstehende Code auf jeden Fall.

Genau diesen Vergleich, nämlich Regex-Pattern vs. ausprogrammierter Code, habe ich mit dN!3Ls Hilfe als anfänglichen Verfechters des Ausprogrammierens exemplarisch für einen praktischen Fall im Programmierspiel durchgespielt. Siehe Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch für die Aufgabe und die folgenden Beiträge für den Lösungsweg/Vergleich.

Daraus erklärt ich, warum in meiner Regel regex>string>stringbuilder Regex ganz vorne steht. Natürlich gibt es auch Fälle, in denen ich mich für String oder StringBuilder entscheide, nämlich wenn mich die Performance zwingt (meist lande ich dann bei StringBuilder) oder es im jeweiligen Fall lesbareren Code ergibt, einfache String-Operationen zu verwenden.

Ich denke, wir haben - wenn ich nichts übersehe - jetzt die wesentlichen Argumente getauscht. Letztlich muss es sowieso jeder für sich entscheiden.

herbivore

(*) Wobei ich natürlich trotzdem von Anfang an darauf achte, bekannten potenziellen Performance-Problemen aus dem Weg zu gehen. Diese liegen allerdings eher selten darin, eine Methode zu wählen, die um einen konstanten Faktor schneller ist oder einen konstanten Overhead weniger benötigt, sondern darin, sich nicht durch die Codestruktur bzw. durch einen unpassenden Algorithmus eine unnötig höhere Aufwandsklasse einzuhandeln, siehe dazu Mergesort langsamer als Bubblesort? [==> Nein, Messfehler / Aufwandsklasse vs. Mikrooptimierungen].

(**) Unter Berücksichtigung des schon oben beschriebenen ggf. notwendigen Escapens.

3.003 Beiträge seit 2006
vor 8 Jahren

du bist leider nicht darauf eingegangen, dass String-Replace mangels passender Überladung nicht dafür taugt, wie in diesem Thread gefragt, nur das erste Vorkommen zu ersetzen.

Da hast du Recht, das wollte ich eigentlich auch noch machen. In diesem Fall ist der Aufruf der RegEx-Methode zwar simpler, der Regex selbst aber nicht. Es gibt m.E. einen Punkt, an dem die Komplexität von ausprogrammiertem Code die der passenden regular expression übersteigt (die Programmieraufgabe ist ein Beispiel, auch wenn ich - persönlicher Geschmack - die regex-Lösung furchtbar finde). Und erst dann sollte man, denke ich, auf RegEx zurückgreifen. Immer die Lösung, die weniger prone-to-error ist. Ich greife zunehmend seltener zu regex, weil die Rückfragen von Kollegen mir zeigen, dass der Code dadurch obfuskiert wird. Für den konkreten Fall oben müsste man mal einen Performancetest machen, neugierig wäre ich schon auf das Ergebnis.
Aber, auch da hast du Recht, im Programmierspiel-Thread sind alle Argumente längst abgehandelt worden. Für diesen simplen Fall hier wäre meine Aufwandsabschätzung jedenfalls sehr schnell zu Ungunsten von regex ausgefallen (dass der Thread existiert, ist allein schon den Nachteilen von regex zu verdanken 😉 ).

Bin dann wieder still, wenn/falls ich mal die performance für diesen Fall teste, poste ich das noch kommentarlos hier drunter 😮).

LaTino

EDIT: nichtrepräsentativer Test (string: MineCraft-EULA einige Male hintereinander, ca. 3.5 MB, je tausend Durchläufe, Ersetzen erstes Vorkommen von "Kopien" durch "Ohren"), Zeitunterschied < 5%. Ist also Hupe. Korrigieren muss ich mich, was die Kompexität in diesem Fall angeht...new Regex(find).Replace(input, replace, 1) ist jetzt wirklich kein Hexenwerk und besser lesbar als die string-Variante.

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)