Die Anzahl eines Zeichens in einem String ermitteln

Heute wurde ich mal wieder mit einem kleinen Problemchen konfrontiert. Und zwar musst ich die Anzhal eines bestimmten Zeichens innerhalb eines Strings ermitteln. Kein Problem Dachte ich mir und fing an.
Währed ich die Funktion schrieb, kam mir ein Gedanke. "Wie sieht es denn mit der Performance aus?"
Hmm. Eine gute Frage...
Rasch eine Neue Projektmappe aufgemacht und angefangen das zu Testen.

Meine erste Ansatz war jedes Zeichen des Strings durchzugehen und bei einer Übereinstimmung mit dem gesuchten Zeichen eine Zählervariable zu inkrementieren.

  1. public static int FindCharWithForEach(string source, char searchFor)
  2. {
  3. var counter = 0;
  4.  
  5. foreach (char c in source)
  6. {
  7. if (c == searchFor) counter++;
  8. }
  9.  
  10. return counter;
  11. }

Als Testszenario hab ich mir einen Verzeichnispfad (C:\Windows\System32\drivers\etc) geschnappt und wollte wissen wieviele '\' darin enthalten sind.
Die Funktion lief ohne Probleme und gab mir nach 00:00:00.0003581 das korrekte Ergebnis von 4 zurück.

Mein zweiter Ansatz war, das ganue Mit LINQ umzusetzen.

  1. public static int FindCharWithLinq(string source, char searchFor)
  2. {
  3. return source.Count(f => f == searchFor);
  4. }

Auch diese Variante lief ohen Probleme. Nur mit einer Zeit von 00:00:00.0046630 hätte ich nicht gerechnet.

Ok. Dann suchen wir mal weiter.
Mein nächste gedanke war den String einfach zu Splitten und die Anzahl der Elemente im Array (-1) zu zählen.

  1. public static int FindCharWithSplit(string source, char searchFor)
  2. {
  3. return source.Split(searchFor).Length - 1;
  4. }

Auch diese Lösung funktionierte. Allerdings war ich ein wenig überrascht, das diese Variante mit 00:00:00.0004546 doch deutlcih schnellsten als die LINQ Version war.

Nachdem ich diese Resultate gesehen hatte, dachte ich mir, das ich den Test etwas Anspruchsvoller zu gestallten und duplizierte den Pfad ein paar mal um einen möglichst langen string zu erzeugen.

  1. string value = "";
  2. for(int i = 0 ; i < 10; i++)
  3. value = value + @"C:\Windows\System32\drivers\etc";

Beim Test war das erwartete Ergebniss 40, da ich den text einfach 10 mal hintereinader kopiert habe.

LINQ: 40 hits in 00:00:00.0055382
Split: 40 hits in 00:00:00.0004590
Count: 40 hits in 00:00:00.0007574

Nach dem Ergebniss dacht ich mir eine kleine Sereie draus zu bauen und habe noch Tests für Text x 100, x 1000, x 10000, x 100000 und x 1000000 laufen lassen.
Um LINQ noch kleine zusatz Chance zu geben, habe ich noch PLINQ mit dazugenommen. Der PC auf dem die Tests laufen ist ein einfacher Dual Core mit einem Intel Core2Duo P9700 mit 2x2.80GHz.

  10 100 1.000
LINQ 40 hits in 00:00:00.0055382 400 hits in 00:00:00.0089991 4000 hits in 00:00:00.0724733
Split 40 hits in 00:00:00.0004590 400 hits in 00:00:00.0004476 4000 hits in 00:00:00.0008498
Count 40 hits in 00:00:00.0007574 400 hits in 00:00:00.0024534 4000 hits in 00:00:00.0204854
PLINQ 40 hits in 00:00:00.6701543 400 hits in 00:00:00.2043689 4000 hits in 00:00:00.2359259
 
  10.000 100.000 1.000.000
LINQ 40000 hits in 00:00:00.5656169 400000 hits in 00:00:05.5925695 4000000 hits in 00:00:56.1154690
Split 40000 hits in 00:00:00.0045024 400000 hits in 00:00:00.0489870 4000000 hits in 00:00:00.9762967
Count 40000 hits in 00:00:01.8509596 400000 hits in 00:00:03.4958518 4000000 hits in 00:00:38.0938921
PLINQ 40000 hits in 00:00:00.6517095 400000 hits in 00:00:04.8506172 4000000 hits in 00:00:45.4006163

Ich glaub zu den Ergbnissen bracht man nicht mehr viel sagen, da Diese sehr eindeutig sind.
LINQ ist kein Allheilmittel und die einfachsten Wege sind nicht unbedingt die Besten.

Ich habe nun meinen Favouriten. :)

For so long,
CJ

Kategorie: 

Neuen Kommentar schreiben