具有命名子模式的正则表达式看不到最佳匹配
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
这样的模式并且 b
是 a
的子集,引擎将首先尝试 a
并成功。 b
部分将 永远不会 匹配。
你可以写 \b(?:[1-5][0-9]|[0-9])\b
但它似乎多余。
只需使用 \b[1-5]?[0-9]\b
(如 stribizhev 所建议的那样)即可始终保持正确。 \b
是一个单词边界,它将确保您匹配一个整数,而不是一个较大数字的几位数字。
对于 PCRE,递归组是原子的(参见 article)。这就是正则表达式引擎无法在 (?&minutes)
.
中回溯的原因
在42:24;
中,24
的2
匹配到第一个分支[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 不同。
在 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
这样的模式并且 b
是 a
的子集,引擎将首先尝试 a
并成功。 b
部分将 永远不会 匹配。
你可以写 \b(?:[1-5][0-9]|[0-9])\b
但它似乎多余。
只需使用 \b[1-5]?[0-9]\b
(如 stribizhev 所建议的那样)即可始终保持正确。 \b
是一个单词边界,它将确保您匹配一个整数,而不是一个较大数字的几位数字。
对于 PCRE,递归组是原子的(参见 article)。这就是正则表达式引擎无法在 (?&minutes)
.
在42:24;
中,24
的2
匹配到第一个分支[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 不同。