如何避免使用这种特定模式的灾难性回溯
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”的积分,但我觉得不得不将其更改为常规的既定术语。 很难猜测像(?<!\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*(?:(?<!\d)
非常多余。 [[:punct:]]
与 \W
重叠,因此如果匹配在消耗标点符号时失败,则正则表达式引擎必须尝试将每个标点符号匹配为 \W
,这会引入一些但肯定不会 - 孤立地 - 灾难性的回溯。 (?:\b(?:EURO?|[Ee]uro?)|€)\W*
更简洁地匹配相同的字符串,无需回溯((?<!\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
【讨论】:
以上是关于如何避免使用这种特定模式的灾难性回溯的主要内容,如果未能解决你的问题,请参考以下文章