受NullUserExceptions答案的启发(他已经删除了它,因为它在一个案例中失败了),我认为我自己找到了一个解决方案:
$regex = '~^
(?=(a(?-1)?b)c)
a+(b(?-1)?c)
$~x';
var_dump(preg_match($regex, 'aabbcc')); // 1
var_dump(preg_match($regex, 'aaabbbccc')); // 1
var_dump(preg_match($regex, 'aaabbbcc')); // 0
var_dump(preg_match($regex, 'aaaccc')); // 0
var_dump(preg_match($regex, 'aabcc')); // 0
var_dump(preg_match($regex, 'abbcc')); // 0
请自行尝试:http://codepad.viper-7.com/1erq9v
如果你考虑不带正向先行断言((?=...)
部分)的正则表达式,你会得到以下内容:
~^a+(b(?-1)?c)$~
这个代码段只是检查了任意数量的a
后面跟着相等数量的b
和c
。
但这并不能满足我们的语法要求,因为a
的数量也必须相同。我们可以通过检查a
的数量是否等于b
的数量来确保这一点。这就是先行断言表达式 (a(?-1)?b)c
的作用:。必须加上c
,以便我们不仅匹配b
的一部分。
我认为这个例子令人印象深刻地表明,现代正则表达式不仅能够解析非正则语法,甚至能够解析非上下文无关的语法。希望这将结束“你不能使用正则表达式做X,因为X不是正则的”这种毫无根据的说法。
c
代替 (?!b)
,因为它已经在前瞻组内了。 - ChriszumaX
任务,因为X
不是正则”的无休止的重复。但这只是美好愿望,不是吗? - NullUserException~^(a(?-1)?b)c+(?<=a(b(?-1)?c))$~x
。不幸的是,在php中这个功能没有实现;它会引发错误:preg_match(): Compilation failed: lookbehind assertion is not fixed length at offset 13
。在python3中,似乎可以使用regex
模块,尽管相对引用显然不受支持,因此必须更改为绝对引用:import regex; regex.search(r"^(a(?1)?b)c+(?<=a(b(?2)?c))$", "aabbcc") is not None
等。 - Don Hatch以下是使用.NET正则表达式和平衡组的另一种解决方案:
^(?'a'a)+(?'b-a'b)+(?(a)(?!))(?'c-b'c)+(?(b)(?!))$
虽然不是PCRE,但可能会有兴趣。
编辑:为a组添加了缺失的平衡检查,并添加了在线示例。
(?'a' ... )
时,它实际上是将捕获推送到捕获堆栈中。然后,语法(?'-a' ... )
从a
堆栈中弹出一个项目(如果堆栈为空,则失败)。您还可以在条件正则表达式中使用捕获堆栈,这就是语法(?(a) ... )
的作用。如果堆栈包含项目,则堆栈计算为true
。 - porges(?!)
是无条件失败),接着使用“b”堆栈重复相同的操作来处理“c”。 - porges(?'-x'...)
不会创建新的/添加到堆栈)。我的正则表达式的较短版本如下:^(a)+(?'b-1'b)+(?(1)(?!))(?'-b'c)+(?(b)(?!))$
。 - Qtax我的问题是:你可以走多远?
为了避免创建一堆标点符号的代码,使其难以阅读,我冒着被踩的风险回答一个不同但非常相关的问题:你应该走多远?
正则表达式解析器是工具箱中必备的妙物,但它们并不是编程的全部。能够以易于阅读的方式编写解析器也是一件很好的事情。
在使用正则表达式时,应该在其开始让你的代码难以理解之前停止。超出这个范围,它们的价值最多是可疑的,在最坏的情况下会对代码造成破坏。对于这种特定情况,与其使用类似下面这样丑陋的东西:
~^(?=(a(?-1)?b)c)a+(b(?-1)?c)$~x
(向NikiC道歉),由于维护这个正则表达式的绝大多数人要么必须完全替换它,要么需要花费相当可观的时间来阅读和理解。因此,你可能想考虑使用非正则表达式的“真正解析器”解决方案(伪代码):
# Match "aa...abb...bcc...c" where:
# - same character count for each letter; and
# - character count is one or more.
def matchABC (string str):
# Init string index and character counts.
index = 0
dim count['a'..'c'] = 0
# Process each character in turn.
for ch in 'a'..'c':
# Count each character in the subsequence.
while index < len(str) and str[index] == ch:
count[ch]++
index++
# Failure conditions.
if index != len(str): return false # did not finish string.
if count['a'] < 1: return false # too few a characters.
if count['a'] != count['b']: return false # inequality a and b count.
if count['a'] != count['c']: return false # inequality a and c count.
# Otherwise, it was okay.
return true
将来维护这段代码会容易得多。我通常建议人们假设后来的那些人(他们需要维护你编写的代码)是精神病患者,他们知道你住在哪里 - 在我的情况下,这可能是对的一半,我不知道你住在哪里 :-)。
除非您真正需要这种类型的正则表达式(有时候确实存在很好的理由,例如在解释性语言中提高性能),否则应优化可读性为首要目标。
abc!
会返回 true。caabbc
也会返回 true。while index < len(str): count[str[index]]++, index++
。for
循环会处理这个问题。实际上,你的第5点是错误的,因为它将允许abcabc通过,这显然是不正确的。 - paxdiablo
(?R)
和~
是正则表达式中的特殊符号,(?R)
表示递归匹配,~
表示取反。preg_match('a*b*c*',...)
存在问题是因为它会匹配到一些不符合预期的字符串,例如ac
或bc
。 - Chriszuma~
的作用。其次,abc*匹配acccccccccc。 - NullUserException@Chriszuma
:~
只是分隔符(你也可以使用/
和许多其他字符)。(?R)
表示递归。它的意思是“再次将整个正则表达式放在这里”。 - NikiCre
模块不支持递归正则表达式,虽然曾经有过这个需求,但是后来被放弃了(http://bugs.python.org/msg83993)。在 Python 中唯一的替代方案是 pyparsing。 - Orbling