我正在解析表单的(种类)名称:
Parus Ater
H. sapiens
T. rex
Tyr. rex
通常有两个术语(二项式)但有时有3个或更多。
Troglodytes troglodytes troglodytes
E. rubecula sensu stricto
我写了
[A-Z][a-z]*\.?\s+[a-z][a-z]+(\s*[a-z]+)*
大部分时间都有效,但偶尔会进入无限循环。需要一些时间来追踪它是在正则表达式匹配中,然后我意识到这是一个错字,我应该写了
[A-Z][a-z]*\.?\s+[a-z][a-z]+(\s+[a-z]+)*
执行得当。
我的问题是:
[注意:我不需要为物种提供更通用的表达式 - 对于物种名称有一个正式的100+行正则表达式规范 - 这只是一个初始过滤器]。
注意:问题出现了,因为虽然大多数名称被精确地提取为2或偶尔3/4术语(因为它们用斜体字表示),但是有一些误报(如"Homo sapiens lives in big cities like London"
)并且匹配在& #34; L"。]
注意:在调试中我发现正则表达式经常完成但速度很慢(例如在较短的目标字符串上)。通过病理案例发现这个错误是很有价值的。我学到了一个重要的教训!
答案 0 :(得分:6)
要解决问题的第一部分,请阅读catastrophic backtracking。从本质上讲,正在发生的事情是有太多方法可以将正则表达式与字符串匹配,并且解析器会不断回溯以尝试使其正常工作。
在你的情况下,它可能是嵌套的重复:(\s*[a-z]+)*
这可能导致一些非常奇怪的循环。正如Qtax娴熟地指出的那样,没有更多信息就很难说出来。
遗憾的是,你的问题的第二部分无法回答。它基本上是Halting problem。由于正则表达式本质上是一个有限状态机,其输入是一个字符串,因此您无法创建一个通用解决方案来预测哪些正则表达式将灾难性地回溯,哪些不会.- / p>
有关使正则表达式运行得更快的一些提示吗?这是一大堆蠕虫。我花了很多时间自己研究正则表达式,有时候优化它们,这是我发现的一般帮助:
^
。另见:Word Boundaries 希望这会对你有所帮助。祝你好运。
答案 1 :(得分:2)
对于第一个正则表达式:
[A-Z][a-z]*\.?\s+[a-z][a-z]+(\s*[a-z]+)*
由于评论中指出的(\s*[a-z]+)*
,发生了灾难性的回溯。但是,只有在使用String.matches()
验证字符串时才会成立,因为遇到无效字符的唯一情况会导致引擎尝试回溯,而不是返回匹配(Matcher
循环)。
让我们将一个无效字符串与(\s*[a-z]+)*
匹配:
inputstringinputstring;
(Repetition 1)
\s*=(empty)
[a-z]+=inputstringinputstring
FAILED
Backtrack [a-z]+=inputstringinputstrin
(Repetition 2)
\s*=(empty)
[a-z]+=g
FAILED
(End repetition 2 since all choices are exhausted)
Backtrack [a-z]+=inputstringinputstri
(Repetition 2)
\s*=(empty)
[a-z]+=ng
FAILED
Backtrack [a-z]+=n
(Repetition 3)
\s*(empty)
[a-z]+=g
FAILED
(End repetition 3 since all choices are exhausted)
(End repetition 2 since all choices are exhausted)
Backtrack [a-z]+=inputstringinputstr
到现在为止,你应该注意到这个问题。让我们将T(n)
定义为检查长度为n的字符串与模式不匹配的工作量。从回溯的方法来看,我们知道T(n) = Sum [i = 0..(n-1)] T(i)
。由此,我们可以导出T(n + 1) = 2T(n)
,这意味着回溯过程在时间复杂度上是指数级的!
将*
更改为+
可以完全避免此问题,因为重复实例只能从空格字符和英文字母字符之间的边界开始。相反,第一个正则表达式允许重复的实例在任何2个字母字符之间开始。
为了证明这一点,当无效输入字符串包含许多用多个连续空格分隔的单词时,(\s+[a-z]+\s*)*
会给你回溯地狱,因为它允许重复实例的多个位置开始。这遵循公式b^d
,其中b
是连续空格的最大数量(减1),d
是空格序列的数量。它没有你拥有的第一个正则表达式那么严重(每次重复需要至少一个Englsh字母和一个空格字符,而在你的第一个正则表达式中每次重复需要一个英文字母),但它仍然是一个问题。