具有命名子模式的正则表达式看不到最佳匹配

Regular expression with named subpattern doesn't see the best match

在 reg exp 中使用定义的子模式时,它不会选择最佳匹配,而是在第一个匹配处停止。我忘记了一些标志吗?

正则表达式:(?<minutes>[0-9]|[1-5][0-9]):(?&minutes); 测试字符串:47:24;.

表达式不匹配:

但是字符串 47:2; 匹配正确:

.

如果我将 'or' 条件更改为 [1-5][0-9]|[0-9],则 reg exp (?<minutes>[1-5][0-9]|[0-9]):(?&minutes); 工作得很好。有没有其他方法可以使字符串 '47:24;'匹配而不反转 'or' 条件?

模式从左到右匹配,也从左到右尝试替代方案。这就是 NFA 正则表达式引擎的工作方式。 PCRE 也有一个 DFA 引擎,它会尝试找到最长的匹配,但它没有暴露给 PHP。

因此,如果您有像 a|b 这样的模式并且 ba 的子集,引擎将首先尝试 a 并成功。 b 部分将 永远不会 匹配。

你可以写 \b(?:[1-5][0-9]|[0-9])\b 但它似乎多余。

只需使用 \b[1-5]?[0-9]\b(如 stribizhev 所建议的那样)即可始终保持正确。 \b 是一个单词边界,它将确保您匹配一个整数,而不是一个较大数字的几位数字。

对于 PCRE,递归组是原子的(参见 article)。这就是正则表达式引擎无法在 (?&minutes).

中回溯的原因

42:24;中,242匹配到第一个分支[0-9](自从第一次获胜),但是当模式失败时,因为有字符串中的 4 而不是 ;,正则表达式引擎无法在 (?&minutes) 子模式内回溯以测试第二个分支 [1-5][0-9](你可以看看debugger)

解决方案:不要对这么小的子模式使用递归,它没有用且没有意义(特别是如果您使用捕获组的名称)。写这样的东西:

(?<minutes>[1-5]?[0-9]):(?<seconds>[1-5]?[0-9]);

或者为什么不:

(?(DEFINE)(?<sex>[1-5]?[0-9]) for "sexagesimal", not for what you think)
(?<minutes>(?&sex)):(?<seconds>(?&sex));

似乎是多余的,但如果你想提取分钟和秒(否则,根本不要使用组)是有道理的并且很有用。毕竟,如果你使用命名捕获,你的目标不是写出世界最短的模式。

如果你无法避免交替:

  • 您可以将最长的分支放在第一位:[1-5][0-9]|[0-9] 按照 Lucas 的建议。
  • 你也可以使用互斥分支:[1-5][0-9]?|[06-9], [06-9]|[1-5][0-9]? (在这种情况下顺序无关紧要)

请注意,递归组的这种行为是 PCRE 特有的,与 Perl 或 Ruby 不同。