【问题标题】:Understanding what makes this regexp so slow了解是什么让这个正则表达式如此缓慢
【发布时间】:2016-07-14 20:59:18
【问题描述】:

我有一个正则表达式:

import re

regexp = re.compile(r'^(?P<parts>(?:[\w-]+/?)+)/$')

它匹配foo/bar/baz/ 之类的字符串,并将foo/bar/baz 放在名为parts 的组中(/?/$ 组合支持这一点)。

这很好用,直到你匹配一个不以斜杠结尾的字符串。然后,随着您添加到要匹配的字符串中的每个新字符,它会以看似指数的速度变慢。

示例

# This is instant (trailing slash)
regexp.match('this-will-take-no-time-at-all/')

# This is slow
regexp.match('this-takes-about-5-seconds')

# This will not finish
regexp.match('this-probably-will-not-finish-until-the-day-star-turns-black')

我试图理解为什么这个特定的递归问题只发生在/$(斜杠)不在字符串中(即不匹配)时。你能帮我理解下斜杠和非斜杠情况下底层算法的控制流程吗?

注意

我不是在为我想要的模式寻找解决方案。我正在尝试了解具体的正则表达式。

【问题讨论】:

  • 将您的模式和示例粘贴到regex101.com,切换到调试器并取消选中内部优化 - 然后看看 :) 引擎尝试使用两种 +-重复的所有可能组合,试图以某种方式找到匹配项。
  • @SebastianProske 感谢您的建议,但 regex101.com 只是在调试器中立即告诉我该模式根本不匹配,这意味着他们的引擎(JS?)没有使用相同的像 Python 一样的算法(永远不会完成)。
  • 又是一个类似(A+)+的模式。导致问题的原因是灾难性的回溯。我猜你需要^(?P&lt;parts&gt;[\w-]+(?:/[\w-]+)*)/$
  • @orokusaki 你应该turn off the internal optimizations 看看会发生什么。
  • 类似情况:stackoverflow.com/questions/22650098/… 有几个很好的解释。

标签: python regex regex-greedy


【解决方案1】:

Wiktor Stribizew 是正确的。问题是重复模式内斜线后的问号。所以,你给出的模式:

'^(?P<parts>(?:[\w-]+/?)+)/$'

说,寻找一组或多组一个或多个单词字符或破折号可能后跟一个斜线,那么最后应该有一个斜线。

所以,对于像arm/ 这样的字符串,内部分组可以是:

(arm)/
(ar)(m)/
(a)(rm)/
(a)(r)(m)/

随着字符串变长,如果正则表达式第一次尝试匹配失败,它将检查越来越多的组合以尝试匹配。为了避免这种情况,可以通过以下方式实现相同的匹配:

'^(?P<parts>(?:[\w-]+/)*[\w-]+)/$'

因为这个版本只能以一种方式匹配你的目标字符串。

【讨论】:

  • 您建议的正则表达式不会为abc-def/ 之类的输入产生预期结果。
  • @WiktorStribiżew 是正确的 - 我的原始模式匹配 foo/foo/bar/foo/bar/baz/ 等。您的模式仅在至少有 2 个段时匹配(即只有 foo/bar/foo/bar/baz/ 以上三个例子)。
  • 通过用* 替换+ 更正了我的正则表达式。 @Wiktor Stribizew 和 @orokusaki 对我的错字是正确的。
【解决方案2】:

由于您的正则表达式中的catastrophic backtracking,它变得越来越慢:

您可以使用此正则表达式修复灾难性回溯:

^(?P<parts>(?:[\w-]+/)*[\w-]+)/$

根据上面的链接:

避免灾难性回溯的解决方案很简单。嵌套重复运算符时,请绝对确保只有一种方法可以匹配相同的匹配项。

【讨论】:

  • 很高兴知道为什么这会修复灾难性的回溯。
  • @Kenneth K,它修复了回溯,因为新表达式只能匹配他想要的目标一种方式,所以它不会尝试以不同的方式匹配失败的表达式.
  • 我提供的链接对灾难性回溯以及避免它的方法有很好的解释。我从上面的链接中引用 解决方案很简单。嵌套重复运算符时,请绝对确保只有一种方法可以匹配相同的匹配项
  • 根据总是引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。你应该这样做;)
猜你喜欢
  • 1970-01-01
  • 2015-02-11
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 2012-01-26
  • 2011-03-08
  • 1970-01-01
  • 2020-11-27
相关资源
最近更新 更多