【问题标题】:More concise/efficient regex to match a string within matching parentheses更简洁/高效的正则表达式来匹配匹配括号内的字符串
【发布时间】:2015-05-15 09:07:49
【问题描述】:

我想匹配字符串,其中foo 出现在select([...]) 中,但前提是可能出现的括号匹配在一起。例如。匹配 select(((foo))) 或 select(x(())(foo(x))()x((y)x)x()) 但不匹配 select((foo) 或 select(x(foo)y()。

我知道,我必须限制嵌套括号的最大数量,我想出了以下正则表达式来解决另外一对括号的问题:

select\((?:
    (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
    foo
    (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
    |
    (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
    \([^()]*foo[^()]*\)
    (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
)\)

这意味着在 select([...]) 内 either 匹配 foo,前后没有括号或一对括号 或 匹配一对括号内的 foo并且前面或后面没有括号或一对括号。

有人对此有更简洁的解决方案吗?

扩展我的正则表达式以解决另外两对括号的问题如下所示:

select\((?:
    (?:[^()]*|[^()]*\((?:[^()]*|[^()]*\([^()]*\)[^()]*)*\)[^()]*)*
    foo
    (?:[^()]*|[^()]*\((?:[^()]*|[^()]*\([^()]*\)[^()]*)*\)[^()]*)*
    |
    (?:[^()]*|[^()]*\((?:[^()]*|[^()]*\([^()]*\)[^()]*)*\)[^()]*)*
    \((?:
        (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
        foo
        (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
        |
        (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
        \([^()]*foo[^()]*\)
        (?:[^()]*|[^()]*\([^()]*\)[^()]*)*
    )\)
    (?:[^()]*|[^()]*\((?:[^()]*|[^()]*\([^()]*\)[^()]*)*\)[^()]*)*
)\)

其中缩进最多的部分基本上是前面的正则表达式,no or one pair of parentheses 部分已扩展为no or one or two pair of parentheses。

我将最后一个正则表达式放在 regex101 上:https://www.regex101.com/r/fJ6cR4/1

问题是这个正则表达式(甚至更多扩展版本)非常耗时,所以我希望有更好的想法。

【问题讨论】:

  • 为什么不使用简单的解析器呢?正则表达式是匹配嵌套语言的错误工具。
  • 您使用的是什么正则表达式实现,它支持哪些功能?
  • 我想在文件中搜索。通常我使用 Notepad++、Eclipse 或 grepWin。 AFAIK 环顾四周的表达式是允许的,但条件是不允许的。但是可能有允许条件正则表达式的搜索工具,所以如果需要,请随意使用条件(我不太了解)。
  • 如果您有兴趣通过:regex101.com/r/nX2jM3/5

标签: regex


【解决方案1】:

您应该做两件事来简化(并加快)您的正则表达式:

  • (?: [^()]* | [^()]*\([^()]*\)[^()]* )* 是catastrophic backtracking 的一个示例。外部的重复组应该只有两种选择:非括号字符序列或括号之间的序列:

    (?: [^()]+ | \([^()]*\) )*
    

    您将不带括号的字符 [^()]* 混合到两个选项中。

  • 与其使用…foo…|…\(foo\)…,不如使用…(?:foo|\(foo\))…,这样您就不必重复冗长的…。

有了这两个,你的小表情就变成了

select\(
(?: [^()]+ | \([^()]*\) )*
(?: foo | \([^()]*foo[^()]*\) )
(?: [^()]+ | \([^()]*\) )*
\)

我将把这些应用到更大的表达式上交给你。

【讨论】:

  • 非常快速且非常有帮助的答案,谢谢!有趣的是,与减少冗长事情的重复相比,修复灾难性回溯部分减少了所需步骤的数量。将冗长的东西从 OR 部分中拉出来是很明显的,显然我没有看到树木的木材;-) 将您建议的扩展版本应用于我的 regex101 示例将步骤数从 25'548 减少到 514!不幸的是,即使尝试使用 Eclipse 搜索的非扩展版本也没有结束,所以我仍然希望有更多的想法/建议。
【解决方案2】:

根据Regular-expressions.info:

如果您想要一个在字符串中找不到任何匹配项的正则表达式 包含不平衡的括号,那么您需要使用subroutine call 而不是递归。

我已尝试根据您的需要调整该网站上的正则表达式:

(?im)^(?![^()]*\(\))[^()\n]*+(\((?>[^()\n]|(?1))*+\)[^()\n]*)++$

见demo

您可以在Sublime Text 中使用此正则表达式,不幸的是,它在 Notepad++ 中不起作用。

要强制要求匹配必须以select(,包含foo并以)结尾,您只需添加一个(?=select\(.*foo.*\)$)一开始就积极向前看:

^(?=select\(.*foo.*\)$)(?![^()]*\(\))[^()\n]*+(\((?>[^()\n]|(?1))*+\)[^()\n]*)++$

见updated demo

.NET 正则表达式解决方案

您可以使用带有 .NET 正则表达式的平衡 + 条件构造(例如,与 grepWin 一起使用):

select\((?>(?<f>foo)|[^()]|(?<o>)\(|(?<-o>)\))*(?(o)(?!))(?(f)|(?!))\)

请参阅regex demo。如您所见,select(((foo))) 和 select(x(())(foo(x))()x((y)x)x()) 匹配,但 select((fo))(没有 foo)和 select((foo)(没有匹配的括号)不匹配。

详情

  • select\( - select( 子字符串
  • (?&gt; - 原子组的开始:
    • (?&lt;f&gt;foo)| - 组“f”:foo 字符串,或
    • [^()]| - 除( 和) 之外的任何字符,或
    • (?&lt;o&gt;)\(| - 一个 ( 字符,以及一个压入“o”组堆栈的空字符串
    • (?&lt;-o&gt;)\)
  • )* - 原子组结束,0 次或多次重复
  • (?(o)(?!)) - 第一个条件构造:如果组“o”堆栈不为空,则匹配失败(嵌套括号检查)
  • (?(f)|(?!)) - 第二个条件构造:如果“f”组堆栈为空,则匹配失败(如果“foo”不存在,当foo 存在时,“f”组堆栈不为空)
  • \) - 一个 ) 字符

【讨论】:

  • 我希望这会有所帮助,因为您在评论中写道可能有搜索工具允许条件正则表达式,所以如果需要,请随意使用条件(我不太了解它们)。
  • 感谢您指出这一点,非常有趣。不幸的是,你的正则表达式太符合我的需要了。匹配必须以select( 开头,包含foo 并以) 结尾。我在这里更新了测试字符串:regex101.com/r/oO7qP4/2 我还尝试调整您的正则表达式以满足这些添加的测试字符串但没有成功:-(随着您对这些高级正则表达式功能的熟练程度,您知道如何调整正则表达式相应地?
  • 我更新了答案,你只需要在开头添加一个积极的前瞻来设置这个约束。
  • 哦,这种前瞻的用法很不错,谢谢!我将使用 Sublime Text 尝试此解决方案。
  • 如果我自己找到答案,我会回到这里。
猜你喜欢
  • 2017-09-15
  • 2023-04-02
  • 1970-01-01
  • 2016-02-13
  • 2023-03-15
  • 2023-03-04
  • 1970-01-01
  • 2011-07-18
  • 1970-01-01
相关资源
最近更新 更多