带有命名子模式的正则表达式没有找到最佳匹配。

4
在正则表达式中使用定义的子模式时,它不会选择最佳匹配,而是停在第一个匹配上。我忘了添加一些标记吗?
正则表达式:(?<minutes>[0-9]|[1-5][0-9]):(?&minutes); 测试字符串:47:24;
表达式无法匹配: pic 1 (47:24;) 但是字符串47:2;被正确匹配: pic 2 (47:2;)
如果我将'or'条件更改为[1-5][0-9]|[0-9],则正则表达式(?<minutes>[1-5][0-9]|[0-9]):(?&minutes);就可以正常工作。是否有其他方法使字符串“47:24;”匹配而无需反转“或”条件?

1
我看到你正在测试PCRE正则表达式。我建议在[1-5]中使用?(?<minutes>[1-5]?[0-9]):(?&minutes);。或者你想保留交替吗? - Wiktor Stribiżew
1
@stribizhev 我会将 [0-5] 扩展到允许以 0 开头的两位数分钟。 - Thomas Ayoub
1
卡西米尔已经正确回答了这个问题。 - Wiktor Stribiżew
2个回答

3
模式从左到右匹配,备选项也从左到右尝试。这是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 是一个单词边界,它将确保您匹配整个数字,而不仅仅是更大数字的几个数字。

@stribizhev 把你的建议发表为答案...不要害羞 ;) - Stephan

2
使用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中会有所不同。

你是说为了让这个正则表达式正常工作,必须首先提供最长模式的分支,然后再提供较小的模式吗? - kos
1
@kos:是的,可以这样理解(针对特定的模式)。 - Casimir et Hippolyte
我知道这个例子似乎没什么用,而且可以用另一种更方便的方式重写,但它是一个使用分钟、小时、星期和月份的大表达式中的一部分。尽管如此,非常感谢你详细的回答。 - kos
1
@kos: 我只是想保护你免受最短模式/有趣的特性/我会到处使用它的疯狂侵袭。 - Casimir et Hippolyte

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