DFA与NFA的终极比较

NFA与DFA各有利弊。

DFA与NFA:在预编译阶段(pre-use compile)的区别

在使用正则表达式搜索之前,两种引擎都会编译表达式,得到一套内化形式,适应各自的匹配算法。NFA的编译过程通常要快一些,需要的内存也更少一些。传统型NFA和POSIX NFA之间并没有实质的差别。

DFA与NFA:匹配速度的差别

对于“正常”情况下的简单文本匹配测试,两种引擎的速度差不多。一般来说,DFA的速度与正则表达式无关,而NFA中两者直接相关。

传统的NFA在报告无法匹配以前,必须尝试正则表达式的所有变体。这就是为什么我要用整章(第6章)来论述提高NFA表达式匹配速度的技巧。我们将会看到,有时候一个NFA永远无法结束匹配。传统型NFA如果能找到一个匹配,肯定会停止匹配。

相反,POSIX NFA必须尝试正则表达式的所有变体,确保获得最长的匹配文本,所以如果匹配失败,它所花的时间与传统型NFA一样(有可能很长)。因此,对POSIX NFA来说,表达式的效率问题更为重要。

在某种意义上,我说得绝对了一点,因为优化措施通常能够减少获得匹配结果的时间。我们已经看到,优化引擎不会在字符串开头之外的任何地方尝试带「^」锚点的表达式,我们会在第6章看到更多的优化措施。

DFA不需要做太多的优化,因为它的匹配速度很快,不过最重要的是,DFA在预编译阶段所作的工作提供的优化效果,要好于大多数NFA引擎复杂的优化措施。

现代DFA引擎经常会尝试在匹配需要时再进行预编译,减少所需的时间和内存。因为应用的文本各异,通常情况下大部分的预编译都是白费工夫。因此,如果在匹配过程确实需要的情况下再进行编译,有时候能节省相当的时间和内存(技术术语就是“延迟求值(lazy evaluation)”)。这样,正则表达式、待匹配的文本和匹配速度之间就建立了某种联系。

DFA与NFA:匹配结果的差别

DFA(或者POSIX NFA)返回最左边的最长的匹配文本。传统型NFA可能返回同样的结果,当然也可能是别的文本。针对某一型具体的引擎,同样的正则表达式,同样的文本,总是得到同样的结果,在这个意义上来说,它不是“随机”的,但是其他NFA引擎可能返回不一样的结果。事实上,我见过的所有传统型NFA返回的结果都是一样的,但并没有任何标准来硬性规定。
 
DFA与NFA:能力的差异

NFA引擎能提供一些DFA不支持的功能,例如:
 捕获由括号内的子表达式匹配的文本。相关的功能是反向引用和后匹配信息(after- match information),它们描述匹配的文本中每个括号内的子表达式所匹配文本的位置。
 环视,以及其他复杂的零长度确认(注8)(133)。
 非匹配优先的量词,以及有序的多选结构。DFA很容易就能支持选择最短的匹配文本(尽管因为某些原因,这个选项似乎从未向用户提供过),但是它无法实现我们讨论过的局部的忽略优先性和有序的多选结构。
 占有优先量词(142)和固化分组(139)。

兼具DFA的速度和NFA的功能:正则表达式的终极境界

我已经多次说过,DFA不能支持捕获括号和反向引用。这无疑是对的,但这并不是说,我们不能组合不同的技术,以达到正则表达式的终极境界。180页的补充内容描述了NFA为了追求更强大的功能,如何脱离了纯理论的道路和限制,DFA的情况也是如此。受自身结构的限制,DFA进行这种突破更加困难,但并非不可能。

GNU grep采取了一种简单但有效的策略。它尽可能多地使用DFA,在需要反向引用的时候,才切换到NFA。GNU awk的办法也差不多——在进行“是否匹配”的检查时,它采用GNU grep的DFA引擎,如果需要知道具体的匹配文本的内容,就采用不同的引擎。这里的“不同的引擎”就是NFA,利用自己的gensub函数,GNU awk能够很方便地提供捕获括号。

Tcl的正则引擎由Henry Spencer(你或许记得,这个人在正则表达式的早期发展和流行中扮演了重要的角色)开发,它也是混合型的。Tcl引擎有时候像NFA——它支持环视、捕获括号、反向引用和忽略优先量词。但是,它也确实能提供POSIX的最左最长匹配(177),但没有我们将在第6章看到的NFA的问题。这点确实很棒。

DFA与NFA:实现难度的差异

尽管存在限制,但简单的DFA和NFA引擎都很容易理解和实现。对效率(包括时间和空间效率)和增强性能的追求,令实现越来越复杂。

用代码长度来衡量的话,支持NFA正则表达式的ed Version 7(1979年1月发布)只有不到350行的C代码(所以,整个grep只有区区478行代码)。Henry Spencer1986年免费提供的Version 8正则程序差不多有1 900行C代码,1992年Tom Lord的POSIX NFA package rx(被GNU sed和其他工具采用)长达9 700行。

为了糅合DFA和NFA的优点,GNU egrep Version 2.4.2使用了两个功能完整的引擎(差不多8 900行代码),Tcl的DFA/NFA混合引擎(请看上一页的补充内容)更是长达9 500行。

某些实现很简单,但这并不是说它们支持的功能有限。我曾经想要用Pascal的正则表达式来处理某些文本。从毕业以后我就没用过Pascal了,但是写个简单的NFA引擎并不需要太多工夫。它并不追求花哨,也不追求速度,但是提供了相对全面的功能,非常实用。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值