为什么正则表达式“[^<] *< \\?”展示文本没有“<”时的指数时间?

时间:2008-11-24 11:12:44

标签: regex

使用ICU 4.0正则表达式库,我发现以下正则表达式呈现指数时间:

actual: "[^<]*<\?"
C code: "[^<]*<\\?"

目标:找到“&lt;?”哪里没有其他“&lt;”在它之前

在没有“&lt;”的纯文本上运行此正则表达式时字符似乎需要指数时间。如果文本至少有一个“&lt;”那很快。我不明白为什么。

不应与“&lt;?”上的所需匹配防止这需要回溯?我本以为它会尝试找到第一个“&lt;”然后测试表达式的其余部分。如果找不到“&lt;”然后它会放弃,因为模式显然无法匹配。

这是ICU正则表达式中的错误还是预期?

6 个答案:

答案 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 re dump

很抱歉这么长的帖子。为清晰起见,已对样本输出进行了编辑。

Perl正则表达式引擎采用快捷方式。所以我的第一次运行没有输出任何有用的东西。

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<?defabc<? },它匹配^[^<]*<\?。提供的其他一些建议将表现不同。特别是,{{1}}与这两个示例中的任何内容都不匹配。