使用正则表达式 (PCRE) 匹配 a^n b^n c^n(例如“aaabbbccc”)

Posted

技术标签:

【中文标题】使用正则表达式 (PCRE) 匹配 a^n b^n c^n(例如“aaabbbccc”)【英文标题】:Match a^n b^n c^n (e.g. "aaabbbccc") using regular expressions (PCRE) 【发布时间】:2011-11-18 01:19:55 【问题描述】:

众所周知,现代正则表达式实现(最显着的是 PCRE)与 regular grammars 的原始概念几乎没有共同之处。例如,您可以解析 context-free grammar anbn; 的经典示例。 n>0(例如aaabbb)使用这个正则表达式(demo):

~^(a(?1)?b)$~

我的问题是:你能走多远?是否也可以使用解析context-sensitive grammar anbncn;n>0 (例如aaabbbccc)聚合酶链反应?

【问题讨论】:

(?R)~ 到底是做什么的? preg_match('a*b*c*',...) 有什么问题? 首先,php 正则表达式需要分隔符。这就是~ 的用途。其次,abc* 匹配 acccccccccc @Chriszuma:~ 只是分隔符(您也可以使用/ 和许多其他字符)。 (?R) 表示递归。它的意思是“把整个正则表达式再放在这里”。 a*b*c* 匹配 acccccccccccc 是正确的,但我看不出a+b+c+ 有什么问题。 等等,我现在明白了。棘手的部分是每个组的n 必须相同,对吗?因此,如果有 5 个as,那么bc 中的每个必须正好有 5 个。不,我知道没有递归是不可能的,如果你即使使用递归也能做到,那我当然不知道该怎么做。 【参考方案1】:

Qtax 技巧

未提及的解决方案:

^(?:a(?=a*(\1?+b)b*(\2?+c)))+\1\2$

查看the regex demo 中的匹配项和失败项。

这使用自引用组(@Qtax 在his vertical regex 上使用的想法)。

【讨论】:

@Unihedron 是的,看到了,但他的解决方案在这里使用平衡组(.NET),而这个使用自引用组(Perl,PCRE)。完全不同的野兽——但你当然会从line number question 中知道这一点:) 呵呵,“Qtax 技巧”,谢谢,但我必须感谢 polygenelubricants 的这种技巧。我从他出色的正则表达式问题和答案中了解到这一点,例如 How can we match a^n b^n with Java regex?【参考方案2】:

我的问题是:你能走多远?

为了不创建难以阅读的标点符号的代码,我将冒着被否决的风险并回答一个不同但非常相关的问题:应该 你去吗?

正则表达式解析器是您的工具包中的一个出色功能,但它们并不是编程的全部和结束。以可读的方式编写解析器的能力也是在你的工具包中的一个很棒的东西。

应该一直使用正则表达式,直到它们开始使您的代码难以理解为止。除此之外,它们的价值充其量是可疑的,最坏的情况是破坏性的。对于这种特定情况,而不是使用像可怕的东西:

~^(?=(a(?-1)?b)c)a+(b(?-1)?c)$~x

(向 NikiC 道歉),绝大多数试图维护它的人要么必须完全替换它,要么花费 大量 时间阅读和理解,您可能需要考虑类似于非 RE 的“正确解析器”解决方案(伪代码):

# 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

这将在未来更容易维护。我总是喜欢向人们建议,他们应该假设那些追随他们的人(他们必须维护他们编写的代码)是知道你住在哪里的精神病患者 - 在我的情况下,这可能是对的,我不知道你住在哪里:-)

除非您真正需要这种正则表达式(有时有充分的理由,例如解释语言的性能),否则您应该优化可读性首先。

【讨论】:

1. 这些模式并非用于生产代码,它们纯粹是为了娱乐。 2. 您的代码允许使用其他字符 - abc! 将返回 true。 3. 您不介意顺序 - caabbc 也会返回 true。 4. 像大多数代码一样:代码行越多,错误就越多。 5. 您可以一次计算字符数:while index &lt; len(str): count[str[index]]++, index++6. 您可以测试和comment 模式。看起来很棒。 @Kobi,您需要重新检查我的代码和问题中的规范。它不允许更多字符超过结尾,这是由第一次失败检查处理的。它不允许乱序的 abc 序列——for 循环负责处理。实际上,您的第 5 点是不正确的,因为它将允许 abcabc 通过,这显然是不正确的。 我还应该提到,代码行数和错误之间的相关性并不总是如此——它通常在代码复杂性和错误之间。有时复杂性和代码行数是相关的,但情况并非总是如此。举例来说,上面的代码尽管行数并不复杂。 @Kobi,没有问题,我认为这可能是对'a'..'c' 位的简单误解,您认为这可能意味着这些字符按任何顺序排列,而不是我想要的顺序。这就是使用童话般的伪代码而不是特定语言所固有的危险:-) @paxdiablo 1. 正如 Kobi 已经说过的:问题更多的是理论类型,而不是实际问题。我不知道 a^n b^n c^n 有什么实际用途的地方。这只是理论。【参考方案3】:

受 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)$~

这只是检查是否存在任意数量的as,然后是相等数量的bs 和cs。

这还不满足我们的语法,因为as 的数量也必须相同。我们可以通过检查as 的数量等于bs 的数量来确保这一点。这就是前瞻断言中的表达式所做的:(a(?-1)?b)cc 是必需的,所以我们不仅仅匹配bs 的一部分。


结论

我认为这令人印象深刻地表明,现代正则表达式不仅能够解析非常规语法,甚至可以解析非上下文无关语法。希望这将平息“你不能用正则表达式做 X,因为 X 不规则”的无休止的鹦鹉学舌

【讨论】:

关闭。在 abbcc 上失败。您已经检查了b 以匹配每个a,并检查c 以匹配每个b,但如果您缺少a,没人会知道。我敢打赌,你可以让它发挥作用。 我认为是。不过,您可以使用 c 而不是 (?!b) 来简化您的第一次前瞻,因为它已经在前瞻组中。 +1 表明现代正则表达式实现可以理解非常规,甚至非上下文无关的语法。希望这将平息“你不能用正则表达式做X,因为X 不正常”的无休止的鹦鹉学舌。但那是一厢情愿,对吧? 我会断言,在这种情况下,正则表达式是错误的工作工具。仅仅因为您可以使用手术刀切牛排并不意味着您应该 @zzzzBov 这个问题(和答案)纯属理论性质;)我断言我不会在生产中使用该代码,如果它让你开心:)【参考方案4】:

这是使用带有 .NET 正则表达式的 balancing groups 的替代解决方案:

^(?'a'a)+(?'b-a'b)+(?(a)(?!))(?'c-b'c)+(?(b)(?!))$

不是 PCRE,但可能会感兴趣。

例如:http://ideone.com/szhuE

编辑:添加了 a 组缺少的平衡检查,以及一个在线示例。

【讨论】:

@NikiC:其实它们很简单。在 .NET 中,每次使用命名捕获语法 (?'a' ... ) 时,它实际上是将捕获推送到捕获的 stack 上。然后,语法(?'-a' ... )a 堆栈中弹出一个项目(如果堆栈为空,则失败)。您还可以在条件正则表达式中使用捕获堆栈,这就是 (?(a) ... ) 的语法。如果堆栈包含项目,则堆栈评估为 true 所以上面的代码是这样的:将所有“a”捕获压入堆栈,将所有“b”捕获压入堆栈(同时为每个“b”弹出一个“a”),然后断言'a' 堆栈为空((?!) 是无条件失败),然后使用'b' 堆栈对'c' 执行相同的操作。 @Porges,请注意,它不必是命名的捕获组。未命名的工作也一样(除了(?'-x'...) 不会创建新的/添加到堆栈)。我的正则表达式的较短版本是:^(a)+(?'b-1'b)+(?(1)(?!))(?'-b'c)+(?(b)(?!))$

以上是关于使用正则表达式 (PCRE) 匹配 a^n b^n c^n(例如“aaabbbccc”)的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中使用正则表达式匹配 a^n b^n c^n(例如“aaabbbccc”)

正则表达式

Python正则表达式详解

正则表达式字符匹配

python 正则表达式

正则表达式