2009-05-05 12 views
7

Ich habe eine Methode, um jedes Zeichen außer den angegebenen zu ersetzen. Zum BeispielInverse String.Replace - Schneller geht es?

ReplaceNot("test. stop; or, not", ".;/\\".ToCharArray(), '*'); 

zurückkehren würden

 
"****.*****;***,****". 

Nun, dies ist nicht eine Instanz vorzeitiger Optimierung ist. Ich rufe diese Methode während einer Netzwerkoperation einige Male auf. Ich habe festgestellt, dass es bei längeren Saiten etwas Latenz verursacht, und das Entfernen hat etwas geholfen. Jede Hilfe, um dies zu beschleunigen, wäre willkommen.

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    {   
     int index = 0; 
     int old = -1; 

     StringBuilder sb = new StringBuilder(original.Length); 

     while ((index = original.IndexOfAny(pattern, index)) > -1) 
     { 
      sb.Append(new string(replacement, index - old - 1)); 
      sb.Append(original[index]); 
      old = index++; 
     } 

     if (original.Length - old > 1) 
     { 
      sb.Append(new string(replacement, original.Length - (old + 1))); 
     } 

     return sb.ToString(); 
    } 

Endgültige # s. Ich fügte auch einen Testfall für eine 3K Zeichenkette hinzu, lief bei 100K mal anstelle von 1M, um zu sehen, wie gut jede dieser Skalen ist. Die einzige Überraschung war, dass der reguläre Ausdruck ‚besser skaliert‘ als die anderen, aber es ist keine Hilfe, da es sehr langsam beginnen:

 
User   Short * 1M Long * 100K  Scale 
John   319    2125   6.66 
Luke   360    2659   7.39 
Guffa   409    2827   6.91 
Mine   447    3372   7.54 
DirkGently  1094   9134   8.35 
Michael   1591   12785   8.04 
Peter   21106   94386   4.47 

Update: Ich habe die Schaffung des regulären Ausdrucks für Peters Version eine statische Variable, und es RegexOptions.Compiled gesetzt, fair zu sein:

 
User   Short * 1M  Long * 100K  Scale 
Peter   8997   74715   8.30 

Pastebin Link zu meinem Test-Code, bitte korrigieren sie mich, wenn es falsch ist: http://pastebin.com/f64f260ee

+0

Ein nit: bitte ändern 'pattern.Contains (original [i]) == false? Ersatz: original [i] 'to' pattern.Contains (original [i])? Original [i]: Ersatz. Das Vergleichen eines bool-Ausdrucks mit wahr/falsch ist nicht notwendig und wird normalerweise als schlechte Form angesehen. –

+0

Ich habe Geschwindigkeitstests für alle diese Versionen durchgeführt, und Ihre neueste Bearbeitung ist tatsächlich die langsamste von allen, wie erhalten Sie Ergebnisse 4x schneller? –

+0

@john, ich überprüfe sie erneut, in der Hoffnung, dass ich nicht etwas vermasselt habe, wie das Laden einer Webseite während des Tests :) – esac

Antwort

6

Ordnung, auf einem ~ 60KB String, wird dies etwa 40% schneller als die Version ausführen besteht darin, eine neue Zeichenfolge mit allen Ersatzzeichen zu initialisieren, da die meisten davon ersetzt werden.

+0

+1 - Nice verwenden muss. Allerdings garantiere ich in diesem Fall, dass, da das Ergebnis des Indexes ++ oder Index ++ nicht verwendet wird, es absolut keinen Unterschied in der Leistung geben würde (soweit, wie Sie Index erhöhen). Ich bin mir sicher, dass das JIT genau den gleichen Code generieren würde. Ich wäre nicht überrascht, wenn csc.exe genau die gleiche IL generiert hätte. –

+0

@Michael - es scheint, dass Sie Recht haben, als ich es zurück änderte es scheint ähnlich zu tun, ich weiß nicht, warum es anders war, als ich es zuletzt getestet –

+0

Um IL anzuzeigen gibt es ildasm, die mit dem .NET Framework (oder vielleicht das SDK), oder du kannst tun, was all die coolen Kids machen und den kostenlosen .NET Reflektor benutzen: http://www.red-gate.com/products/reflector/ –

0

es O sein wird (n). Sie scheinen alle Alphabete und Leerzeichen durch * zu ersetzen, warum testen Sie nicht einfach, ob das aktuelle Zeichen ein Alphabet/Leerzeichen ist und ersetzen Sie es?

8

Können Sie nicht verwenden Regex.Replace wie so:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
{ 
    int index = 0; 

    StringBuilder sb = new StringBuilder(new string(replacement, original.Length)); 

    while ((index = original.IndexOfAny(pattern, index)) > -1) 
    { 
     sb[index] = original[index++]; 
    } 

    return sb.ToString(); 
} 

Der Trick:

Regex regex = new Regex(@"[^.;/\\]"); 
string s = regex.Replace("test. stop; or, not", "*"); 
+0

+1, Beat mich dazu! –

+0

Das schreit auch Regex zu mir. Sobald Sie mehr als ein paar Aufrufe von .IndexOf * oder .Substring-Methoden (oder die möglicherweise weniger effiziente (?) "Neue Zeichenfolge (Ersatz, Index - alt - 1)") haben, können Sie wahrscheinlich erreichen, was auch immer Sie sind versuchen, mit einer relativ einfachen Regex, in ein paar Zeilen Code zu tun. – gregmac

+0

Und Regex wird schneller als einmal über die Zeichenfolge laufen? Ich habe immer gesagt, dass Regex-Es nicht gut waren, wenn Leistung ein Problem war! – dirkgently

4

Ich weiß nicht, ob dies schneller sein wird, aber es vermeidet Saiten newing bis nur so können sie auf den String-Builder angehängt werden können, die helfen können:

public static string ReplaceNot(this string original, char[] pattern, char replacement) 
    { 
     StringBuilder sb = new StringBuilder(original.Length); 

     foreach (char ch in original) { 
      if (Array.IndexOf(pattern, ch) >= 0) { 
       sb.Append(ch); 
      } 
      else { 
       sb.Append(replacement); 
      } 
     } 

     return sb.ToString(); 
    } 

Wenn die Anzahl der Zeichen in pattern wird von jeder Größe sein (die ich vermute es wird in der Regel nicht), könnte es sich auszahlen, es zu sortieren und eine Array.BinarySearch() anstelle der Array.indexOf() durchzuführen.

Für solch eine einfache Umwandlung würde ich wetten, dass es kein Problem haben wird, schneller als eine Regex auch zu sein.

Da auch Ihren Satz von Zeichen in pattern wahrscheinlich ist in der Regel aus einem String trotzdem kommen (zumindest das ist schon meine allgemeine Erfahrung mit dieser Art von API), warum Sie nicht die Methode Signatur sein:

public static string ReplaceNot(this string original, string pattern, char replacement) 

oder noch besser, haben eine Überlastung wo pattern ein char[] oder string sein kann?

+0

Gut - nachdem du deine Timing Ergebnisse angesehen hast ' Ich bin ein wenig überrascht, wie schlecht das im Vergleich zu Ihrem Original ist ... Ich denke, dass die Verwendung von String.IndexOfAny() ist weit, viel besser als Array.IndexOf wiederholt auf jedem Zeichen aufrufen. –

2

Der StringBuilder hat eine Überladung, die ein Zeichen und eine Anzahl akzeptiert, so dass Sie keine Zwischenzeichenfolgen zum Hinzufügen zum StringBuilder erstellen müssen. Ich bekomme etwa 20% ige Verbesserung durch diese ersetzt:

sb.Append(new string(replacement, index - old - 1)); 

mit:

sb.Append(replacement, index - old - 1); 

und diese:

sb.Append(new string(replacement, original.Length - (old + 1))); 

mit:

sb.Append(replacement, original.Length - (old + 1)); 

(Getestet habe ich den Code das du gesagt hast war ungefähr viermal schneller, und ich finde es etwa 15 mal langsamer ...)

+0

Also, was ich denke, ist die interessante Frage jetzt: ist das schneller als John Raschs Routine, die den StringBuilder mit dem Ersatzzeichen oder Raschs schneller optimistisch lädt? Ich könnte sehen, dass es in beide Richtungen geht (und ich vermute, dass es datenabhängig ist). –

+0

@Michael: aktualisierte Zahlen kommen in einigen. – esac

+0

@esac - danke ... Ich bin wirklich interessiert, und ich bin froh, dass hier jemand ist, der weniger faul ist als ich, um es herauszufinden! –

4

Hier ist eine andere Version für Sie. Meine Tests deuten darauf hin, dass seine Leistung ziemlich gut ist.

public static string ReplaceNot(
    this string original, char[] pattern, char replacement) 
{ 
    char[] buffer = new char[original.Length]; 

    for (int i = 0; i < buffer.Length; i++) 
    { 
     bool replace = true; 

     for (int j = 0; j < pattern.Length; j++) 
     { 
      if (original[i] == pattern[j]) 
      { 
       replace = false; 
       break; 
      } 
     } 

     buffer[i] = replace ? replacement : original[i]; 
    } 

    return new string(buffer); 
} 
+0

sehr schön, obwohl Ihre Skalierung ist ein wenig, Sie sind sehr nahe an der Top-Einreichung zu schlagen :) Ich habe die wichtigsten Zeiten aktualisiert. – esac

+0

Interessant - algorithmisch ist das meinem Post ähnlich. Die Hauptunterschiede sind eine explizite Schleife, anstatt Array.IndexOf() aufzurufen und char [] zu verwenden, um das Ergebnis anstelle eines StringBuffer() zu erzeugen. Aber diese Implementierung ist 4-5x schneller ... –

+0

@esac, Es scheint, dass die Benchmarks je nach Rechner/Framework, auf dem sie laufen, stark variieren: Auf meinem alten PC (P4 2.6GHz, 1.5GB, XP32) meine Version ist doppelt so schnell wie John; Auf meinem neueren PC (Core2Duo 2.66GHz, 4GB, Vista64) ist Johns Version doppelt so schnell wie meine! – LukeH

Verwandte Themen