为什么正则表达式具有指数运行时间?

kir*_*uku 27 regex

可以编写一个在某些情况下需要指数运行时间的正则表达式.这样的例子是(aa|aa)*.如果输入奇数个as,则需要指数运行时间.

这很容易测试.如果输入仅包含as并且长度为51,则正则表达式需要几秒钟才能计算(在我的机器上).相反,如果输入长度为52,则其计算时间不明显(我使用JavaRE的内置Regex-parser对其进行了测试).

我写了一个正则表达式解析器来找到这种行为的原因,但我没有找到它.我的解析器可以基于正则表达式构建ASTNFA.之后,它可以将NFA翻译为DFA.为此,它使用了powerset构造算法.

当我解析上面提到的Rgex时,解析器会创建一个具有7种状态的NFA - 转换后,DFA中只剩下3个状态.DFA代表更明智的正则表达式(aa)*,可以非常快速地解析.

因此,我不明白为什么有解析器可以这么慢.这是什么原因?他们不会将NFA翻译成DFA吗?如果是的话,为什么不呢?他们计算得如此之慢的技术原因是什么?

小智 19

Russ Cox有一篇非常详细的文章,说明为什么会这样,以及正则表达式的历史(第2 部分,第3部分).

正则表达式匹配可以简单快速,使用已知数十年的基于有限自动机的技术.相比之下,Perl,PCRE,Python,Ruby,Java和许多其他语言都有基于递归回溯的正则表达式实现,这些实现简单但速度极慢.除了反向引用之外,慢速回溯实现提供的功能可以通过基于自动机的实现以更快,更一致的速度提供.

很大程度上,它归结为"常规"表达式中的非常规功能的扩散,例如反向引用,以及大多数程序员的(持续)无知,对于不包含此类功能的正则表达式(其中许多功能)有更好的替代方案.

在20世纪80年代早期编写文本编辑器时,Rob Pike编写了一个新的正则表达式实现,Dave Presotto将其提取到第八版中出现的库中.Pike的实现将子匹配跟踪整合到一个有效的NFA模拟中,但是,与第八版源的其余部分一样,并没有广泛分布.派克自己没有意识到他的技术有什么新意.Henry Spencer从零开始重新实现了第八版库接口,但使用了回溯,并将其实现发布到了公共领域.它被广泛使用,最终成为前面提到的慢速正则表达式实现的基础:Perl,PCRE,Python等.(在他的辩护中,Spencer知道例程可能很慢,他不知道存在更有效的算法.他甚至在文档中警告说,"许多用户发现速度完全足够,尽管用egrep取代了内部这段代码将是一个错误.")Pike的正则表达式实现,扩展为支持Unicode,在1992年末与sam一起免费提供,但特别有效的正则表达式搜索算法没有被注意到.

  • 这个答案是否表明大多数正则表达式引擎都不是基于每个计算理论课程中教授的快速简单的有限状态自动机?那是因为大多数人在80年代/ 90年代都不知道他们?我发现*非常难以置信 - 自从时间开始以来,计算理论是不是每个计算机科学课程的一部分? (7认同)
  • @BlueRaja:回溯更容易实现.它还承认非常规功能(但正如我在下面所说,这是一个警察因为检查非常规功能可以定期完成).真的,这就是它.你大大高估了人们从学校里记得多少,他们多么聪明,即使他们记得FSA认可常规语言,有多少人会被要求实际选择一个正则表达式库,而不是使用已经给出的那个他们用语言.(或者说,请仔细阅读文章.为什么没有人再阅读链接?) (3认同)
  • @BlueRaja:您是否否认大多数(按市场份额)正则表达式实现使用回溯实现?如果你不相信我的摘录,你可以阅读这些文章。或者大部分项目的源代码。或者像 Antoras 那样自己做一些基准测试。 (2认同)