使用正则表达式提取两个短语之间的所有单词

时间:2018-07-03 03:32:37

标签: php regex perl

我正尝试使用以下正则表达式提取两个短语之间的所有单词:

\b(?:item\W+(?:\w+\W+){0,2}?(?:1|one)\W+(?:\w+\W+){0,3}?business)\b(.*)\b(?:item\W+(?:\w+\W+){0,2}?(?:3|three)\W+(?:\w+\W+){0,3}?legal\W+(?:\w+\W+){0,3}?proceedings)\b

我正在运行此正则表达式的文档为10-K档案。这些文件太长了,无法在此处发布(例如,参见下面的regex101 url),但是基本上它们是这样的:

ITEM 1. BUSINESS

lots of words

ITEM 2. PROPERTIES

lots of words

ITEM 3. LEGAL PROCEEDINGS

我想提取ITEM 1ITEM 3之间的所有单词。请注意,对于每个10-K档案,每个ITEM的字幕可能会略有不同,因此,我允许在每个词之间添加几个词。

我不断收到灾难性的回溯错误,我不知道为什么。例如,请参阅https://regex101.com/r/zgTiyb/1

我在做什么错了?

2 个答案:

答案 0 :(得分:3)

灾难性的回溯几乎有一个主要原因:

  

A possible match is found but can't finish.

您为正则表达式提供了太多职位,无法尝试。这达到了PCRE的回溯限制。一种快速的解决方法是删除正则表达式中唯一的点星,以便将其替换为限制性的量词,即

.{0,200}

请参见live demo here

但是更好的方法是重新构造正则表达式:

\bitem\b.*?\b(?:1|one)\b(*COMMIT)\W+(?:\w+\W+){0,2}?business\b\h*\R+(?:(?!item\h+(?:3|three)\b)[\s\S])*+item\h+(?:3|three)\b\W+(?:\w+\W+){0,3}?legal\W+(?:\w+\W+){0,3}?proceedings\b

请参见live demo here

您自己的正则表达式在给定的输入字符串上需要大约45K步才能找到这两个匹配项。相比之下,此经过修改的正则表达式需要大约8000个步骤来完成任务。这是一个巨大的进步。

后者不需要s标志(并且不应启用)。如果发现可能的匹配但可能无法完成,我使用(*COMMIT)回溯动词导致了早期失败。

@ Sebastian Proske的解决方案匹配三个子字符串,但我认为第三个匹配不是预期的匹配。如此巨大的第三局比赛是您的正则表达式中断的唯一原因。

read this answer对这个问题有更好的了解。

答案 1 :(得分:0)

这并不是真正的灾难性回溯,只是大量文本和regex101中相对较低的回溯限制。在这种情况下,使用.*并不是最佳选择,因为一旦到达文本文件,它将与文本文件的其余部分匹配,然后逐字符回退以匹配其后的部分-这意味着要过程。

似乎您也可以在那个地方坚持使用\w+\W+,并使用惰性匹配而不是贪婪来获取结果,例如

\b(?:item\W+(?:\w+\W+){0,2}?(?:1|one)\W+(?:\w+\W+){0,3}?business)\b\W+(?:\w+\W+)*?\b(?:item\W+(?:\w+\W+){0,2}?(?:3|three)\W+(?:\w+\W+){0,3}?legal\W+(?:\w+\W+){0,3}?proceedings)\b

请注意,pcre引擎将(?:\w+\W+)优化为(?>\w++\W++),从而通过无字块而不是单个字符来工作。