使正则表达式更有效

时间:2010-10-19 04:26:22

标签: regex optimization performance negative-lookbehind

我试图通过查找“!”,“?”来确定英语句子的结尾(仅大约)。或“。”,但在“。”的情况下。只有在没有诸如Mr.或Dr.

之类的常用缩写之前

有没有办法让下面的正则表达式更有效率?也许通过按尺寸减小,甚至按字母顺序排列负面的观察背景?

这是我现在的正则表达式:

((?<!St|Sgt|Rev|Ltd|Inc|Lt|Jr|Sr|Esq|Inst|Hon|Gen|Cpl|Comdr|Col|Corp|Mr|Dr|Gov|Mrs|Ms|[A-Z]|Assn|Capt)(\.)|(!)|(\?))(\s*$|\s+([_$#]|[A-Z][^.]))

问题:

http://regex.powertoy.org/的网站报道:“7匹配21044探测(已完成)”即使是一个简单的段落......数字21044的惊人大小似乎与负面观察的数量密切相关。

我正在寻求降低RegEx引擎的计算复杂度,因为我有几GB的数据要通过它。

有什么办法可以解决这个问题吗?负面观察真的是实现这一目标的最好/唯一方法吗?有没有办法把它作为一个前瞻呢?正则表达式是否是执行此任务的错误工具?

编辑:我可以使用ActionScript或PHP的RegEx引擎。

编辑:我不能指望句子之间的空格数。 真的吗?叹息。

如果您不了解RegEx引擎的内部工作原理,请不要回答,与优化相关。

提前致谢。

3 个答案:

答案 0 :(得分:4)

我会首先匹配这个时期。而不是:

(?<!St|Sgt|Rev|Ltd|Inc|...|Capt)\.

......这样做:

\.(?<!(?:St|Sgt|Rev|Ltd|Inc|...|Capt)\.)

你拥有它的方式,正则表达式引擎必须在它确定下一个字符是一个句点之前执行lookbehind。当我进行更改时,它会从 28,423 探针变为 1,945 。 (我使用的是Powertoy网站提供的默认文本,因为您没有提供任何文本。)

我还将接下来的两个选项 - (!)|(\?) - 组合成一个,即([!?])。这会将探测计数降至 1,344 。如果你 没有捕获匹配的各个部分,我建议你使用非捕获括号 - 即(?:...)而不是(...)。虽然它没有反映在探测计数中,但它确实使正则表达式更有效。

编辑:仔细观察正则表达式,我发现找不到任何匹配项的原因是[A-Z]替代方案。由于整个匹配是在不区分大小写的模式下完成的,因此该替代方案胜过所有其他模式。您应该删除/i修饰符或使用(?-i:...)构造在本地覆盖。正如@Swiss所建议的那样,添加\b(单词边界)也是一个好主意。这让你:

(?-i:\.(?<!\b(?:St|Sgt|Rev|Ltd|Inc|...|[A-Z]|Assn|Capt)\.)

...与[!?]更改一起,在Regex Powertoy网站上的1404个探针中产生6个匹配。

答案 1 :(得分:4)

也许只有在成功匹配后才尝试进行负面观察测试。而不是每个角色:

(?x:  # Allow spacing and comments
    (   
        (\.)    # First match "."
        (?<!    # Then negative-look-behind for titles followed by "."
            (?: St|Sgt|Rev|Ltd|Inc|Lt|Jr|Sr|Esq|Inst|Hon|Gen|Cpl|Comdr|Col|Corp|Mr|Dr|Gov|Mrs|Ms|[A-Z]|Assn|Capt)
            \.
        )
      |  (!)  
      |  (\?)
    )
    ( \s* $  |  \s+ ( [_$#] | [A-Z] [^.] ))
)

使用该网站的初始帮助文本,在powertoy.org上将探测次数从70000降至2500左右。 (但是powertoy不喜欢我的多行正则表达式或“x”标志或其他东西所以我不得不将正则表达式压缩到一行来测试它。)

您可以通过在标题列表中使用公共前缀来进一步:

(?x:  # Allow spacing and comments
    (
        (\.)    # First match "."
        (?<!    # Then negative-look-behind for titles followed by "."
            (?:Assn|C(?:apt|ol|omdr|orp|pl)|Dr|Esq|G(?:en|ov)|Hon|I(?:nc|nst)|Jr|L(?:t|td)|M(?:[rs]|rs)|Rev|S(?:gt|[rt])|[A-Z])
            \.
        )
      |  (!)  
      |  (\?)
    )
    ( \s* $  |  \s+ ( [_$#] | [A-Z] [^.] ))
)

这使得探测次数减少到大约2000次。

修改
减少探测次数的另一个技巧是在后视部分的开头包含一个大写字母的预测(但我不能肯定它会使正则表达式更有效)(还包括@ Swiss的关于字边界的建议):

        (?<!   # Then negative-look-behind for titles followed by "."
               \b (?= [A-Z] )  # But first ensure we have a capital letter before going on
               (?:Assn|C(?:apt|ol|omdr|orp|pl)|Dr|Esq|G(?:en|ov)|Hon|I(?:nc|nst)|Jr|L(?:t|td)|M(?:[rs]|rs)|Rev|S(?:gt|[rt])|[A-Z])
            \.
        )

答案 2 :(得分:2)

停止按下!

根据您编辑的问题,您正在为此项目使用PHP或ActionScript,这意味着到目前为止提供的解决方案都不会有任何好处。您的原始正则表达式不仅速度太慢,而且在目标平台上 无法正常工作。它在Regex Powertoy网站上工作的原因是因为该网站使用了Java正则表达式,它对外观有更宽松的限制。

Lookbehind在正则表达式特征中是一种黑羊;几乎所有的口味都对你可以在一个表达中使用的表达方式施加了限制。例如,在Perl或Python中,表达式必须具有固定长度:(?<!St)将起作用,(?<!Sgt|Rev)也起作用,但(?<!St|Sgt)不起作用。 Java更宽松;它接受可变长度的lookbehind表达式,只要可以提前确定最大可能长度,因此(?<!St|Sgt)将起作用,(?<!\w{3,12})也会起作用,但(?<!\w+)不起作用。

PHP和ActionScript正则表达式都由PCRE库提供支持,就外观而言,它只比Perl和Python稍微宽松一些。如果lookbehind表达式是一个替换,每个替代的长度仍然必须修复,但它们不必都具有相同的长度。但这只允许在正则表达式的顶层“级别” - 也就是说,lookbehind既不能包含另一个分组结构,也不能包含在其中。

这是迄今为止提供所有解决方案的部分,不可行。为了解决分组限制,我必须将.!?的检查合并到[.!?],并分发\b和{{1在替代方案中,导致

\.

这使探针计数达到 2208 ,这仍然比您开始时的数量级好一些。对于你的几GB文本来说,这是否足够快,我不知道。

编辑:在您的评论中,您建议按长度对标题进行分组,但不起作用。但是,更进一步,我把每个组都放在自己的外观中,并且(令我惊讶的是)确实似乎有效。以下是自由间隔模式下的外观:

/([.!?])(?<!\bSt\.|\bSgt\.|\bRev\.|\bLtd\.|\bInc\.|\bLt\.|\bJr\.|\bSr\.|\bEsq\.|\bInst\.|\bHon\.|\bGen\.|\bCpl\.|\bComdr\.|\bCol\.|\bCorp\.|\bMr\.|\bDr\.|\bGov\.|\bMrs\.|\bMs\.|\b[A-Z]\.|\bAssn\.|\bCapt\.)(?:\s*$|\s+(?:[_$#]|[A-Z][^.]))/

要查看它的实际效果,请查看PHP demo on ideone.com