使用ICU 4.0正则表达式库,我发现以下正则表达式呈现指数时间:
actual: "[^<]*<\?"
C code: "[^<]*<\\?"
目标:找到“&lt;?”哪里没有其他“&lt;”在它之前
在没有“&lt;”的纯文本上运行此正则表达式时字符似乎需要指数时间。如果文本至少有一个“&lt;”那很快。我不明白为什么。
不应与“&lt;?”上的所需匹配防止这需要回溯?我本以为它会尝试找到第一个“&lt;”然后测试表达式的其余部分。如果找不到“&lt;”然后它会放弃,因为模式显然无法匹配。
这是ICU正则表达式中的错误还是预期?
答案 0 :(得分:6)
您可以在Regular Expression Matching Can Be Simple And Fast找到解释 正如MizardX所说,如果匹配在位置0处失败,引擎将再次尝试位置1,2等。如果文本很长,请准备好等待一段时间......
解决方案是锚定您的表达式:"^[^<]*<\?"
答案 1 :(得分:2)
这是占有量词和原子团发挥作用的地方。在Java中,我会这样做:
String regex = "[^<]*+<\\?";
或者这个:
String regex = "(?>[^<]*)<\\?";
无论哪种方式,一旦[^<]*
部分完全匹配,它就会拒绝回溯。如果下一个部分在下一个位置无法匹配,则匹配失败。 Java和PHP都有这两个功能,.NET有原子组;我不了解其他语言。
答案 2 :(得分:1)
正则表达式引擎并不那么聪明。它将尝试匹配每个位置,每个时间从<?
开始搜索,然后回溯到匹配尝试的开始。这给出了二次时间复杂度O( n 2 )。
答案 3 :(得分:1)
不幸的是,这是预期的。来自RegularExpressions.info
这是一个非常重要的理解点:
一个正则表达式引导的引擎将始终返回最左边的匹配,即使稍后可以找到“更好”的匹配。将正则表达式应用于字符串时,引擎将从字符串的第一个字符开始。它会在第一个时尝试所有可能的正则表达式排列 字符。
只有在尝试了所有可能性并且发现失败时,引擎才会继续使用文本中的第二个字符 同样,它将以完全相同的顺序尝试所有可能的正则表达式排列 结果是正则表达式引擎将返回最左边的匹配。
所以在ABC上它正在尝试“ABC”,失败,尝试“BC”,失败,然后尝试“C”并失败。如果贪婪的“[^&lt;]”实际上一直成功直到结束,那么它就不会那么令人讨厌,因为它找不到&lt;?
答案 4 :(得分:0)
很抱歉这么长的帖子。为清晰起见,已对样本输出进行了编辑。
perl -Mre=debug -e' "abcdefghijklm" =~ /[^<]*<[?]/; '
Compiling REx "[^<]*<[?]"
Final program:
1: STAR (13)
2: ANYOF[\0-;=-\377{unicode_all}] (0)
13: EXACT <<?> (17)
17: END (0)
floating "<?" at 0..2147483647 (checking floating) minlen 2
Guessing start of match in sv for REx "[^<]*<[?]" against "abcdefghijklm"
Did not find floating substr "<?"...
Match rejected by optimizer
Freeing REx: "[^<]*<[?]"
perl -Mre=debug -e' "ab<?" =~ /[^<]*(?!<)<[?]/; '
Compiling REx "[^<]*(?!<)<[?]"
Final program:
1: STAR (13)
2: ANYOF[\0-;=-\377{unicode_all}] (0)
13: UNLESSM[0] (19)
15: EXACT <<> (17)
17: SUCCEED (0)
18: TAIL (19)
19: EXACT <<?> (23)
23: END (0)
floating "<?" at 0..2147483647 (checking floating) minlen 2
Guessing start of match in sv for REx "[^<]*(?!<)<[?]" against "ab<?"
Found floating substr "<?" at offset 2...
Guessed: match at offset 0
Matching REx "[^<]*(?!<)<[?]" against "ab<?"
# Start at first pos()
# |
# V
0 <> <ab<?> | 1:STAR(13)
ANYOF[\0-;=-\377{unicode_all}] can match 2 times out of 2147483647...
2 <ab> <<?> | 13: UNLESSM[0](19)
2 <ab> <<?> | 15: EXACT <<>(17)
3 <ab<> <?> | 17: SUCCEED(0)
subpattern success...
failed...
# try with one fewer [^<]*
1 <a> <b<?> | 13: UNLESSM[0](19)
1 <a> <b<?> | 15: EXACT <<>(17)
failed...
# try with one fewer [^<]* again
1 <a> <b<?> | 19: EXACT <<?>(23)
failed...
# try with zero [^<]*
0 <> <ab<?> | 13: UNLESSM[0](19)
0 <> <ab<?> | 15: EXACT <<>(17)
failed...
0 <> <ab<?> | 19: EXACT <<?>(23)
failed...
failed...
# Start at second pos()
# |
# V
1 <a> <b<?> | 1:STAR(13)
ANYOF[\0-;=-\377{unicode_all}] can match 1 times out of 2147483647...
2 <ab> <<?> | 13: UNLESSM[0](19)
2 <ab> <<?> | 15: EXACT <<>(17)
3 <ab<> <?> | 17: SUCCEED(0)
subpattern success...
failed...
1 <a> <b<?> | 13: UNLESSM[0](19)
1 <a> <b<?> | 15: EXACT <<>(17)
failed...
1 <a> <b<?> | 19: EXACT <<?>(23)
failed...
failed...
# Start at third and final pos()
# |
# V
2 <ab> <<?> | 1:STAR(13)
ANYOF[\0-;=-\377{unicode_all}] can match 0 times out of 2147483647...
2 <ab> <<?> | 13: UNLESSM[0](19)
2 <ab> <<?> | 15: EXACT <<>(17)
3 <ab<> <?> | 17: SUCCEED(0)
subpattern success...
failed...
failed...
Match failed
Freeing REx: "[^<]*(?!<)<[?]"
如果您错过了它,它会尝试在失败之前尽可能地匹配'[^<]*'
。试想一下,如果你试图对一个大文件运行这个匹配,只是发现最后两个字符不是'<?'
。
^
在以下文字中是BOL(行首)。
perl -Mre=debug -e' "abcdefghijklm<?" =~ /^[^<]*+(?!<)<[?]/; '
Compiling REx "^[^<]*+(?!<)<[?]"
Final program:
1: BOL (2)
2: SUSPEND (18)
4: STAR (16)
5: ANYOF[\0-;=-\377{unicode_all}] (0)
16: SUCCEED (0)
17: TAIL (18)
18: UNLESSM[0] (24)
20: EXACT <<> (22)
22: SUCCEED (0)
23: TAIL (24)
24: EXACT <<?> (28)
28: END (0)
floating "<?" at 0..2147483647 (checking floating) anchored(BOL) minlen 2
Guessing start of match in sv for REx "^[^<]*+(?!<)<[?]" against "abcdefghijklm<?"
Found floating substr "<?" at offset 13...
Guessed: match at offset 0
Matching REx "^[^<]*+(?!<)<[?]" against "abcdefghijklm<?"
0 <> <abcdefghij> | 1:BOL(2)
0 <> <abcdefghij> | 2:SUSPEND(18)
0 <> <abcdefghij> | 4: STAR(16)
ANYOF[\0-;=-\377{unicode_all}] can match 13 times out of 2147483647...
13 <defghijklm> <<?> | 16: SUCCEED(0)
subpattern success...
13 <defghijklm> <<?> | 18:UNLESSM[0](24)
13 <defghijklm> <<?> | 20: EXACT <<>(22)
14 <defghijklm<> <?> | 22: SUCCEED(0)
subpattern success...
failed...
Match failed
Freeing REx: "^[^<]*+(?!<)<[?]"
你应该注意到,这比前一个例子快得多。
答案 5 :(得分:0)
我不是真正的正则表达式引擎实际工作的专家,但我知道有些(全部?)是贪婪的,并会尽可能早地尝试匹配。因此,假设您要匹配的字符串s
,其中没有'<'
个字符。它首先匹配正则表达式的[^<]*
部分,基本上匹配从s[0]
到s[n-1]
的所有内容(s
是零索引的,并且没有c-string细微差别,所以这是整个字符串)。然后它将在模式中的下一个元素('<'
字符)上失败。接下来,它将回溯到匹配[^<]*
从s[0]
到s[n-2]
,尝试匹配'<'
,然后再次失败。这将重复,直到它匹配位置0处的长度为0的字符串(注意*
匹配零个或多个重复,并且最后的情况是零重复)。因此,它将确定从位置0开始不能导致成功匹配,因此它将重复上述内容,这次在s[1]
处开始匹配字符的范围,仅在耗尽所有这些范围后再次失败。然后它将从位置2开始,依此类推,直到它在最后一个字符后尝试匹配。然后就会放弃。
修改强>
您的正则表达式基本上匹配以<?
结尾且不包含其他<
的字符串的任何部分,例如<<?
中的<?
匹配ba<abc<?def
和abc<?
},它匹配^[^<]*<\?
。提供的其他一些建议将表现不同。特别是,{{1}}与这两个示例中的任何内容都不匹配。