匹配长字符串时,finditer挂起

时间:2009-04-16 09:14:27

标签: python regex performance

我有一个有点复杂的正则表达式,我试图匹配一个长字符串(65,535个字符)。我正在寻找字符串中多次出现的re,所以我正在使用finditer。它有效,但出于某种原因,它在识别出前几次出现后就会挂起。有谁知道为什么会这样?这是代码片段:

pattern = "(([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*b|d*)*c)"

matches = re.finditer(pattern, string)
for match in matches:
    print "(%d-%d): %s" % (match.start(), match.end(), match.group())

它打印出前四次出现,但随后挂起。当我使用Ctrl-C杀死它时,它告诉我它在迭代器中被杀死了:

Traceback (most recent call last):
  File "code.py", line 133, in <module>
    main(sys.argv[1:])
  File "code.py", line 106, in main
    for match in matches:
KeyboardInterrupt

如果我用更简单的方法尝试它,它可以正常工作。

我在运行在Windows XP上的Cygwin上的python 2.5.4上运行它。

我设法让它以一个非常短的字符串挂起。使用这个50个字符的字符串,它在大约5分钟后就没有返回:

ddddddeddbedddbddddddddddddddddddddddddddddddddddd

使用这个39个字符的字符串返回大约需要15秒(并且不显示匹配项):

ddddddeddbedddbdddddddddddddddddddddddd

使用此字符串,它会立即返回:

ddddddeddbedddbdddddddddddddd

6 个答案:

答案 0 :(得分:5)

你的表达式是否会在Python RE引擎中触发指数行为?

This article处理此问题。如果你有时间,你可能想尝试在使用这些想法开发的RE引擎中运行你的表达式。

答案 1 :(得分:5)

绝对指数行为。你的正则表达式中有很多d*个部分,当它到达长串的d时它会像疯了一样回溯,但是不能提前匹配。您需要重新考虑正则表达式,因此尝试的路径较少。

我特别想:

([ef]d\*b|d\*)*</pre></code> and <code><pre>([ef]|([gh]d\*(ad\*[gh]d)\*b))d\*b

可能需要重新思考,因为他们会强制重试替补比赛。另外,它们在匹配方面也有重叠。例如,他们都匹配edb,但如果一个失败并试图回溯其他部分可能会有相同的行为。

因此,如果可以的话,尽量不要使用|,并尝试确保模式在可能的情况下不重叠。

答案 2 :(得分:2)

我认为你经历了所谓的“灾难性回溯”。

你的正则表达式有许多可选/替代部分,所有部分仍然尝试匹配,所以先前的子表达式在本地失败时将字符返回到下面的表达式。这导致正则表达式中的后退和第四行为以及指数上升的执行时间。

Python(2.7+?,我不确定)支持原子分组和占有量词,你可以检查你的正则表达式来识别应该匹配或失败的部分。这可以控制不必要的回溯。

答案 3 :(得分:2)

灾难性的回溯!

  

正则表达式可能非常昂贵。某些(非预期的和预期的)字符串可能会导致RegExes表现出指数行为。我们为此采取了几个修补程序。 RegExes非常方便,但开发人员真的需要了解它们的工作原理;我们被他们咬了。

示例和调试器:

http://www.codinghorror.com/blog/archives/000488.html

答案 4 :(得分:2)

感谢所有回复,非常有帮助。最后,令人惊讶的是,它很容易加快速度。这是原始的正则表达式:

(([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*b|d*)*c)

我注意到接近末尾的| d *并不是我所需要的,所以我修改了它如下:

(([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*bd*)*c)

现在它几乎可以立即在65,536个字符串上运行。我想我现在必须确保正则表达式真正匹配我需要匹配的字符串...

答案 5 :(得分:1)

你已经给了自己答案:正则表达式是复杂而模棱两可的。

您应该尝试找到一个更简单,更易于处理的表达式。或者告诉我们你想要完成什么,我们可以帮你找到一个。


修改如果您只想在John Montgomery’s answer的评论中对每个位置允许d,则应在测试模式之前将其删除:

import re

string = "ddddddeddbedddbddddddddddddddddddddddddddddddddddd"
pattern = "(([ef]|([gh](a[gh])*b))b([ef]b)*c)"
matches = re.finditer(pattern, re.sub("d+", "", string))
for match in matches:
    print "(%d-%d): %s" % (match.start(), match.end(), match.group())