2009-05-05 10 views
7

私は指定した文字以外のすべての文字を置き換える方法があります。例えば、Inverse String.Replace - それをより速く行う方法?

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

は今

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

を返します、これは時期尚早の最適化のインスタンスではありません。私はネットワーク操作中にこのメソッドをかなり数回呼び出します。長い文字列では待ち時間があり、それを取り除くと少し助けになることがわかった。これをスピードアップするための助けをいただければ幸いです。

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(); 
    } 

ファイナル# 's。また、3Kの文字列のテストケースを追加し、1Mの代わりに100K回実行して、これらのスケールのそれぞれがどれほど優れているかを確認しました。唯一の驚きは、正規表現が他のものより「より良いスケール」ということでしたが、そもそも非常に遅いので、それは何の助けではありません:

 
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 

アップデート:私はピーターのバージョンの正規表現の作成を行いました静的変数、かつ公正であることをRegexOptions.Compiledに設定します。

 
User   Short * 1M  Long * 100K  Scale 
Peter   8997   74715   8.30 
私のテストコードに

ペーストビンリンクそれが間違っている場合、私を修正してください: http://pastebin.com/f64f260ee

+0

「pattern.Contains(original [i])== false」を変更してください。 replacement:original [i] '〜' pattern.Contains(original [i])? original [i]:replacement'。 boolの式をtrue/falseと比較する必要はなく、通常は悪い形式とみなされます。 –

+0

私はこれらすべてのバージョンのスピードテストを実行しています。最新の編集は実際にはそれらの中で最も遅いですが、どのように結果が4倍速くなっていますか? –

+0

@john、私はテストを実行しながらウェブページをリロードするような何かを台無しにしないことを望んで、私はそれらを再チェックしています:) – esac

答えて

6

さてさて、〜60キロバイトの文字列で、これはあなたのバージョンより約40%速く実行します。

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(); 
} 

トリックほとんどの文字が置換されるため、すべての置換文字で新しい文字列を初期化することです。

これはどの速くなりますが、それが役立つ可能性がある、彼らは文字列ビルダに追加できるだけのでNEWINGまでの文字列を回避した場合、私は知らない
+0

+1 - ニース。しかし、この場合、++インデックスまたはindex ++式の結果が使用されていないため、パフォーマンスの差がゼロになることが保証されています(インデックスの増分方法)。私はJITがまったく同じコードを生成すると確信しています。私はcsc.exeがまったく同じILを生成しても驚くことはありません。 –

+0

@Michael - あなたが正しいと思われます。私はそれを元の状態に戻しました。同じように動作するように思われます。前回テストしたときの違いはわかりません。 –

+0

ILを表示するには、.NET Frameworkに付属のildasmおそらくSDK)、またはすべてのクールな子供たちがやっていることをやって、無料の.NET Reflectorを使うことができます:http://www.red-gate.com/products/reflector/ –

0

それはO(になるだろうn)。あなたはすべてのアルファベットと空白を*で置き換えているようですが、現在の文字がアルファベット/空白であるかどうかテストして置き換えてみませんか?

8

あなたがそうのようなRegex.Replaceを使用することはできません:

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

+1、私にそれを打つ! –

+0

これは私にも正規表現を叫びます。 .IndexOf *メソッドや.Substringメソッド(または、効率の悪い(?)「新しい文字列(replacement、index-old-1)」)を2回以上呼び出すとすぐに、比較的シンプルな正規表現で、数行のコードで処理しようとしています。 – gregmac

+0

正規表現は文字列を一度実行するより速くなりますか?私はいつも正規表現はパフォーマンスが問題であったときには役に立たなかったのですが! – dirkgently

4

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(); 
    } 

中の文字数場合patternはどんなサイズでも構いませんが(私はそれが一般的にはそうではないと推測しています)、それをソートして、Array.indexOf()の代わりにArray.BinarySearch()を実行します。

このような単純な変換では、正規表現よりも高速で問題がないと確信しています。

:あなたはメソッドのシグネチャがあることはありません、なぜまた

pattern内の文字のあなたのセットは、通常、(少なくとも、それはAPIのこのタイプの私の一般的な経験をされている)とにかく文字列から来る可能性があることから、

以上で、patternchar[]またはstringになる可能性があります。

+0

あなたのタイミングの結果を見て、私はString.IndexOfAny()を使っても、各文字にArray.IndexOfを繰り返し呼び出すよりはるかに優れていると思います。 –

2

StringBuilderには文字とカウントが必要なオーバーロードがあるため、StringBuilderに追加する中間文字列を作成する必要はありません。

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

:で

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

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

この私はこれを置き換えることによって、約20%の改善を得る

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

(Iコードをテストあなたが言ったことは約4倍速かったと私はそれを見つける約15倍遅く...)

+0

私は今興味深い質問があると思います:楽観的にStringBuilderを置換するcharまたはRaschを高速でロードするJohn Raschのルーチンよりも速いですか?私はそれがいずれかの方向に行くのを見ることができました(そして、それはデータに依存していると思われます)。 –

+0

@Michael:更新された数字がいくつか登場します。 – esac

+0

@esac - ありがとう...私は本当に興味があり、私はここで私よりも怠惰な人が見つかってうれしいです! –

4

ここで別のバージョンがあります。私のテストは、その性能がかなり良いと示唆しています。

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

非常にいいですが、あなたのスケーリングは少し外れていますが、一番上の投稿を打ち負かすことに非常に近いです。私はメインタイムを更新しました。 – esac

+0

興味深い - アルゴリズム - これは私のポストに似ています。主な違いは、Array.IndexOf()を呼び出す代わりに、StringBuffer()の代わりにchar []を使用して結果を構築する代わりに、明示的なループである点です。しかし、この実装は4〜5倍高速です... –

+0

@esac、ベンチマークは実行しているマシン/フレームワークによって大きく異なるようです:私の古いPC(P4 2.6GHz、1.5GB、XP32)では、私のバージョンジョンの倍の速さです。私の新しいPC(Core2Duo 2.66GHz、4GB、Vista64)では、Johnのバージョンは私の2倍の速さです! – LukeH

関連する問題