正则表达式替换多个组

29

我想使用正则表达式将多个组替换为相应的替换字符串。

替换表:

  • "&" -> "__amp"
  • "#" -> "__hsh"
  • "1" -> "5"
  • "5" -> "6"

例如,对于以下输入字符串:

"a1asda&fj#ahdk5adfls"

对应的输出字符串为

"a5asda__ampfj__hshahdk6adfls"

有没有什么方法可以做到这一点?


使用MatchEvaluator和自动生成的命名组来识别正确匹配项的替代答案,因此允许在搜索模式和替换模式中使用复杂的正则表达式,同时仅遍历搜索文本一次:https://dev59.com/8cXsa4cB1Zd3GeqPmJSh#74737776 - NineBerry
使用MatchEvaluator和自动生成的命名组来识别正确的匹配项,从而允许在搜索模式和替换模式中使用复杂的正则表达式,同时只遍历搜索文本一次的替代答案:https://stackoverflow.com/a/74737776/101087 - undefined
5个回答

55

给定一个定义了你需要替换的字典:

IDictionary<string, string> map = new Dictionary<string, string>()
{
    {"&","__amp"},
    {"#","__hsh"},
    {"1","5"},
    {"5","6"},
};

你可以同时用它来构建正则表达式,并为每个匹配项形成替换:

var str = "a1asda&fj#ahdk5adfls";
var regex = new Regex(String.Join("|",map.Keys));
var newStr = regex.Replace(str, m => map[m.Value]);
// newStr = a5asda__ampfj__hshahdk6adfls

实时示例: http://rextester.com/rundotnet?code=ADDN57626

这使用了一个Regex.Replace重载,它允许您指定替换的lambda表达式。


评论中指出,如果查找模式中具有正则表达式语法,则不会按预期工作。这可以通过使用Regex.Escape和对上面的代码进行轻微更改来解决:

var str = "a1asda&fj#ahdk5adfls";
var regex = new Regex(String.Join("|",map.Keys.Select(k => Regex.Escape(k))));
var newStr = regex.Replace(str, m => map[m.Value]);
// newStr = a5asda__ampfj__hshahdk6adfls

3
不过,仍然需要转义字符串。如果要替换的标记之一是 "$",该怎么办? - Tim Pietzcker
请查看我的答案,它会进一步扩展以使其更加灵活(这样您就可以使用更多的正则表达式语法)。 - Ray
已经进行了编辑,但正在等待同行审查。将 map.Keys 替换为 map.Keys.Select(s => Regex.Escape(s)),以处理键是正则表达式敏感字符(如 +*)的情况。 - Kache
@Kache - 谢谢,我认为你的编辑很不错,但似乎已被拒绝。我可能会自己进行类似的编辑,但作为脚注而不是对实际代码的更改。 - Jamiec
非常感谢大家提供的解决方案!这个方法真是太棒了。 - Jari Turkia

7

给定一个类似于其他答案中的字典,您可以使用“聚合”将字典中的每个模式映射到替换内容。这将为您提供比其他答案更灵活的选择,因为您可以为每个模式设置不同的正则表达式选项。

例如,以下代码将对希腊文本进行罗马化处理(https://en.wikipedia.org/w/index.php?title=Romanization_of_Greek&section=3#Modern_Greek, 标准/联合国):

var map = new Dictionary<string,string>() {
    {"α[ύυ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "av"}, {"α[ύυ]", "af"}, {"α[ϊΐ]", "aï"}, {"α[ιί]", "ai"}, {"[άα]", "a"},
    {"β", "v"}, {"γ(?=[γξχ])", "n"}, {"γ", "g"}, {"δ", "d"},
    {"ε[υύ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "ev"}, {"ε[υύ]", "ef"}, {"ει", "ei"}, {"[εέ]", "e"}, {"ζ", "z"},
    {"η[υύ](?=[άαβγδέεζήηίΐϊιλμνόορύΰϋυώω])", "iv"}, {"η[υύ]", "if"}, {"[ηήιί]", "i"}, {"[ϊΐ]", "ï"},
    {"θ", "th"}, {"κ", "k"}, {"λ", "l"}, {"\\bμπ|μπ\\b", "b"}, {"μπ", "mb"}, {"μ", "m"}, {"ν", "n"},
    {"ο[ιί]", "oi"}, {"ο[υύ]", "ou"}, {"[οόωώ]", "o"}, {"ξ", "x"}, {"π", "p"}, {"ρ", "r"},
    {"[σς]", "s"}, {"τ", "t"}, {"[υύϋΰ]", "y"}, {"φ", "f"}, {"χ", "ch"}, {"ψ", "ps"}
};

var input = "Ο Καλύμνιος σφουγγαράς ψυθίρισε πως θα βουτήξει χωρίς να διστάζει."; 
map.Aggregate(input, (i, m) => Regex.Replace(i, m.Key, m.Value, RegexOptions.IgnoreCase));

返回(不修改“input”变量):

"o kalymnios sfoungaras psythirise pos tha voutixei choris na distazei."

当然你可以使用类似这样的内容:

foreach (var m in map) input = Regex.Replace(input, m.Key, m.Value, RegexOptions.IgnoreCase);

这会修改"input"变量。

此外,您可以添加以下内容以提高性能:

var remap = new Dictionary<Regex, string>();
foreach (var m in map) remap.Add(new Regex(m.Key, RegexOptions.IgnoreCase | RegexOptions.Compiled), m.Value);

缓存或静态化重映射字典,然后使用:
remap.Aggregate(input, (i, m) => m.Key.Replace(i, m.Value));

非常优雅的最终解决方案。 - FiringSquadWitness

6
如何使用string.Replace()函数?
string foo = "a1asda&fj#ahdk5adfls"; 

string bar = foo.Replace("&","__amp")
                .Replace("#","__hsh")
                .Replace("5", "6")
                .Replace("1", "5");

4
我认为这个比我的更好,但原作者要求正则表达式,现在他有两个问题 :) - Jamiec
5
那只有因为你重新排列了5 -> 6和1 -> 5,实际上如果你把1 -> 5和5 -> 1进行排列,它就行不通了。 - Ray
1
Ray:这是故意的。我编写了解决方案以确保它能够正常工作。你所说的“只有”因为这个才能工作是什么意思?这只是一个简单的字符串替换。有很多种编码方式可以使其“不能工作”。我构建答案是为了让它正常工作。你想表达什么?我是靠运气或者其他什么吗? - p.campbell
不认为这是一个好主意。这样会创建原始字符串的4个副本吗? - Kache
有人应该进行一些性能测试,以查看正则表达式是否真的比四重替换更快...但我猜正则表达式要快得多! - Michiel Cornille
显示剩余4条评论

5

和Jamiec的答案类似,但这样可以使用不完全匹配的正则表达式,例如\.不能与Jamiec的答案一起使用,因为你无法在字典中查找匹配项。

此解决方案依赖于创建组,查找匹配的组,然后查找替换值。它更复杂,但更灵活。

首先将映射创建为KeyValuePairs列表

var map = new List<KeyValuePair<string, string>>();           
map.Add(new KeyValuePair<string, string>("\.", "dot"));

然后,按照以下方式创建你的正则表达式:
string pattern = String.Join("|", map.Select(k => "(" + k.Key + ")"));
var regex = new Regex(pattern, RegexOptions.Compiled);

然后匹配评估器变得更加复杂:
private static string Evaluator(List<KeyValuePair<string, string>> map, Match match)
{            
    for (int i = 0; i < match.Groups.Count; i++)
    {
        var group = match.Groups[i];
        if (group.Success)
        {
            return map[i].Value;
        }
    }

    //shouldn't happen
    throw new ArgumentException("Match found that doesn't have any successful groups");
}

然后像这样调用正则表达式的替换函数:
var newString = regex.Replace(text, m => Evaluator(map, m))

谢谢,但我的输入“Hello [[salutation]] [[firstname]] [[lastname]]”输出为“Hello Dr. Dr. Dr.”。映射如下: map.Add(new KeyValuePair<string, string>(@"[{2}salutation]{2}", "Dr.")); map.Add(new KeyValuePair<string, string>(@"[{2}firstname]{2}", "John")); map.Add(new KeyValuePair<string, string>(@"[{2}lastname]{2}", "Doe")); - Manish

1

我想分享一下对Jamiec和Costas解决方案的经验。

如果你遇到以下问题:

字典中没有给定键“<搜索参数>”。

请记住,在字典键中放置正则表达式模式。

IDictionary<string, string> map = new Dictionary<string, string>()
{
   {"(?<=KeyWord){","("},
   {"}",")"}
};

并且这样使用它

var regex = new Regex(String.Join("|",map.Keys));
var newStr = regex.Replace(str, m => map[m.Value]);

或者这样。
var newStr = Regex.Replace(content, pattern, m => replacementMap[m.Value]);

可能会抛出上述异常,因为模式在替换比较之前被执行,只留下要与字典中的正则表达式键进行比较的匹配项。这样,键和匹配项可能不同,从而引发异常。

'(?<=KeyWord){' != '{'

这是我的解决方案:

我需要将跟在关键字后面的“{”和相应的“}”替换为“(”和“)”,使其更加通俗易懂。

简而言之,将其变为:

@"some random text KeyWord{"Value1", "Value2"} some more 
random text";

转换为这样

@"some random text KeyWord('"Value1", "Value2"') some more 
    random text";

重要内容

IDictionary<string, string> map = new Dictionary<string, string>()
{
    {"{","('"},
    {"}","')"}
};

var content = @"some random text KeyWord{"Value1", "Value2"} some more 
    random text";
var pattern = "((?<=KeyWord){)|((?<=\")})";
var newStr = Regex.Replace(content, pattern, m => map[m.Value]);

希望这堆词对某些人有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接