RCIE-ジャンクのコード屋

主に自分のためにコーディングのTIPSを蓄積しています。

(C#)複数の文字列をすべて置き換える(最長一致)

解説

 文字列を「やさしい→優しい」「いい→良い」のようなルールで置き換えることを考えてみましょう。1単語ずつ置き換えると、

  • 「やさしいいいひと」→「優しいいいひと」→「優し良いいひと」→「優し良良いひと」

のように範囲が被ってしまい、うまくいきません。複数の文字列をまとめて置き換えるには、正規表現を使います。

前提

コード

var d = new SortedDictionary<string, string>();
string s = "あいうえお"; // 置き換えの対象文字列
d["あいう"] = "アイウ"; // 置き換えのルール
d["あ"] = "ア";
d["うえお"] = "ウエオ";
d["お"] = "オ";
string rule = Regex.Escape(string.Join("\0", d.Keys.Reverse())).Replace('\0', '|');
s = new Regex(rule).Replace(s, (MatchEvaluator)(m => d[m.Groups[0].Value]));
MessageBox.Show(s); // アいウエオ

補足

 これは次のコードと等価です。

var d = new SortedDictionary<string, string>();
string s = "あいうえお"; // 置き換えの対象文字列
d["あいう"] = "アイウ"; // 置き換えのルール
d["あ"] = "ア";
d["うえお"] = "ウエオ";
d["お"] = "オ";
string join = string.Join("\0", d.Keys.Reverse()); // お\0うえお\0あいう\0あ
string escape = Regex.Escape(join); // 正規表現文字が入らないようにする
string rule = escape.Replace('\0', '|'); // お|うえお|あいう|あ
var rgx = new Regex(rule);
MatchEvaluator evaluator = delegate(Match m){
	return d[m.Groups[0].Value]; // "あいう"=>"アイウ" のようにする関数
};
s = rgx.Replace(s, evaluator);

 string.Join で、わざわざ \0 を使って結合しているのは、Regex.Escape で | が \| に変換されてしまうからです。キーの中にヌル文字が入るケースは稀ですから、これで問題ないでしょう。

 途中で、d.Keys.Reverse() で逆順に結合していますが、こうすることにより、「あいう」が「あ」より先に来て、最長一致させることができます。SortedDictionary を使っているので、辞書順にキーが格納されていることが保障されています。

 MatchEvaluator を定義することにより、正規表現で一致した文字列が出現したときの処理を詳しく定めることができます。今回は、キーを値に変換しているだけです。