是否可以在不使用递归或平衡组的情况下将嵌套括号与正则表达式匹配?

时间:2017-11-07 15:49:57

标签: java regex

StackOverflow鼓励自己回答问题,所以我决定创建这篇文章来分享我最近发现的东西。

问题:在正则表达式中匹配任意嵌套的括号组,例如Java' s java.util.regex ,既不支持递归也不支持平衡组。即,匹配3个外部组:

  

(F(i(r(s)t)))((S)(e)((c)(o))(n)d)((((((((Third)))))) )

这个练习纯粹是学术性的,因为我们都知道正则表达式不应该被用来匹配这些东西,就像Q-tips不应该被用来清理耳朵一样。

2 个答案:

答案 0 :(得分:30)

的确!可以使用前向引用:

(?=\()(?:(?=.*?\((?!.*?\1)(.*\)(?!.*\2).*))(?=.*?\)(?!.*?\2)(.*)).)+?.*?(?=\1)[^(]*(?=\2$)

Proof

Et voila ;它就是。在那里,从头到尾匹配一组完整的嵌套括号。每次匹配必须捕获并保存两个子串;这对你没用。只关注主要比赛的结果。

不,深度没有限制。不,那里没有隐藏的递归结构。只是简单的'lookarounds,带有前瞻性的参考。如果你的味道不支持前向引用(我在看你,JavaScript),那我很抱歉。我真的是。我希望我能帮助你,但我不是一个奇迹般的奇迹工作者。

这很棒,但我想要匹配内部团体!

好的,这是交易。我们能够匹配这些外部组的原因是因为它们不重叠。一旦我们想要的比赛开始重叠,我们必须稍微调整我们的策略。我们仍然可以检查主题是否正确平衡括号组。但是,我们需要使用捕获组来保存它们,而不是直接匹配它们:

(?=\()(?=((?:(?=.*?\((?!.*?\2)(.*\)(?!.*\3).*))(?=.*?\)(?!.*?\3)(.*)).)+?.*?(?=\2)[^(]*(?=\3$))) 

与上一个表达式完全相同,除了我将它的大部分包装在一个预测中以避免消耗字符,添加一个捕获组,并调整后向引用索引以便它们与新朋友一起玩得很好。现在表达式匹配在下一个括号组之前的位置,并且感兴趣的子字符串保存为\ 1.

那么......这到底是怎么回事?

我很高兴你问。一般方法非常简单:一次迭代一个字符,同时匹配下一次出现的'('和')',在每种情况下捕获字符串的其余部分,以便建立从中继续搜索的位置。下一次迭代。让我一块一块地分解:

breakdown of regex

结论

所以,你有它。使用前向引用和标准(扩展)正则表达式功能匹配平衡嵌套结构的方法 - 无递归或平衡组。它效率不高,而且肯定不漂亮,但它是可能的。而且以前从未做过。对我来说,这非常令人兴奋。

我知道很多人使用正则表达式来完成并帮助其他用户完成更简单和更实际的任务,但是如果有人与我分享我对用正则表达式推动可能性极限的兴奋那么我会喜欢听到你的消息。如果有兴趣,我还有其他类似材料可以发布。

答案 1 :(得分:7)

输入更正

首先,您的输入不正确,因为有一个额外的括号(如下所示)

(F(i(r(s)t))) ((S)(e)((c)(o))n)d) (((((((Third)))))))
                                ^

对包括或排除附加括号的进行适当修改,最终可能会出现以下字符串之一:

删除了额外的括号

(F(i(r(s)t))) ((S)(e)((c)(o))n)d (((((((Third)))))))
                                ^

添加了附加括号以匹配额外的右括号

((F(i(r(s)t))) ((S)(e)((c)(o))n)d) (((((((Third)))))))
^

正则表达式功能

其次,这真的只能在包含递归功能的正则表达式中实现,因为任何其他方法都不能正确匹配开/关括号(如OP的解决方案中所见,它匹配来自错误输入的额外括号如上所述)。

这意味着对于当前支持递归(Java,Python,JavaScript等)的正则表达式,正则表达式中的递归(或尝试模拟递归)不< / strong>可能。

输入

考虑到原始输入实际上是无效的,我们将使用以下输入进行测试。

(F(i(r(s)t))) ((S)(e)((c)(o))n)d) (((((((Third)))))))
(F(i(r(s)t))) ((S)(e)((c)(o))n)d (((((((Third)))))))
((F(i(r(s)t))) ((S)(e)((c)(o))n)d) (((((((Third)))))))

针对这些输入进行测试应产生以下结果:

  1. 无效(不匹配)
  2. 有效(匹配)
  3. 有效(匹配)
  4. 代码

    有多种匹配嵌套组的方法。下面提供的解决方案都依赖于包含递归功能的正则表达式风格(例如PCRE)。

    See regex in use here

    使用DEFINE块

    (?(DEFINE)
      (?<value>[^()\r\n]+)
      (?<groupVal>(?&group)|(?&value))
      (?<group>(?&value)*\((?&groupVal)\)(?&groupVal)*)
    )
    ^(?&group)$
    

    注意:此正则表达式使用标记gmx

    没有DEFINE块

    See regex in use here

    ^(?<group>
      (?<value>[^()\r\n]+)*
      \((?<groupVal>(?&group)|(?&value))\)
      (?&groupVal)*
    )$
    

    注意:此正则表达式使用标记gmx

    没有x修饰符(单行)

    See regex in use here

    ^(?<group>(?<value>[^()\r\n]+)*\((?<groupVal>(?&group)|(?&value))\)(?&groupVal)*)$
    

    没有命名(组和参考)

    See regex in use here

    ^(([^()\r\n]+)*\(((?1)|(?2))\)(?3)*)$
    

    注意:这是我能想到的最简短的方法。

    解释

    我将解释最后一个正则表达式,因为它是上面所有其他正则表达式的简化和最小的例子。

    • ^在行首处断言位置
    • (([^()\r\n]+)*\(((?1)|(?2))\)(?3)*)将以下内容捕获到捕获组 1
      • ([^()\r\n]+)*将以下内容捕获到捕获组 2
        • [^()\r\n]+匹配集()\r\n中不存在的任何字符
      • \(字面上匹配左/左括号字符(
      • ((?1)|(?2))将以下任意一项捕获到捕获组 3
        • (?1)递归第一个子模式(1)
        • (?2)递归第二个子模式(2)
      • \)匹配右/右括号字符)字面意思
      • (?3)*递归第三个子模式(3)
    • $断言行尾的位置