正则表达式中的重叠匹配

49
我似乎找不到这个问题的答案,我想知道是否有一个。以下是一个简化示例:
考虑一个字符串“nnnn”,我想找到所有匹配的“nn”,还包括重叠的匹配项。因此,正则表达式将提供以下3个匹配项:
1. nn nn 2. nnn 3. nn
我意识到这不完全是正则表达式的用途,但手动遍历字符串并进行解析似乎需要大量的代码,考虑到实际上必须使用模式而不是文本字符串进行匹配。

谢谢您添加这个问题。我甚至不确定如何以一种让其他人能够理解的方式陈述这个问题! - Efe Zaladin
3个回答

35

更新2016:

要获取nnnnnnSDJMcHattie评论中提出使用(?=(nn))(见regex101)

(?=(nn))

原始答案(2008年)

可能的解决方案是使用正向后瞻

(?<=n)n

这将给出以下字符串的结尾位置:

  1. nnnn  
  2. nnnn  
  3. nnnn

正如Timothy Khouri所提到的,使用正向预查更加直观(见示例)。

我更喜欢他提出的简单形式 (?=nn)n

(n)(?=(n))

那将引用您想要的字符串的第一个位置,并捕获组(2)中的第二个“n”。这是因为:
- 任何有效的正则表达式都可以在前瞻中使用。 - 如果它包含捕获括号,则会保存反向引用。
因此,无论'n'代表什么(即使它是一个复杂的正则表达式),组(1)和组(2)将捕获它。

2
另外,你也可以使用正向先行断言来完成:(?=nn)n ... 这表示“当两个N在前面时,匹配一个N”。 - Timothy Khouri
不好意思,但我仍然没有看到所请求的三个重叠捕获。您捕获了两个n,但没有三个组。如果我在foo4237bar上匹配(\d\d)(?=(\d\d)),我只得到两个捕获,而不是三个:42和37(在Regex Coach和PCRE Workbench中都是如此)。我可能太蠢了,所以我需要更多的解释。 - PhiLho
请再次阅读答案:(\d)(?=(\d)),而不是(\d\d)(?=(\d\d)):您将有3组捕获组:(4)(2),(2)(3),(3)(7)。 - VonC
2
为什么不直接使用(?=(nn))呢?这样你就可以得到每个匹配的单一捕获组。 - SDJMcHattie
我可能漏掉了什么,但是@SDJMcHattie的评论看起来像是答案:它适用于任意表达式,而不仅仅是一系列“n”。 - Andrew Savinykh
@AndrewSavinykh 同意。我可能错过了这个2016年的评论,对于我2008年的回答。我已经在答案中包含了这个评论。 - VonC

29

使用带有捕获组的前瞻可以实现功能,但代价是使您的正则表达式变得更加缓慢和复杂。另一种解决方案是告诉Regex.Match()方法下一个匹配尝试应从哪里开始。请尝试以下方法:

Regex regexObj = new Regex("nn");
Match matchObj = regexObj.Match(subjectString);
while (matchObj.Success) {
    matchObj = regexObj.Match(subjectString, matchObj.Index + 1); 
}

Regular-Expressions.info的网站管理员... => 必须的 + 1。另外,你当然是对的。 - VonC

2
据我所知,没有一种纯正的正则表达式方法可以一次性完成这个任务(即返回您请求的三个捕获而不需要循环)。
现在,您可以找到一个模式,然后循环搜索起始位置为偏移量(找到的位置+1)。应该将正则表达式与简单代码结合使用。
[编辑]太好了,当我基本上说出了Jan展示的内容时,我被投票否决了...
[编辑2]明确一下:Jan的答案更好。不是更精确,但肯定更详细,值得选择。我只是不明白为什么我的回答会被投票否决,因为我仍然认为它没有错误。不是什么大不了的事,只是有点烦人。

如果你比我快一秒钟回答了,我就会撤回我的相同答案! - Simon Steele
@Timothy:那样不能捕获,而且你仍然需要在结果上循环,所以我不确定有什么优势... - PhiLho
@PhiLho:再次强调,这并不正确:您可以在零宽断言中捕获组,比如正向预查。请参见我的-完成的-答案。 - VonC
@PhiLho:我回复了你的评论。在我看来,你的答案比Jan的不够精确:“模式”可能指的是“n”,而正确的策略是使用“nn”,然后再从偏移量+1开始。你可能一直是这个意思,只是没有解释清楚。 - VonC
@VonC:问题非常明确,模式一直是“nn”,我在那里看不到任何歧义。 - PhiLho

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