複数のstring
フィールドを持ついくつかのレコードを古いデータベースから新しいデータベースにインポートしています。それは非常に遅いようで、私がこれをしているからだと思います:
foreach (var oldObj in oldDB)
{
NewObject newObj = new NewObject();
newObj.Name = oldObj.Name.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
.Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
.Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
newObj.Surname = oldObj.Surname.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
.Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
.Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
newObj.Address = oldObj.Address.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
.Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
.Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
newObj.Note = oldObj.Note.Trim().Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š')
.Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć')
.Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
/*
... some processing ...
*/
}
今、私はネットでいくつかの投稿や記事を読んだことがあります。そこでは、これについてさまざまな考えがありました。 MatchEvaluator
を使用して正規表現を実行した方が良いと言う人もいれば、そのままにしておくのが最善だと言う人もいます。
自分でベンチマークケースを作成する方が簡単な場合もありますが、誰かが同じ質問について疑問に思っている場合や、誰かが事前に知っている場合のために、ここで質問することにしました。
それで、C#でこれを行う最も速い方法は何ですか?
[〜#〜]編集[〜#〜]
ベンチマーク here を投稿しました。一見すると、リチャードの方法が最速かもしれません。しかし、彼の方法もマークの方法も、Regexパターンが間違っているため、何もしません。からパターンを修正した後
@"\^@\[\]`\}~\{\\"
に
@"\^|@|\[|\]|`|\}|~|\{|\\"
チェーンされた.Replace()呼び出しの古い方法が結局最速のように見えます
ご協力ありがとうございます。私はあなたの入力をテストするために迅速で汚いベンチマークを書きました。 500.000回の反復で4つの文字列の解析をテストし、4つのパスを実行しました。結果は次のとおりです。
*** Pass 1 Old(Chained String.Replace())way completed in 814 ms logicnp(ToCharArray)way completed in 916 ms oleksii( StringBuilder)943 msで完了した方法 AndréChristoffer Andersen(Lambda w/Aggregate)方法は2551 msで完了 Richard(Regex w/MatchEvaluator)方法は215 msで完了 Marc Gravell (静的正規表現)1008ミリ秒で完了した方法 ***パス2 Old(Chained String.Replace())方法は786ミリ秒で完了しました logicnp( ToCharArray)方法は920ミリ秒で完了しました oleksii(StringBuilder)方法は905ミリ秒で完了しました AndréChristoffer Andersen(Lambda w/Aggregate)方法は2515ミリ秒で完了しました Richard(Regex w/MatchEvaluator)217 msで完了した方法 Marc Gravell(Static Regex)の方法は1025 msで完了 *** 3 Old(Chained String.Replace( ))775 msで完了した方法 logicnp(ToCharArray)903 msで完了した方法 oleksii(StringBuilder)方法は931 msで完了 AndréChristoffer Andersen(Lambda w/Aggregate)ウェイは2529 msで完了しました Richard(Regex w/MatchEvaluator)ウェイは214 msで完了しました Marc Gravell(Static Regex)ウェイは1022 msで完了しました *** Pass 4 Old(Chained String.Replace())ウェイは799ミリ秒で完了しました logicnp(ToCharArray)ウェイは908ミリ秒で完了しました oleksii(StringBuilder)の方法は938 msで完了しました AndréChristoffer Andersen(Lambda w/Aggregate)の方法は2592 msで完了しました Richard(Regex w/MatchEvaluator)の方法は225 msで完了しました Marc Gravell(Static Regex)の方法は1050ミリ秒で完了しました
このベンチマークのコードは次のとおりです。コードを確認し、@ Richardが最速の方法であることを確認してください。出力が正しいかどうかはチェックしていませんが、正しいと思いました。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace StringReplaceTest
{
class Program
{
static string test1 = "A^@[BCD";
static string test2 = "E]FGH\\";
static string test3 = "ijk`l}m";
static string test4 = "nopq~{r";
static readonly Dictionary<char, string> repl =
new Dictionary<char, string>
{
{'^', "Č"}, {'@', "Ž"}, {'[', "Š"}, {']', "Ć"}, {'`', "ž"}, {'}', "ć"}, {'~', "č"}, {'{', "š"}, {'\\', "Đ"}
};
static readonly Regex replaceRegex;
static Program() // static initializer
{
StringBuilder pattern = new StringBuilder().Append('[');
foreach (var key in repl.Keys)
pattern.Append(Regex.Escape(key.ToString()));
pattern.Append(']');
replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
}
public static string Sanitize(string input)
{
return replaceRegex.Replace(input, match =>
{
return repl[match.Value[0]];
});
}
static string DoGeneralReplace(string input)
{
var sb = new StringBuilder(input);
return sb.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ').ToString();
}
//Method for replacing chars with a mapping
static string Replace(string input, IDictionary<char, char> replacementMap)
{
return replacementMap.Keys
.Aggregate(input, (current, oldChar)
=> current.Replace(oldChar, replacementMap[oldChar]));
}
static void Main(string[] args)
{
for (int i = 1; i < 5; i++)
DoIt(i);
}
static void DoIt(int n)
{
Stopwatch sw = new Stopwatch();
int idx = 0;
Console.WriteLine("*** Pass " + n.ToString());
// old way
sw.Start();
for (idx = 0; idx < 500000; idx++)
{
string result1 = test1.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
string result2 = test2.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
string result3 = test3.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
string result4 = test4.Replace('^', 'Č').Replace('@', 'Ž').Replace('[', 'Š').Replace(']', 'Ć').Replace('`', 'ž').Replace('}', 'ć').Replace('~', 'č').Replace('{', 'š').Replace('\\', 'Đ');
}
sw.Stop();
Console.WriteLine("Old (Chained String.Replace()) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");
Dictionary<char, char> replacements = new Dictionary<char, char>();
replacements.Add('^', 'Č');
replacements.Add('@', 'Ž');
replacements.Add('[', 'Š');
replacements.Add(']', 'Ć');
replacements.Add('`', 'ž');
replacements.Add('}', 'ć');
replacements.Add('~', 'č');
replacements.Add('{', 'š');
replacements.Add('\\', 'Đ');
// logicnp way
sw.Reset();
sw.Start();
for (idx = 0; idx < 500000; idx++)
{
char[] charArray1 = test1.ToCharArray();
for (int i = 0; i < charArray1.Length; i++)
{
char newChar;
if (replacements.TryGetValue(test1[i], out newChar))
charArray1[i] = newChar;
}
string result1 = new string(charArray1);
char[] charArray2 = test2.ToCharArray();
for (int i = 0; i < charArray2.Length; i++)
{
char newChar;
if (replacements.TryGetValue(test2[i], out newChar))
charArray2[i] = newChar;
}
string result2 = new string(charArray2);
char[] charArray3 = test3.ToCharArray();
for (int i = 0; i < charArray3.Length; i++)
{
char newChar;
if (replacements.TryGetValue(test3[i], out newChar))
charArray3[i] = newChar;
}
string result3 = new string(charArray3);
char[] charArray4 = test4.ToCharArray();
for (int i = 0; i < charArray4.Length; i++)
{
char newChar;
if (replacements.TryGetValue(test4[i], out newChar))
charArray4[i] = newChar;
}
string result4 = new string(charArray4);
}
sw.Stop();
Console.WriteLine("logicnp (ToCharArray) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");
// oleksii way
sw.Reset();
sw.Start();
for (idx = 0; idx < 500000; idx++)
{
string result1 = DoGeneralReplace(test1);
string result2 = DoGeneralReplace(test2);
string result3 = DoGeneralReplace(test3);
string result4 = DoGeneralReplace(test4);
}
sw.Stop();
Console.WriteLine("oleksii (StringBuilder) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");
// André Christoffer Andersen way
sw.Reset();
sw.Start();
for (idx = 0; idx < 500000; idx++)
{
string result1 = Replace(test1, replacements);
string result2 = Replace(test2, replacements);
string result3 = Replace(test3, replacements);
string result4 = Replace(test4, replacements);
}
sw.Stop();
Console.WriteLine("André Christoffer Andersen (Lambda w/ Aggregate) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");
// Richard way
sw.Reset();
sw.Start();
Regex reg = new Regex(@"\^|@|\[|\]|`|\}|~|\{|\\");
MatchEvaluator eval = match =>
{
switch (match.Value)
{
case "^": return "Č";
case "@": return "Ž";
case "[": return "Š";
case "]": return "Ć";
case "`": return "ž";
case "}": return "ć";
case "~": return "č";
case "{": return "š";
case "\\": return "Đ";
default: throw new Exception("Unexpected match!");
}
};
for (idx = 0; idx < 500000; idx++)
{
string result1 = reg.Replace(test1, eval);
string result2 = reg.Replace(test2, eval);
string result3 = reg.Replace(test3, eval);
string result4 = reg.Replace(test4, eval);
}
sw.Stop();
Console.WriteLine("Richard (Regex w/ MatchEvaluator) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms");
// Marc Gravell way
sw.Reset();
sw.Start();
for (idx = 0; idx < 500000; idx++)
{
string result1 = Sanitize(test1);
string result2 = Sanitize(test2);
string result3 = Sanitize(test3);
string result4 = Sanitize(test4);
}
sw.Stop();
Console.WriteLine("Marc Gravell (Static Regex) way completed in " + sw.ElapsedMilliseconds.ToString() + " ms\n");
}
}
}
最速の方法
唯一の方法は、パフォーマンスを自分で比較することです。 StringBuilder
およびRegex.Replace
を使用して、Qと同様に試してください。
しかし、マイクロベンチマークはシステム全体の範囲を考慮していません。この方法がシステム全体のほんの一部である場合、そのパフォーマンスはおそらくアプリケーション全体のパフォーマンスには影響しません。
いくつかのメモ:
String
を使用すると(多くの場合)、中間文字列が大量に作成されます。GCの作業が増えます。しかし、それは簡単です。StringBuilder
を使用すると、置換のたびに同じ基本データを変更できます。これによりゴミが減ります。 String
を使用するのとほとんど同じくらい簡単です。regex
の使用は最も複雑です(置換を行うためにコードが必要になるため)が、単一の式を使用できます。置換のリストが非常に大きく、入力文字列での置換がまれである場合を除き(つまり、ほとんどのreplaceメソッド呼び出しは何も置換せず、文字列全体を検索するだけのコストがかかります)、これは遅くなると思います。GCの負荷が少ないため、繰り返し使用(数千回)よりも#2の方が少し速いと思います。
正規表現アプローチの場合、次のようなものが必要です。
newObj.Name = Regex.Replace(oldObj.Name.Trim(), @"[@^\[\]`}~{\\]", match => {
switch (match.Value) {
case "^": return "Č";
case "@": return "Ž";
case "[": return "Š";
case "]": return "Ć";
case "`": return "ž";
case "}": return "ć";
case "~": return "č";
case "{": return "š";
case "\\": return "Đ";
default: throw new Exception("Unexpected match!");
}
});
これは、再利用可能な方法で Dictionary<char,char>
を使用してパラメーター化し、置換と再利用可能な MatchEvaluator
を保持することで実行できます。
これを試して:
Dictionary<char, char> replacements = new Dictionary<char, char>();
// populate replacements
string str = "mystring";
char []charArray = str.ToCharArray();
for (int i = 0; i < charArray.Length; i++)
{
char newChar;
if (replacements.TryGetValue(str[i], out newChar))
charArray[i] = newChar;
}
string newStr = new string(charArray);
可能な解決策の1つは、これにStringBuilder
クラスを使用することです。
最初にコードを単一のメソッドにリファクタリングできます
public string DoGeneralReplace(string input)
{
var sb = new StringBuilder(input);
sb.Replace("^", "Č")
.Replace("@", "Ž") ...;
}
//usage
foreach (var oldObj in oldDB)
{
NewObject newObj = new NewObject();
newObj.Name = DoGeneralReplace(oldObj.Name);
...
}
これには、文字マップでAggregateを使用してラムダ式を使用できます。
//Method for replacing chars with a mapping
static string Replace(string input, IDictionary<char, char> replacementMap) {
return replacementMap.Keys
.Aggregate(input, (current, oldChar)
=> current.Replace(oldChar, replacementMap[oldChar]));
}
これは次のように実行できます。
private static void Main(string[] args) {
//Char to char map using <oldChar, newChar>
var charMap = new Dictionary<char, char>();
charMap.Add('-', 'D'); charMap.Add('|', 'P'); charMap.Add('@', 'A');
//Your input string
string myString = "asgjk--@dfsg||jshd--f@jgsld-kj|rhgunfh-@-nsdflngs";
//Your own replacement method
myString = Replace(myString, charMap);
//out: myString = "asgjkDDAdfsgPPjshdDDfAjgsldDkjPrhgunfhDADnsdflngs"
}
まあ、私はtryのようなことをします:
static readonly Dictionary<char, string> replacements =
new Dictionary<char, string>
{
{']',"Ć"}, {'~', "č"} // etc
};
static readonly Regex replaceRegex;
static YourUtilityType() // static initializer
{
StringBuilder pattern = new StringBuilder().Append('[');
foreach(var key in replacements.Keys)
pattern.Append(Regex.Escape(key.ToString()));
pattern.Append(']');
replaceRegex = new Regex(pattern.ToString(), RegexOptions.Compiled);
}
public static string Sanitize(string input)
{
return replaceRegex.Replace(input, match =>
{
return replacements[match.Value[0]];
});
}
これは(上部に)維持する単一の場所があり、置換を処理するためにプリコンパイルされたRegex
を構築します。すべてのオーバーヘッドは1つだけ行われます(つまり、static
)。
IndexOfAnyを使用したハイブリッドStringBuilderアプローチ:
protected String ReplaceChars(String sIn)
{
int replChar = sIn.IndexOfAny(badChars);
if (replChar < 0)
return sIn;
// Don't even bother making a copy unless you know you have something to swap
StringBuilder sb = new StringBuilder(sIn, 0, replChar, sIn.Length + 10);
while (replChar >= 0 && replChar < sIn.Length)
{
char? c = sIn[replChar];
string s = null;
// This approach lets you swap a char for a string or to remove some
// If you had a straight char for char swap, you could just have your repl chars in an array with the same ordinals and do it all in 2 lines matching the ordinals.
switch (c)
{
case "^": c = "Č";
...
case '\ufeff': c = null; break;
}
if (s != null) sb.Append(s);
else if (c != null) sb.Append(c);
replChar++; // skip over what we just replaced
if (replChar < sIn.Length)
{
int nextRepChar = sIn.IndexOfAny(badChars, replChar);
sb.Append(sIn, replChar, (nextRepChar > 0 ? nextRepChar : sIn.Length) - replChar);
replChar = nextRepChar;
}
}
return sb.ToString();
}