いくつかの置換を行う必要がある文字列があります。私はDictionary<string, string>
ここで検索と置換のペアを定義しています。この操作を実行するために、次の拡張メソッドを作成しました。
public static string Replace(this string str, Dictionary<string, string> dict)
{
StringBuilder sb = new StringBuilder(str);
return sb.Replace(dict).ToString();
}
public static StringBuild Replace(this StringBuilder sb,
Dictionary<string, string> dict)
{
foreach (KeyValuePair<string, string> replacement in dict)
{
sb.Replace(replacement.Key, replacement.Value);
}
return sb;
}
それを行うためのより良い方法はありますか?
データがトークン化されている場合(つまり、「Dear $ name $、$ date $の時点で残高は$ amount $です」)、Regex
が役立ちます。
static readonly Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
static void Main() {
string input = @"Dear $name$, as of $date$ your balance is $amount$";
var args = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase) {
{"name", "Mr Smith"},
{"date", "05 Aug 2009"},
{"amount", "GBP200"}
};
string output = re.Replace(input, match => args[match.Groups[1].Value]);
}
ただし、このようなものがなければ、Replace
ループは、極端な長さにならずに、おそらくできる限りのことになると思います。トークン化されていない場合は、プロファイルを作成してください。 Replace
は実際に問題ですか?
Linqでこれを行う:
var newstr = dict.Aggregate(str, (current, value) =>
current.Replace(value.Key, value.Value));
dictは、検索と置換のペアで定義されたディクショナリオブジェクトです。
strは、いくつかの置換を行う必要がある文字列です。
1つのことを除いて、私には合理的なようです。それは順序に敏感です。たとえば、「$ x $ y」の入力文字列と次の置換辞書を取得します。
"$x" => "$y"
"$y" => "foo"
置換の結果は、最初に実行される置換に応じて、いずれか "foofoo"または "$ yfoo"になります。
代わりにList<KeyValuePair<string, string>>
を使用して順序を制御できます。別の方法は、文字列をウォークスルーして、以降の置換操作で置換を消費しないようにすることです。しかし、それはもっと難しいでしょう。
これは、@ Marcのすばらしい答えを少しリファクタリングしたバージョンで、Regexの拡張メソッドとして機能を利用できるようにします。
static void Main()
{
string input = @"Dear $name$, as of $date$ your balance is $amount$";
var args = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
args.Add("name", "Mr Smith");
args.Add("date", "05 Aug 2009");
args.Add("amount", "GBP200");
Regex re = new Regex(@"\$(\w+)\$", RegexOptions.Compiled);
string output = re.replaceTokens(input, args);
// spot the LinqPad user // output.Dump();
}
public static class ReplaceTokensUsingDictionary
{
public static string replaceTokens(this Regex re, string input, IDictionary<string, string> args)
{
return re.Replace(input, match => args[match.Groups[1].Value]);
}
}
marc GravellのRegExソリューションを使用する場合は、最初に、ContainsKeyを使用してトークンが使用可能かどうかを確認します。これにより、KeyNotFoundExceptionエラーが防止されます。
string output = re.Replace(zpl, match => { return args.ContainsKey(match.Groups[1].Value) ? arg[match.Groups[1].Value] : match.Value; });
次のわずかに変更されたサンプルコードを使用する場合(最初のパラメーターの名前が異なります):
var args = new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase)
{
{"nameWRONG", "Mr Smith"},
{"date", "05 Aug 2009"},
{"AMOUNT", "GBP200"}
};
これにより、次のようになります。
「親愛なる$ name $、2009年8月5日現在の残高はGBP200です」
はい、どうぞ:
public static class StringExm
{
public static String ReplaceAll(this String str, KeyValuePair<String, String>[] map)
{
if (String.IsNullOrEmpty(str))
return str;
StringBuilder result = new StringBuilder(str.Length);
StringBuilder Word = new StringBuilder(str.Length);
Int32[] indices = new Int32[map.Length];
for (Int32 characterIndex = 0; characterIndex < str.Length; characterIndex++)
{
Char c = str[characterIndex];
Word.Append(c);
for (var i = 0; i < map.Length; i++)
{
String old = map[i].Key;
if (Word.Length - 1 != indices[i])
continue;
if (old.Length == Word.Length && old[Word.Length - 1] == c)
{
indices[i] = -old.Length;
continue;
}
if (old.Length > Word.Length && old[Word.Length - 1] == c)
{
indices[i]++;
continue;
}
indices[i] = 0;
}
Int32 length = 0, index = -1;
Boolean exists = false;
for (int i = 0; i < indices.Length; i++)
{
if (indices[i] > 0)
{
exists = true;
break;
}
if (-indices[i] > length)
{
length = -indices[i];
index = i;
}
}
if (exists)
continue;
if (index >= 0)
{
String value = map[index].Value;
Word.Remove(0, length);
result.Append(value);
if (Word.Length > 0)
{
characterIndex -= Word.Length;
Word.Length = 0;
}
}
result.Append(Word);
Word.Length = 0;
for (int i = 0; i < indices.Length; i++)
indices[i] = 0;
}
if (Word.Length > 0)
result.Append(Word);
return result.ToString();
}
}