最快的正则表达式,与任何字符串都不匹配

时间:2013-01-26 08:25:24

标签: python regex

任何字符串不匹配的执行速度最快的正则表达式是什么?这似乎是一件无用的事情,但考虑一个程序,它将强制正则表达式作为例子过滤器(这实际上是我的场景)。我尝试了一些,发现b(?<!b)是最佳表现者,因为b在输入中很少出现。

这是我编写的一个python代码,用于测试速度的不同模式:

#!/usr/bin/env python

import re
import time

tests = [
  r'a\A',
  r'b\A',
  r'a^',
  r'b^',
  r'[^\s\S]',
  r'^(?<=a)',
  r'^(?<=b)',
  r'a(?<!a)',
  r'b(?<!b)',
  r'\Za',
  r'\Zb',
  r'$a',
  r'$b'
]
timing = []
text = 'a' * 50000000

for t in tests:
  pat = re.compile(t)
  start = time.time()
  pat.search(text)
  dur = time.time() - start
  timing.append((t, dur))

timing.sort(key=lambda x: x[1])
print('%-30s %s' % ('Pattern', 'Time'))
for t, dur in timing:
  print('%-30s %0.3f' % (t, dur))

在我的机器上,我得到以下时间:

Pattern                        Time
b(?<!b)                        0.043
b\A                            0.043
b^                             0.043
$a                             0.382
$b                             0.382
^(?<=a)                        0.395
\Za                            0.395
\Zb                            0.395
^(?<=b)                        0.414
a\A                            0.437
a^                             0.440
a(?<!a)                        0.796
[^\s\S]                        1.469

更新:为一些建议的正则表达式添加了基准。

3 个答案:

答案 0 :(得分:2)

单个字符是有效的正则表达式。一个不是“魔法”的单个角色会与自己匹配。如果你能识别出一个永远不会出现在你的文本中的单个字符,你可以从中创建一个模式。

ASCII NUL,字符0怎么样?

我在测试程序中又插入了一个字符串,字符串:'\0'

它与您最好的模式一样快:b(?<!b)

好的,你在字符串结尾后已经有了一个字符。在字符串开头之前字符怎么样?这是不可能的:'x^'

啊哈!这比在字符串结束后检查字符要快。但它与你最好的模式一样快。

我建议用ASCII NUL替换b并将其称为好。当我尝试这种模式时:\0(?<!\0)

它赢了一小部分。但实际上,在我的计算机上,上面讨论的所有内容都非常接近,以至于没有太多可以区分它们。

结果:

Pattern                        Time
\0(?<!\0)                      0.098
\0                             0.099
x^                             0.099
b(?<!b)                        0.099
^(?<=x)                        1.416
$b                             1.446
$a                             1.447
\Za                            1.462
\Zb                            1.465
[^\s\S]                        2.280
a(?<!a)                        2.843

那是有趣。感谢您发布问题。

编辑:啊哈哈!我重写了程序,用真实的输入数据进行测试,得到了不同的结果。

我从Project Gutenberg下载了“William Shakespeare全集”作为文本文件。 (很奇怪,它在wget上出错,但让我的浏览器得到它...某种措施可以防止自动复制?)网址:http://www.gutenberg.org/cache/epub/100/pg100.txt

以下是结果,然后在我运行时修改了程序。

Pattern                        Time
\0(?<!\0)                      0.110
\0                             0.118
x^                             0.119
b(?<!b)                        0.143
a(?<!a)                        0.275
^(?<=x)                        1.577
$b                             1.605
$a                             1.611
\Za                            1.634
\Zb                            1.634
[^\s\S]                        2.441

所以是的,我肯定会选择第一个。

#!/usr/bin/env python

import re
import time

tests = [
  r'x^',
  r'\0',
  r'[^\s\S]',
  r'^(?<=x)',
  r'a(?<!a)',
  r'b(?<!b)',
  r'\0(?<!\0)',
  r'\Za',
  r'\Zb',
  r'$a',
  r'$b'
]
timing = []
#text = 'a' * 50000000
text = open("/tmp/pg100.txt").read()
text = text * 10

for t in tests:
  pat = re.compile(t)
  start = time.time()
  pat.search(text)
  dur = time.time() - start
  timing.append((t, dur))

timing.sort(key=lambda x: x[1])
print('%-30s %s' % ('Pattern', 'Time'))
for t, dur in timing:
  print('%-30s %0.3f' % (t, dur))

答案 1 :(得分:0)

这可能不是你正在寻找的那种答案,但我在这里摆弄了几分钟,我能做的最好就是:'a{%d}' % (len(input)+1)。或者,换句话说,将您的模式计算为最大量词长度超过字符串的已知长度的任何字符。这显然只有在您有权访问输入字符串时才有效。它似乎也很好地扩展;我将文本变量输出到texts = [os.urandom(20000) for x in range(20000)],在这种情况下,a{20001}仍然在0.013

但是,我还没有计算出获取字符串长度并生成模式的操作成本,显然你必须在野外处理它。

更新了脚本:

import os
import re
import time

tests = [
  r'\0(?<!\0)',
  r'\0',
  r'x^',
  r'a{2001}',
  r'a(?<!a)',
  r'b(?<!b)',
  r'\Za',
  r'\Zb',
  r'$a',
  r'$b'
]
texts = [os.urandom(2000) for x in range(20000)]
flag = 0

for t in tests:
    pat = re.compile(t)
    start = time.time()
    for text in texts:
        if pat.search(text):
            print('%-30s %10s' % (t, "FAILED"))
            flag = 1
            break
    if flag == 0:
        dur = time.time() - start
        print('%-30s %0.3f' % (t, dur))
    else:
        flag = 0

基准:

\0(?<!\0)                      0.058
\0                                 FAILED
x^                             0.055
a{2001}                        0.022
a(?<!a)                        0.060
b(?<!b)                        0.073
\Za                            0.798
\Zb                            0.797
$a                             0.757
$b                             0.767

答案 2 :(得分:0)

通常我们会使用直接失败的简短(?!)

如果你想要更明确Perl / PCRE有你可以使用的(*FAIL)(*F)动词。