我有一个有点复杂的正则表达式,我试图匹配一个长字符串(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
答案 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非常方便,但开发人员真的需要了解它们的工作原理;我们被他们咬了。
示例和调试器:
答案 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())