如何避免使用这种特定模式的灾难性回溯

Posted

技术标签:

【中文标题】如何避免使用这种特定模式的灾难性回溯【英文标题】:How to avoid catastrophic backtracking with this specific pattern 【发布时间】:2020-11-17 10:32:00 【问题描述】:

我的前任编写的这个大 reg 模式遇到了灾难性的回溯问题。 我们基本上只是想从职位描述中过滤掉薪水。

这是模式:

(?:(?:(?:\bEURO|\bEuro|\beuro|\bEUR|\bEur|\beur|€)[[:punct:]]?\W*(?:(?<!\d)(?:(\d1,3(?:[,.\'\s])\d3)|([12]?\d3,5))(?!\d)(?:[,.](?:\d\d|-|--))?))|(?:(?:(?<!\d)(?:(\d1,3(?:[,.\'\s])\d3)|([12]?\d3,5))(?!\d)(?:[,.](?:\d\d|-|--))?)(?:\W*(?:\bEURO|\bEuro|\beuro|\bEUR|\bEur|\beur|€))))|(?:(?:(?:gehalt|lohn|entgelt|netto|brutto|überzahlung|kollektivvertrag|wage\W|salary)\w*\W+(?:[a-zA-Z[:punct:]]+\W+)0,6(?:(?<!\d)(?:(\d1,3(?:[,.\'\s])\d3)|([12]?\d3,5))(?!\d)(?:[,.](?:\d\d|-|--))?))|(?:(?:(?<!\d)(?:(\d1,3(?:[,.\'\s])\d3)|([12]?\d3,5))(?!\d)(?:[,.](?:\d\d|-|--))?)(?:\w*\W+)0,6\w*(?:gehalt|lohn|entgelt|netto|brutto|überzahlung|kollektivvertrag|wage\W|salary)))

它基本上完成了它打算做的事情,但是在我的 Java 应用程序中,它会导致一些 Job 字符串冻结。那一定不能发生。

我几乎可以肯定,这可以做得更容易,而且要好一百倍,我只是没有时间学习高级的正则表达式。也许一些专业人士对如何防止灾难性的回溯问题有一个快速的、第一眼的想法并帮助我。

它应该能识别出类似的模式

Eur 40.000

<some random text>gehalt: 2000-3000

(两个数值,因此我可以进一步处理以确定最小值/最大值)

2000 - 50.000 euro

等等

例子:

输入:

We offer a salary of 40.000 - 50.000.

匹配:

40.000 , 50.000

输入:

date: 12.10.2020 - We offer eur 3000 - 20.000.

匹配:

3000 , 20.000

输入:

Hello world ! Today is: 12.10.2020 - We offer a Gehalt of eur3000-20.000

匹配:

3000 , 20.000

问题出现在疯狂的字符串中,例如 Crazy String that unfortunately can occur during a web crawl

【问题讨论】:

新造币“cacktracking”的积分,但我觉得不得不将其更改为常规的既定术语。 很难猜测像(?&lt;!\d)(?:(\d1,3(?:[,.\'\s])\d3)|([12]?\d3,5))(?!\d)(?:[,.](?:\d\d|-|--))? 这样的重复片段真正应该捕捉到什么。可以猜到你希望正则表达式的意思,但如果你能把它拼出来就更好了。 感谢您的示例。不过,您的第一个示例似乎实际上并不匹配。见regex101.com/r/ZflJFQ/1 确实没有,但目前这是次要问题。我主要担心程序不会因为灾难性的回溯而冻结。我将发布一个示例字符串:regex101.com/r/BGXYdP/1 这样的字符串永远不会发生......但不幸的是它们可能会发生...... 我没有看到很多回溯的机会,尽管例如(?:\bEURO|\bEuro|\beuro|\bEUR|\bEur|\beur|€)[[:punct:]]?\W*(?:(?&lt;!\d) 非常多余。 [[:punct:]]\W 重叠,因此如果匹配在消耗标点符号时失败,则正则表达式引擎必须尝试将每个标点符号匹配为 \W,这会引入一些但肯定不会 - 孤立地 - 灾难性的回溯。 (?:\b(?:EURO?|[Ee]uro?)|€)\W* 更简洁地匹配相同的字符串,无需回溯((?&lt;!\d) 是多余的,因为无论如何匹配都不能是数字)。 【参考方案1】:

[:punct:] 表达式匹配标点符号,\W 也可以匹配,因此\W+(?:[a-zA-Z[:punct:]]+\W+)0,6 必须替换为\W+(?:[a-zA-Z]+\W+)0,6(?:\w*\W+)0,6 也容易发生灾难性的回溯,因为 \w* 匹配 0 个或多个单词字符,因此该模式属于 (A+)+ 类型。 替换为\w*(?:\W+\w+)0,6\W+

--|- 必须缩小为--?,以尽可能少地使用具有相同首字符的替代项。

大量冗余的非捕获组,删除了一些冗余的lookbehinds。

其余的变化只是增强

使用

(?:(?:\bEURO?|€)\W*(\d1,3[,.\'\s]\d3|[12]?\d3,5)(?!\d)(?:[,.](?:\d\d|--?))?|(?<!\d)(\d1,3[,.\'\s]\d3|[12]?\d3,5)(?!\d)(?:[,.](?:\d\d|--?))?\W*(?:\bEURO?|€))|(?:(?:gehalt|lohn|entgelt|netto|brutto|überzahlung|kollektivvertrag|wage\b|salary)\w*\W+(?:[a-zA-Z]+\W+)0,6(\d1,3[,.\'\s]\d3|[12]?\d3,5)(?!\d)(?:[,.](?:\d\d|--?))?|(?<!\d)(\d1,3[,.\'\s]\d3|[12]?\d3,5)(?!\d)(?:[,.](?:\d\d|--?))?\w*(?:\W+\w+)0,6\W+(?:gehalt|lohn|entgelt|netto|brutto|überzahlung|kollektivvertrag|wage\b|salary))

见proof

【讨论】:

以上是关于如何避免使用这种特定模式的灾难性回溯的主要内容,如果未能解决你的问题,请参考以下文章

如何彻底避免正则表达式的灾难性回溯?

灾难性回溯搜索括号中的数字

使用正则表达式模式时的灾难性回溯错误

如何使这个正则表达式不会导致“灾难性回溯”?

这个正则表达式不应该发生灾难性的回溯

Go 正则表达式中没有灾难性的回溯吗?