为什么 Regex (c++) 需要指数时间?
Why is Regex (c++) taking exponential time?
我正在做一些教科书中的正则表达式问题,其中内容如下:
[匹配] 在行首以整数开头并在行尾以单词结尾的所有字符串。"
我为此编写了以下正则表达式:
^[0-9]+\s.*+\b[a-zA-Z]+$
然而,当我使用以下代码在 C++ 中实现它时:
#include <iostream>
#include <string>
#include <regex>
#include <time.h>
int main(){
clock_t t;
bool match;
std::string exp = "^[0-9]+\s.*+\b[a-zA-Z]+$";
std::string str = "1 a few words 1";
std::string s (str);
std::smatch m;
std::regex e (exp);
while (true){
t = clock();
match = std::regex_match(s, m, e);
s = s + "1";
std::cout << clock() - t << std::endl;
}
}
每次迭代花费的 cpu 时间是:
1 1181529
2 3398674
3 10102763
4 30370932
5 92491242
看起来复杂度是 O( 3^n )
为什么会这样?是不是我的表达有问题?
如果我使用像“1 a 1”这样的字符串,虽然常数较小,但增长因子是相同的。
编辑:我看到问题是我遇到了 .*+
哎呀!我仍然不确定为什么这会导致指数行为。
我认为正则表达式引擎只想找到任何 .* 1 次,因为 +。这已经是无穷无尽的了,所以引擎会在一段时间后取消操作。
问题是 .*+\b
而不是 .*\b
我很确定你想要的。
至于为什么这会导致可怕的行为:问题是 .*
可以计算任意数量的字符,而 +
意味着匹配任意数量的字符。但是,为了符合 POSIX 规范,它必须尝试使整个模式匹配尽可能长的字符串。我的猜测是,要做到这一点,首先要尝试使用 .*
来匹配一个字符,然后重复 N 次。然后它尝试用 .*
匹配两个字符,并重复 M 次。然后它尝试用 .*
匹配三个字符,并重复它们 L 次(依此类推)。哦,请注意,它也不必让所有 .*
模式都匹配相同数量的字符,因此组合的数量呈指数增长。
由于它不知道整体应该匹配多少个字符,它会尝试所有可能的组合,直到到达最后一个,发现它们都匹配相同长度的字符串,并宣布整体失败(因为你有一个 \b
这是一个反 space 字符,它不存在于你的输入字符串中)。根据您是使用 NFA 还是 DFA 进行正则表达式匹配,您可能会得到您观察到的可怕行为,或者您可能会得到完全线性的行为——或者(取决于您如何进行 DFA/NFA 转换)它可能只是无法编译正则表达式(这可能不太符合要求,但仍然可能是更可取的行为)。
我正在做一些教科书中的正则表达式问题,其中内容如下:
[匹配] 在行首以整数开头并在行尾以单词结尾的所有字符串。"
我为此编写了以下正则表达式:
^[0-9]+\s.*+\b[a-zA-Z]+$
然而,当我使用以下代码在 C++ 中实现它时:
#include <iostream>
#include <string>
#include <regex>
#include <time.h>
int main(){
clock_t t;
bool match;
std::string exp = "^[0-9]+\s.*+\b[a-zA-Z]+$";
std::string str = "1 a few words 1";
std::string s (str);
std::smatch m;
std::regex e (exp);
while (true){
t = clock();
match = std::regex_match(s, m, e);
s = s + "1";
std::cout << clock() - t << std::endl;
}
}
每次迭代花费的 cpu 时间是:
1 1181529
2 3398674
3 10102763
4 30370932
5 92491242
看起来复杂度是 O( 3^n )
为什么会这样?是不是我的表达有问题?
如果我使用像“1 a 1”这样的字符串,虽然常数较小,但增长因子是相同的。
编辑:我看到问题是我遇到了 .*+
哎呀!我仍然不确定为什么这会导致指数行为。
我认为正则表达式引擎只想找到任何 .* 1 次,因为 +。这已经是无穷无尽的了,所以引擎会在一段时间后取消操作。
问题是 .*+\b
而不是 .*\b
我很确定你想要的。
至于为什么这会导致可怕的行为:问题是 .*
可以计算任意数量的字符,而 +
意味着匹配任意数量的字符。但是,为了符合 POSIX 规范,它必须尝试使整个模式匹配尽可能长的字符串。我的猜测是,要做到这一点,首先要尝试使用 .*
来匹配一个字符,然后重复 N 次。然后它尝试用 .*
匹配两个字符,并重复 M 次。然后它尝试用 .*
匹配三个字符,并重复它们 L 次(依此类推)。哦,请注意,它也不必让所有 .*
模式都匹配相同数量的字符,因此组合的数量呈指数增长。
由于它不知道整体应该匹配多少个字符,它会尝试所有可能的组合,直到到达最后一个,发现它们都匹配相同长度的字符串,并宣布整体失败(因为你有一个 \b
这是一个反 space 字符,它不存在于你的输入字符串中)。根据您是使用 NFA 还是 DFA 进行正则表达式匹配,您可能会得到您观察到的可怕行为,或者您可能会得到完全线性的行为——或者(取决于您如何进行 DFA/NFA 转换)它可能只是无法编译正则表达式(这可能不太符合要求,但仍然可能是更可取的行为)。