【问题标题】:Replace Nth regex match occurrence in string替换字符串中的第 N 个正则表达式匹配项
【发布时间】:2017-11-09 11:35:58
【问题描述】:

我知道关于 SO 有很多这样的问题,但我找不到一个解释他们如何实现模式以返回第 N 个匹配项的问题,这是被分解的。我查看的所有答案都只是将代码提供给 OP,并进行了最少的解释。

我所知道的是,您需要在 X 是您要返回的数字出现的模式中实现此 {X}

所以我试图在两个chars 之间匹配一个string,我似乎已经能够让它工作了。

要测试的字符串是这样的,

"=StringOne&=StringTwo&=StringThree&=StringFour&"

"[^/=]+(?=&)"

再一次,在尽可能多地阅读之后,这个模式也将返回所有匹配项,

[^/=]+(?=&){1}

由于{1} 是默认值,因此在上述模式中是多余的。 但我不能这样做,

[^/=]+(?=&){2}

因为它不会像我期望的那样返回第三场比赛。

那么有人可以将我推向正确的方向并解释如何获得所需的模式来找到所需的匹配项吗?

【问题讨论】:

  • 如何创建一个依次匹配每个组的正则表达式,使用接收委托的替换重载并只使用计数器?
  • 对url参数使用解析器怎么样?
  • @LasseVågsætherKarlsen,我正在研究如何按您所说的依次匹配每个组。可能要花点时间看看我是否可以纯粹通过 RegEx 来解决它,因为 Witor 实际上已经使用 C# 和 RegEx 的组合解决了这个问题,正如您可能看到的那样。
  • 为什么不直接使用 uribuilder 的内置查询字符串解析器?

标签: c# regex


【解决方案1】:

纯正则表达式方法是可能的,但如果您的模式很复杂,则效率不是很高。

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx = 2;     // Replace this occurrence
var result = Regex.Replace(s, $@"^(=(?:[^=&]+&=){{{idx-1}}})[^=&]+", "${1}REPLACED");
Console.WriteLine(result); // => =StringOne&=REPLACED&=StringThree&=StringFour&

参见this C# demoregex demo

正则表达式详细信息

  • ^ - 字符串开头
  • (=(?:[^=&]+&=){1}) - 第 1 组捕获:
    • = - = 符号
    • (?:[^=&]+&=){1} - 出现 1 次(此数字是动态生成的)
    • [^=&]+ - 除=& 之外的1 个或多个字符(注意 如果字符串可能包含=&,则将其替换为@987654338 会更安全@ 并将RegexOptions.Singleline 选项传递给正则表达式编译器)
    • &= - &= 子字符串。
  • [^=&]+ - 除了 =& 之外的 1 个或多个字符

替换模式中的${1} 将第 1 组的内容插入回结果字符串中。

作为替代方案,我可以建议引入一个计数器并在每次匹配时递增,并且仅在计数器等于您指定的匹配出现时替换一个。

使用

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx_to_replace = 2; // Replace this occurrence
var cnt = 0;            // Counter
var result = Regex.Replace(s, "[^=]+(?=&)", m => {  // Match evaluator
        cnt++; return cnt == idx_to_replace ? "REPLACED" : m.Value; });
Console.WriteLine(result); 
// => =StringOne&=REPLACED&=StringThree&=StringFour&

请参阅C# demo

cntRegex.Replace 内的匹配评估器内递增,m 被分配当前的 Match 对象。当cnt 等于idx_to_replace 时,替换发生,否则,整个匹配被粘贴回来(m.Value)。

另一种方法是遍历匹配项,一旦找到第 N 个匹配项,通过将字符串拆分为匹配前和匹配后的部分来替换它,一旦替换完成,则退出循环:

var s = "=StringOne&=StringTwo&=StringThree&=StringFour&";
var idx_to_replace = 2;     // Replace this occurrence
var cnt = 0;                // Counter
var result = string.Empty;  // Final result variable
var rx = "[^=]+(?=&)";      // Pattern
for (var m=Regex.Match(s, rx); m.Success; m = m.NextMatch())
{
    cnt++;
    if (cnt == idx_to_replace) {
        result = $"{s.Substring(0, m.Index)}REPLACED{s.Substring(m.Index+m.Length)}";
        break;
    }
}
Console.WriteLine(result); // => =StringOne&=REPLACED&=StringThree&=StringFour&

another C# demo

这可能会更快,因为引擎不必找到所有个匹配项。

【讨论】:

  • 非常有趣的方法,我不知道你可以做这样的事情。 +1。在我在这里找到的任何搜索中,我都没有看到有人这样做。
  • 非常感谢您的精彩解释,这正是我所希望的。这个答案肯定会在未来帮助我以外的人。
猜你喜欢
  • 2012-09-17
  • 2023-03-25
  • 2019-04-14
  • 2015-11-30
  • 2010-09-07
  • 1970-01-01
  • 2012-01-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多