正则表达式的真正威力(3)

不受限文法

乔姆斯基谱系中的再上一层是不受限文法。这个语言集合可以组成所有的递归可枚举语言集合。

这个文法是不受限的,所以没啥可说的。不受限文法的产生式规则有这样的形式α -> β,这里面αβ是任意的符号串。

基本上不受限文法就是去掉了非收缩文法中的限制。所以A B C -> H Q是合法的,即便在非收缩文法看来不合法。

那么不受限文法到底有多强大呢?他们是图灵完备的。甚至有一个基于不受限文法的“编程语言”:Thue。因为它是图灵完备的,所以可以做其他语言能做的任何事。

图灵完备同时也意味着不能判定一个特定的字符串是否符合某种文法。

很遗憾的是我不清楚正则表达式和不受限文法的关联。我甚至也找不到关于有意义的不受限文法的示例(不是非收缩的)。

但是既然谈到图灵完备性我们也谈谈另外一点:

带反向引用的正则表达式是NP完备的

前面没有提到正则表达式具有的一个很强大的功能:反向引用。

例如下面的例子:

/^(.+)\1$/

(.+)匹配任意一些文本并且\1匹配相同的文本。一般\n表示“第n个子模式匹配的内容”。如果(.+)匹配foo,那么\1也同样匹配foo。所以表达式(.+)\1意味着“一些文本后面接着同样的文本”。

这个简单的正则匹配的就是所谓的“复制语言”并且也是另一个上下文相关语言的示例。

类似的我们可以使用反向引用匹配其他几个示例:

# {a^n b^n, n>0} (context-free)
/^ (?: a (?= a* (\1?+ b) ) )+ \1 $/x
# {a^n b^n c^n, n>0} (context-sensitive)
/^ (?: a (?= a* (\1?+ b) b* (\2?+ c) ) )+ \1 \2 $/x

解释这些是如何工作的不在讨论范围,不过StackOverflow上有个很好的解释

如上所见,反向引用(没有子模式递归支持)使得正则表达式能力大大增强。事实上有了这项增强,正则的匹配成为了一个NP完备问题。

NP完备是什么意思呢?NP完备是一种针对决策问题的计算复杂度类别,很多难题属于此类。例如旅行商问题(TSP),布尔可满足性问题(SAT)和背包问题(BKP)。

成为NP完备问题的一个主要条件是每个NP问题可以化简成NP完备的。因此所有的NP完备问题基本上都是不可再变换的。如果其中一个找到了答案,其他的也就迎刃而解了。

因此只要某人找到了NP完备问题的快速解决办法,那么人类的几乎所有计算性难题都能得到解决。这也意味着文明的终结。

为了证明带反向引用的正则表达式确实是NP完备的,我们拿出一个NP完备问题,然后证明可以使用正则表达式解决。我选择的是3-CNF SAT问题。

3-CNF SAT代表“3连一般形式的布尔可满足性问题”,是很容易理解的。假如有如下形式的一个布尔公式:

   (!$a ||  $b ||  $d)
&& ( $a || !$c ||  $d)
&& ( $a || !$b || !$d)
&& ( $b || !$c || !$d)
&& (!$a ||  $c || !$d)
&& ( $a ||  $b ||  $c)
&& (!$a || !$b || !$c)

这个布尔公式由AND分割的几个子式组成。每个子式由OR分割的三个变量(或者取反)组成。问题就是这样布尔公式是否存在解(其值为真)。

上面的式子可以转化为下面的正则表达式:

$regex = '/^
    (x?)(x?)(x?)(x?) .* ;
    (?: x\1 | \2  | \4  ),
    (?: \1  | x\3 | \4  ),
    (?: \1  | x\2 | x\4 ),
    (?: \2  | x\3 | x\4 ),
    (?: x\1 | \3  | x\4 ),
    (?: \1  | \2  | \3  ),
    (?: x\1 | x\2 | x\3 ),
$/x';
$string = 'xxxx;x,x,x,x,x,x,x,';
var_dump(preg_match($regex, $string, $matches));
var_dump($matches);

如果执行代码会下面的结果:

array(5) {
  [0]=> string(19) "xxxx;x,x,x,x,x,x,x,"
  [1]=> string(1) "x"
  [2]=> string(1) "x"
  [3]=> string(0) ""
  [4]=> string(0) ""
}

这意味着上面的式子成立的条件是$a = true, $b = true, $c = false and $d = false

上面的正则表达式工作原理很简单:对于每个子式都包含有一个x需要被匹配。因此对于(?: \1 | x\3 | \4 ),要想成功匹配x,要么\1x(真),\3是空字符串(假)或者\4x(真)。

剩下的工作交给引擎即可。它会尝试不同的方式来匹配字符串只要找到答案或者失败。

总结

本文很长,下面总结一下主要的结论。

  • 程序员使用的正则表达式和形式语言理论中的正则式基本没啥关联。
  • 正则表达式(至少PCRE)可以匹配所有的上下文无关语言。因此他们可以匹配结构良好的HTML和几乎所有的编程语言。
  • 正则表达式至少可以匹配一些上下文相关语言。
  • 正则表达式的匹配是NP完备的。因此你可以用它解决任意的NP问题。

但是别忘了,你能做并不代表不应该这样做。在很多场合下用正则表达式匹配HTML是错误的做法。不排除有做得对的时候。

应该总是寻找特定问题的更简单的解决方法。如果要使用正则解决问题时,不要忘记x描述符,可以允许你更好的格式化正则。对于负责的正则表达式不要忘记使用DEFINE断言并且给子模式命名来使得代码更清晰易读。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值