【问题标题】:Which regular expression performs better?哪个正则表达式表现更好?
【发布时间】:2014-02-06 07:20:51
【问题描述】:

我想匹配具有公共前缀 (/files) 但只有两个特定目录 - 图像和视频 - 在其下的 URL。我可以为此想到两个正则表达式:

/files/(images/.*|videos/.*)

/files/(image|video)s/.*

我有两个问题:

  1. 从性能角度来看,哪个更好?我的猜测是第二个,因为它的 DFA 的状态数会更少。
  2. 是否有一种通用编程语言,其内置的正则表达式编译器会将给定的正则表达式简化为最小 DFA?

性能对我很重要,因为我将使用它来匹配数十亿个字符串。所以,任何一点点的改进对我来说也很重要。

【问题讨论】:

  • @nhahtdh - 感谢您提及这一点。我知道差异和事实。我只提到了 DFA,因为 NFA 至少在理论上等同于某些 DFA。

标签: regex


【解决方案1】:

从性能角度来看,哪个更好?我的猜测是第二个,因为它的 DFA 的状态数会更少。

两个表达式在最小 DFA 中具有相同数量的状态,并且它们的 DFA 匹配相同的“语言”(理论上)。

无论 DFA 中有多少状态,理论上性能是相同的,因为您将在将输入提供给自动机时确定性地遍历这些状态。

在实践中,由于缓存未命中可能会存在差异,当状态更多时,这种情况可能会更频繁地发生。但是,除非您正在使用正则表达式引擎,否则我想不出任何充分的理由花时间进行黑盒优化。

是否有一种通用编程语言,其内置的正则表达式编译器会将给定的正则表达式简化为最小 DFA?

Go (re2) 和 Haskell 具有将正则表达式转换为 DFA 的引擎。不过,我不知道 DFA 是否最小。

由于 POSIX ERE 不支持反向引用(反向引用不同于对替换捕获组的引用),因此可以为 POSIX ERE 编写一个在 DFA 上运行的引擎或高效的 NFA 模拟。然而,由于标准不强制要求这样的实现,只要结果正确(匹配最左边最长的字符串),实现可以穷举搜索与 NFA 回溯引擎上的正则表达式匹配的所有字符串。

但是,至少 GNU egrep seems to implements POSIX ERE with DFA(基于文件名 dfa.c)。


供您参考,有3 approaches to implement a regular expression matching

  • DFA
  • NFA 模拟算法
  • NFA 回溯

更多详情,article(引用于this question)解释:

  • 对于(理论上的)正则表达式,存在具有子匹配跟踪(捕获组)的高效 NFA 模拟算法。
  • 为什么回溯引擎如此突出(例如 Java、Python、Perl2、...)
    2:Perl 实现了一个带记忆的回溯引擎。
  • 回溯引擎在输入长度上可能需要指数级时间,而 Thompson 的 NFA 模拟算法需要 O(mn) 时间,其中 m 是正则表达式的长度,n 是输入的长度。
  • 目前已知的将(ir)正则表达式与反向引用匹配的最有效算法是回溯方法。因此,一些引擎决定不支持反向引用以提高匹配效率。
  • 回溯引擎(即使有记忆)比 Thompson 的 NFA 模拟算法慢。

顺便说一下,re2引擎(上面提到的)包括基于DFA和基于NFA(高效模拟)匹配算法的实现。

【讨论】:

    【解决方案2】:

    Python 2.7 说:

    import timeit
    once = 'import re; m="/files/images/test"'
    num = 1000000
    print timeit.timeit(stmt='re.findall(r"/files/(images/.*|videos/.*)", m)', setup=once, number=num)
    -> 1.5884420871734619
    print timeit.timeit(stmt='re.findall(r"/files/(image|video)s/.*", m)', setup=once, number=num)
    -> 1.5990869998931885
    

    这使用了 100 万次正则表达式,并且在多次运行两条线后,它们的速度都相同。

    Python 可能会缓存已编译的正则表达式...

    我用它测试过

    • /files/images/test
    • /files/videos/test
    • /files/viddeos/test

    您的第一个版本 (/files/(images/.*|videos/.*)) 在我的测试中运行得快了一点(0.1 秒)

    【讨论】:

    • 感谢您的测试用例!你会碰巧知道为什么第一个跑得更快吗? python内部是否将其降低到最小的DFA?
    【解决方案3】:

    我的直觉(我知道不是很有帮助)会说第二种选择更好。但是考虑一下:

    • 为什么要添加.*
    • 能否将正则表达式锚定到行首?
    • 您需要捕获该组吗?

    如果这些适用,我会这样做:

    ^/files/(?:image|video)s/
    

    【讨论】:

    • 我无法控制该表达式的使用位置。他们要求 .* 存在。正则表达式不需要锚定到行首)。而且我不需要捕获该组。而且我不知道这如何回答我的问题 :) 你可以作为 cmets 提出这些澄清。
    • 答案是“我的直觉告诉我第二个更好”。当然,其余的可能是评论。 :)
    猜你喜欢
    • 2011-11-22
    • 1970-01-01
    • 1970-01-01
    • 2021-12-29
    • 1970-01-01
    • 2019-11-19
    • 2019-02-08
    • 2023-03-31
    • 1970-01-01
    相关资源
    最近更新 更多