使用正则表达式(PCRE)匹配a ^ nb ^ nc ^ n(例如"aaabbbccc")

Nik*_*kiC 42 php regex pcre

众所周知,现代正则表达式实现(最值得注意的是PCRE)与常规语法的原始概念几乎没有共同之处.例如,您可以解析无上下文语法的经典示例{a n b n ; n> 0}(例如aaabbb)使用此正则表达式(演示):

~^(a(?1)?b)$~
Run Code Online (Sandbox Code Playgroud)

我的问题是:你能走多远?是否也可以使用PCRE 解析上下文敏感的语法 {a n b n c n ; n> 0}(例如aaabbbccc)?

Nik*_*kiC 33

受到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
Run Code Online (Sandbox Code Playgroud)

亲自尝试:http://codepad.viper-7.com/1erq9v


说明

如果你考虑没有正向前瞻断言((?=...)部分)的正则表达式,你有这个:

~^a+(b(?-1)?c)$~
Run Code Online (Sandbox Code Playgroud)

这只会检查是否存在任意数量的as,后跟相同数量的bs和cs.

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


结论

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

  • +1表示现代正则表达式实现*可以*理解非​​常规,甚至非上下文无关的语法.希望这将使"你不能用正则表达式做X`因为`X`不规则"的无休止的鹦鹉学舌.但那是一厢情愿的想法,对吧? (7认同)
  • 我觉得是这样的.你可以用`c`代替`(?!b)`来简化你的第一个前瞻,因为它已经在一个前瞻组中了. (2认同)
  • 我认为正则表达式是这种情况下工作的错误工具.仅仅因为你*可以*用手术刀切牛排并不意味着你应该*. (2认同)

pax*_*blo 11

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

为了不创造一个难以理解的标点符号的代码,我将冒险投票并回答一个不同但非常相关的问题:应该走多远?

正则表达式解析器在您的工具包中是一个很棒的东西,但它们并不是所有的编程.以可读的方式编写解析器的能力在您的工具包中也是一件非常棒的事情.

应该使用正则表达式直到它们开始使代码难以理解.除此之外,他们的价值充其量是可疑的,最糟糕的是破坏性.对于这种特殊情况,而不是使用像hideous这样的东西:

~^(?=(a(?-1)?b)c)a+(b(?-1)?c)$~x
Run Code Online (Sandbox Code Playgroud)

(向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
Run Code Online (Sandbox Code Playgroud)

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

除非你真的需要这种正则表达式(有时候有很好的理由,比如解释语言中的性能),你应该首先优化可读性.

  • **1.**这些模式不适用于生产代码,它们纯粹是娱乐性的.**2.**你的代码允许其他字符 - "abc!"将返回true.**3.**你不介意顺序 - "caabbc"也会返回true.**4.**像大多数代码一样:代码行数越多,错误越多.**5.**你可以在一次传递中统计字符:`while index <len(str):count [str [index]] ++,index ++`.**6.**您可以测试和[评论](http://stackoverflow.com/q/4034386)模式.看起来不错. (3认同)
  • @paxdiablo 1. 正如科比已经说过的:问题更多的是理论类型,而不是实践类型。我不知道 a^nb^nc^n 有什么实际用途。这只是理论。 (2认同)

Qta*_*tax 10

以下是使用.NET正则表达式的平衡组的替代解决方案:

^(?'a'a)+(?'b-a'b)+(?(a)(?!))(?'c-b'c)+(?(b)(?!))$
Run Code Online (Sandbox Code Playgroud)

不是PCRE,但可能有意义.

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

编辑:添加了组a的缺失平衡检查和在线示例.

  • @NikiC:其实他们很简单.在.NET中,每次使用命名捕获语法`(?'a'...)`时,它实际上是将捕获推送到*堆栈*捕获.然后,语法`(?' - a'...)`从`a`堆栈中弹出一个项目(如果堆栈为空则失败).您还可以在条件正则表达式中使用捕获堆栈,这就是语法`(?(a)...)`.如果堆栈包含项目,则堆栈评估为"true". (2认同)
  • 所以上面的代码做了:将所有'a'捕获推送到堆栈,将所有'b'捕获推送到堆栈(同时为每个'b'弹出'a'),然后声明'a'堆栈为空(`(?!)`是一个无条件的失败),然后用'c'做同样的事情,使用'b'堆栈. (2认同)

zx8*_*x81 5

Qtax技巧

未提及的解决方案:

^(?:a(?=a*(\1?+b)b*(\2?+c)))+\1\2$
Run Code Online (Sandbox Code Playgroud)

在正则表达式演示中查看哪些内容匹配,哪些内容失败。

这使用自引用组(@Qtax 在他的垂直正则表达式上使用的想法)。