为啥回顾中的有限重复在某些情况下不起作用?

Posted

技术标签:

【中文标题】为啥回顾中的有限重复在某些情况下不起作用?【英文标题】:Why doesn't finite repetition in lookbehind work in some flavors?为什么回顾中的有限重复在某些情况下不起作用? 【发布时间】:2011-03-10 17:26:20 【问题描述】:

我想从 dd/mm/yy 格式的日期解析中间的 2 位数字,但也允许单个数字表示日期和月份。

这是我想出的:

(?<=^[\d]1,2\/)[\d]1,2

我想要一个 1 位或 2 位数字 [\d]1,2 和一个 1 位或 2 位数字并在其前面加上斜线 ^[\d]1,2\/

这对很多组合都不起作用,我测试过10/10/1011/12/13 等...

但令我惊讶的是,(?&lt;=^\d\d\/)[\d]1,2 奏效了。

但是如果\d\d 匹配,[\d]1,2 也应该匹配,还是我错了?

【问题讨论】:

我使用 C#,但在 Web 正则表达式测试器上测试了上述正则表达式。我不知道不同语言之间存在如此显着的差异。 【参考方案1】:

关于后视支持

主要的正则表达式风格有不同的lookbehind支持;有些施加了某些限制,有些甚至根本不支持。

javascript:不支持 Python:仅固定长度 Java:仅限有限长度 .NET:没有限制

参考文献

regular-expressions.info/Flavor comparison

Python

在仅支持固定长度后视的 Python 中,您的原始模式会引发错误,因为 \d1,2 显然没有固定长度。您可以通过交替使用两个不同的固定长度后视来“解决”这个问题,例如像这样:

(?<=^\d\/)\d1,2|(?<=^\d\d\/)\d1,2

或者也许您可以将两个后视作为非捕获组的替代:

(?:(?<=^\d\/)|(?<=^\d\d\/))\d1,2

(请注意,您可以只使用不带括号的\d)。

也就是说,使用捕获组可能更简单:

^\d1,2\/(\d1,2)

请注意,如果您只有一个组,findall 返回组 1 捕获的内容。捕获组比lookbehind 得到更广泛的支持,并且通常会导致更易读的模式(例如在这种情况下)。

这个 sn-p 说明了上述所有要点:

p = re.compile(r'(?:(?<=^\d\/)|(?<=^\d\d\/))\d1,2')

print(p.findall("12/34/56"))   # "[34]"
print(p.findall("1/23/45"))    # "[23]"

p = re.compile(r'^\d1,2\/(\d1,2)')

print(p.findall("12/34/56"))   # "[34]"
print(p.findall("1/23/45"))    # "[23]"

p = re.compile(r'(?<=^\d1,2\/)\d1,2')
# raise error("look-behind requires fixed-width pattern")

参考文献

regular-expressions.info/Lookarounds, Character classes, Alternation, Capturing groups

Java

Java 仅支持有限长度的lookbehind,因此您可以像在原始模式中一样使用\d1,2。下面的 sn-p 证明了这一点:

    String text =
        "12/34/56 date\n" +
        "1/23/45 another date\n";

    Pattern p = Pattern.compile("(?m)(?<=^\\d1,2/)\\d1,2");
    Matcher m = p.matcher(text);
    while (m.find()) 
        System.out.println(m.group());
     // "34", "23"

注意(?m) 是嵌入的Pattern.MULTILINE,因此^ 匹配每一行的开头。另请注意,由于 \ 是字符串文字的转义字符,因此您必须编写 "\\" 才能在 Java 中获得一个反斜杠。


C-夏普

C# 在lookbehind 上支持完整的正则表达式。以下 sn-p 显示了如何在后视中使用 + 重复:

var text = @"
1/23/45
12/34/56
123/45/67
1234/56/78
";

Regex r = new Regex(@"(?m)(?<=^\d+/)\d1,2");
foreach (Match m in r.Matches(text)) 
  Console.WriteLine(m);
 // "23", "34", "45", "56"

请注意,与 Java 不同的是,在 C# 中,您可以使用 @-quoted string,这样您就不必转义 \

为了完整起见,以下是在 C# 中使用捕获组选项的方法:

Regex r = new Regex(@"(?m)^\d+/(\d1,2)");
foreach (Match m in r.Matches(text)) 
  Console.WriteLine("Matched [" + m + "]; month = " + m.Groups[1]);

鉴于之前的text,打印如下:

Matched [1/23]; month = 23
Matched [12/34]; month = 34
Matched [123/45]; month = 45
Matched [1234/56]; month = 56

相关问题

How can I match on, but exclude a regex pattern?

【讨论】:

【参考方案2】:

除非有特定原因使用问题中未提及的后视,否则如何简单地匹配整个事物并仅捕获您感兴趣的位?

JavaScript 示例:

>>> /^\d1,2\/(\d1,2)\/\d1,2$/.exec("12/12/12")[1]
"12"

【讨论】:

【参考方案3】:

引用regular-expressions.info:

坏消息是大多数正则表达式 口味不允许你只使用 向后看中的任何正则表达式,因为 他们不能应用正则表达式 向后。因此,常规 表达式引擎需要能够 计算退后几步 在检查后视之前。

因此,许多正则表达式风格, 包括 Perl 和 Python,只允许定长 字符串。您可以使用任何正则表达式 匹配的长度可以是 预定的。这意味着您可以使用 文字文本和字符类。 您不能使用重复或可选 项目。您可以使用交替,但 仅当交替中的所有选项 长度相同。

换句话说,您的正则表达式不起作用,因为您在后视中使用了可变宽度表达式,而您的正则表达式引擎不支持。

【讨论】:

【参考方案4】:

除了@polygenelubricants 列出的那些之外,“仅固定长度”规则还有两个例外。在 PCRE(php、Apache、et al 的正则表达式引擎)和 Oniguruma(Ruby 1.9、Textmate)中,后视可能由交替组成,其中每个备选可能匹配不同数量的字符,如只要每个备选方案的长度是固定的。例如:

(?<=\b\d\d/|\b\d/)\d1,2(?=/\d2\b)

请注意,交替必须位于lookbehind 子表达式的顶层。你可能像我一样,想把共同的元素分解出来,像这样:

(?<=\b(?:\d\d/|\d)/)\d1,2(?=/\d2\b)

...但它不起作用;在顶层,子表达式现在由一个具有非固定长度的备选方案组成。

第二个例外更有用:\K,由 Perl 和 PCRE 支持。它实际上意味着“假装比赛真的从这里开始”。正则表达式中出现在它之前的任何内容都被视为积极的后视。与 .NET 后视一样,没有任何限制;在\K 之前可以使用普通正则表达式中出现的任何内容。

\b\d1,2/\K\d1,2(?=/\d2\b)

但大多数时候,当有人遇到后视问题时,事实证明他们甚至不应该使用它们。正如@insin 所指出的,使用捕获组可以更轻松地解决这个问题。

编辑:差点忘了 JGSoft,EditPad Pro 和 PowerGrep 使用的正则表达式风格;与 .NET 一样,它具有完全不受限制的后视,正面和负面。

【讨论】:

以上是关于为啥回顾中的有限重复在某些情况下不起作用?的主要内容,如果未能解决你的问题,请参考以下文章

为啥转换在我的情况下不起作用

多个 OpenGL 纹理在某些情况下不起作用?

为啥这个 if 语句在这种情况下不起作用? [关闭]

为啥 selectAnnotation 在这种情况下不起作用?

为啥 DISTINCT 在这种情况下不起作用? (SQL)

为啥我的 JRadioButton 在这种情况下不起作用?