Laden...

String passgenau zerteilen (enthaltene Zahl performant ermitteln)

Erstellt von shufflekeks vor 9 Jahren Letzter Beitrag vor 9 Jahren 2.703 Views
S
shufflekeks Themenstarter:in
14 Beiträge seit 2014
vor 9 Jahren
String passgenau zerteilen (enthaltene Zahl performant ermitteln)

Hallo zusammen,
ich habe folgende sehr langsame Lösung zu folgendem Problem.
Ich habe etliche Strings (ca. 6000), die ich zerteilen muss.
Bsp: G000123-Example
Mein Ziel: Lediglich die Zahl ohne führende nullen und der optionale Teil (-Example).
Mein Vorschlag:

foreach (GEDataSet.PROJEKTERow row in GEDataSet.PROJEKTE)
   try
   {
      test.Add(Convert.ToInt32(row.Projekte.Substring(1, 6)) + row.Projekte.Substring(7));
   }
   catch
   {
   }
   finally
   {
      test.Add(Convert.ToInt32(row.Projekte.Substring(1, 6)).ToString());
   }

Jetzt kann es aber auch sein, dass es zwei Buchstaben gefolgt von einer Zahl ist (GE00123). Dort schlägt natürlich die Konvertierung fehl. Auch das kann ich mit try-catch umgehen.

Dies ist, wie man sich vorstellen kann unglaublich langsam (ca. 20 Sekunden).
Hat jemand eine schnellere Idee?
Vielen Dank für die Hilfe! 😃

2.207 Beiträge seit 2011
vor 9 Jahren

Hallo shufflekeks,

das mit try/catch zu machen ist nicht schön. Verwende mal lieber TryParse. Ob das einen Geschwindigkeitsvorteil erzielt, weiss ich nicht.

Brauchst du denn den ersten Teil als Nummer, oder als String? Im zweiten Fall kannst du dir das Convert/Parsen sparen.

Alternativ kannst du auch mit RegEx arbeiten, was sicher schneller wäre.

[Artikel] Regex-Tutorial
On-the-fly Regex-Tester: Regex-Lab

Gruss

Coffeebean

S
shufflekeks Themenstarter:in
14 Beiträge seit 2014
vor 9 Jahren

Hallo Coffeebean,

Ich werde es sowohl mit TryParse als auch mit RegEx versuchen, vielen Dank für die Hinweise.
Den ersten Teil brauche ich nicht als Nummer, das Konvertieren mach ich lediglich zum entfernen der führenden Nullen.

Gruß
shufflekeks

S
shufflekeks Themenstarter:in
14 Beiträge seit 2014
vor 9 Jahren

Also TryParse dauert ca. 1 Sekunde, während einer der zwei Blöck schon 15 Sekunden braucht. Ganz klare Verbesserung!

Werde dennoch die Regulären Ausdrücke ausprobieren 😃

Gruß
ein sehr glücklicher shufflekeks 😄

49.485 Beiträge seit 2005
vor 9 Jahren

Hallo shufflekeks,

solange es nur zwei Varianten gibt (mit oder ohne zwei führenden Buchstaben), kann man mit TryParse tatsächlich relativ zügig zum Ziel kommen.

Wirklich schön ist es aber nicht. Und robust ist es auch nicht. Zum Beispiel würde gar nicht auffallen, wenn eine Zahl eine Ziffer mehr hat, sondern die zusätzliche Ziffer würde durch das Substring unerkannt abgeschnitten werden.

Daher ist es ratsam, Regex zu verwenden. Mit einem einfachen Pattern wie ^[a-z]+(\d+) kommt man leicht an die benötigte Ziffernfolge, egal wieviele Buchstaben davor stehen und wieviele Ziffern folgen. Und sollte der Aufbau ganz anders sein, bekommt man das zuverlässig mit, weil dann der Pattern gar nicht passt.

Bei Substring schneidet man quasi blind etwas heraus, und wenn das zufällig eine Ziffernfolge ist, denkt man möglicherweise fälschlich, alles wäre gut. Deshalb benutze ich selbst bei Strings mit einem festen Satzaufbau schon lange nicht mehr Substring, sondern immer Regex.

herbivore

S
shufflekeks Themenstarter:in
14 Beiträge seit 2014
vor 9 Jahren

Hallo herbivore,

danke für deinen Rat.
Allerdings halte ich mich da an die Vorgabe, dass es IMMER 7 Zeichen sind. Natürlich könnte ich mit Regulären Ausdrücken die Zahl bestimmen, egal wie lang sie ist. Aber den optionalen Teil (wie auch immer er aussieht) muss ich einfach ab einem bestimmten Char übernehmen.
Und sollte tatsächlich (auch wenn es garnicht möglich ist) die Zahl noch nicht zu ende sein, so würde ich sie einfach übernehmen.

foreach (GEDataSet.PROJEKTERow row in GEDataSet.PROJEKTE)
                {
                    int number;
                    int länge = row.PROJEKT.Length;
                    bool result = Int32.TryParse(row.PROJEKT.Substring(1, 6), out number);
                    if (result)
                    {
                        if (länge <= 7)
                            Hilf.Rows[i][2] = number;
                        else
                            Hilf.Rows[i][2] = number + row.PROJEKT.Substring(7);
                    }
                    else
                    {
                        if (länge <= 7)
                            Hilf.Rows[i][2] = Convert.ToInt32(row.PROJEKT.Substring(2, 5));
                        else
                            Hilf.Rows[i][2] = Convert.ToInt32(row.PROJEKT.Substring(2, 5)) + row.PROJEKT.Substring(7);
                    }
                    i++;
                }

Natürlich könnte Ich auf die Nase fallen, wenn es irgendwann man ein Projekt gibt, welches mit 3 Buchstaben beginnt..

Du hast mich überzeugt, ich nehme die Regulären 😃

Gruß
shufflekeks

1.361 Beiträge seit 2007
vor 9 Jahren

Hi shufflekeks,

ich würde auch zu Regulären Ausdrücken greifen. Sie machen den Code kürzer, behalten alle Formatierungsinformationen in einem String (und nicht verstreut in sechs verschiedenen Zahlen über deine Methode) und lassen sich wesentlich leichter von der Logik anpassen.

Entweder kannst de wie herbivore vorschlägt, die Teile einzeln auslesen: Das Präfix, die Zahl, der optionale Text; und diese danach wieder zusammensetzen. Dann kannst (und solltest) du auch alle Erwartungen bezüglich der Längen dieser Teile im Code via Asserts oder Exceptions formulieren,

        Regex extractor = new Regex(@"^\D*(?<number>0*(?<nonzeros>\d+))(?<suffix>.*)$");
        {
        Func<string, string> onlyNumber = (string s) => 
        {
            var match = extractor.Match(s);
            if(!match.Success || match.Groups["number"].Value.Length != 6)
                throw new FormatException ("Input does not follow spec.");
            return match.Groups["nonzeros"].Value + match.Groups["suffix"].Value;
        };
        
        // Examples
        Console.WriteLine(onlyNumber("G000123-Example"));
        Console.WriteLine(onlyNumber("GE000123-Example"));
        Console.WriteLine(onlyNumber("G000000-Example"));
        Console.WriteLine(onlyNumber("S000000-"));
        Console.WriteLine(onlyNumber("S000001"));
        Console.WriteLine(onlyNumber("S00000017")); // -> exception
        Console.WriteLine(onlyNumber("S456")); // -> exception
        Console.WriteLine(onlyNumber("F000")); // -> exception

Aber du kannst natürlich auch sagen: Eigentlich will ich nur diesen Präfix und die bis zu 5 führenden Nullen abschneiden.
Dann machst du eben genau das:

        Regex replacer = new Regex(@"(^\D*0{0,5})");
        Func<string, string> onlyNumber = (string s) => replacer.Replace(s,"");
        
        // Examples
        Console.WriteLine(onlyNumber("G000123-Example"));
        Console.WriteLine(onlyNumber("GE000123-Example"));
        Console.WriteLine(onlyNumber("G000000-Example"));
        Console.WriteLine(onlyNumber("S000000-"));
        Console.WriteLine(onlyNumber("S000001"));
        Console.WriteLine(onlyNumber("S00000017"));  // -> enthält noch Nullen
        Console.WriteLine(onlyNumber("S456"));
        Console.WriteLine(onlyNumber("F000"));  // -> Zahl fehlt
        }

Wie du siehst, gibt es aber hierbei zwei Fälle, die vielleicht nicht gerade für eine "robuste" Logik sprechen. Aber dank Regulären Ausdrücken kannst du das ganz leicht anpassen, indem du statt der fixen bis zu 5 Nullen sagst, dass so viele Nullen wie möglich abschneiden willst, aber danach noch mindestens eine Ziffer stehen bleiben soll (da sonst die Zahl Null komplett entfernt wird):

^\D*0*(?=\d)

beste Grüße
zommi

S
shufflekeks Themenstarter:in
14 Beiträge seit 2014
vor 9 Jahren

Hallo zommi,

genau so hatte ich es gemeint, vielen Dank für deine Mühe.
Hatte die Idee mit alle Nullen, unabhängig wieviele es sind, schon berücksichtigt.
Tu mich allerdings noch schwer mit der Syntax.
Hab Regex bisher nur einmal kurz in Scala programmiert.
Aber das wird schon.

Vielen Dank für eure Mühen!

Lieben Gruß
shufflekeks

P.s.: Nachdem ich mir nochmal die Replace-Methode angeschaut habe ist alles sonnenklar! Ansonsten habe ich ja noch das Tutorial als Gedankenstütze!
Danke 😃

1.361 Beiträge seit 2007
vor 9 Jahren

Tu mich allerdings noch schwer mit der Syntax.

Das glaub ich, RegEx sind ja noch wie eine eigene Programmiersprache. Aber da man sie nahezu überall gewinnbringend einsetzen kann, selbst auf der Konsole, lohnt sich der Lernaufwand.
Regex und SQL sollte in jeden Programmiererwerkzeugkasten gehören, egal welche Basis-Sprache man verwendet.

Neben den oben geposteten Forums-Link kann ich auch Regex101 empfehlen.
Hier beispielsweise der Link zum obigen Pattern ^\D*(?<number>0*(?<nonzeros>\d+))(?<suffix>.*)$. Es entspricht zwar nicht 100% den Fähigkeiten der .NET-Regexes, kommt dem im pcre Mode aber schon sehr nahe.
Wenn du dann feine .NET-Details testen willst, führt kein Weg an herbivores RegexLab vorbei 😉

beste Grüße
zommi