代码之家  ›  专栏  ›  技术社区  ›  jaytea

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

  •  28
  • jaytea  · 技术社区  · 9 年前

    :匹配正则表达式风格中任意嵌套的一组括号,例如Java的 Java语言util。正则表达式 既不支持递归也不支持平衡组。一、 e.匹配以下三个外部组:

    (F(i(r(s)t))((s)(e)((c)(o))(n)d)(((((((((第三\\4070;)))))

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

    StackOverflow鼓励自我回答问题,所以我决定创建此帖子来分享我最近发现的一些东西。

    2 回复  |  直到 6 年前
        1
  •  46
  •   Zoe - Save the data dump 张群峰    5 年前

    的确可以使用正向引用:

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

    Proof

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

    不,深度没有限制。不,这里没有隐藏的递归构造。只是简单的环顾四周,带有一点向前的参照。如果你的风格不支持转发引用(我在看你,JavaScript),那么我很抱歉。我真的是。我希望我能帮助你,但我不是一个奇怪的奇迹工作者。

    这很好,但我也想匹配内部群体!

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

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

    与前一个表达式完全相同,只是我将其大部分包装在了一个前瞻中,以避免消耗角色,添加了一个捕获组,并调整了反向引用索引,以便它们与新朋友玩得很好。现在表达式在下一个括号组之前的位置匹配,感兴趣的子串保存为1。

    所以这到底是怎么回事?

    很高兴你问我。一般的方法很简单:一次遍历一个字符,同时匹配下一个出现的“('和')”,在每种情况下捕获字符串的其余部分,以便确定在下一次迭代中恢复搜索的位置。让我把它一块一块地分解:

    组成部分 描述
    (?=\() 在做任何艰苦的工作之前,确保“(”紧跟其后。
    (?: 用于遍历字符串的组的开头,因此以下lookaheads重复匹配。
    句柄'(' (?= 这个前瞻性处理查找下一个“(”。
    .*?\((?!.*?\1) \1 . 下面,你会看到 \1 由匹配的最后一个“(”后面的字符串的整个部分填充。因此 (?!.*?\1) 确保不再匹配相同的“(”
    (.*\)(?!.*\2).*) 填满 \1 用剩下的绳子。同时,检查是否至少再次出现“)”。这是一个PCRE创可贴,用于克服在lookaheads中捕获组的错误。
    )
    手柄')' (?= 这种前瞻性处理的是找到下一个“)”
    .*?\)(?!.*?\2) \2 . 与前面的“(”match)一样,这会强制匹配以前没有匹配过的“)”。
    (.*) 用字符串的其余部分填充2。以上。上面提到的bug在这里不适用,所以一个简单的表达式就足够了。
    )
    .
    )+? 尽可能少地匹配,直到找到一个平衡的组。这通过以下检查进行验证
    最终验证 .*?(?=\1) 匹配并包括找到的最后一个“(”。
    [^(]*(?=\2$) 然后进行匹配,直到找到最后一个“)”的位置,确保我们不会遇到另一个“(”(这意味着一个不平衡的组)。

    结论

    好了,给你。一种使用正向引用和标准(扩展)正则表达式特性匹配平衡嵌套结构的方法-无递归或平衡组。它效率不高,当然也不好看,但这是可能的。这是以前从未做过的。对我来说,这是非常令人兴奋的。

    我知道你们很多人都使用正则表达式来完成并帮助其他用户完成更简单、更实用的任务,但如果有人和我一样对正则表达式突破可能性极限感到兴奋,那么我很想听听你们的意见。如果有兴趣,我有其他类似的材料张贴。

        2
  •  6
  •   ctwheels    9 年前

    简明的

    输入校正

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

    (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等),正则表达式中的递归(或尝试模仿递归)是 不 可能的


    输入

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

    (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. 有效 (匹配)

    密码

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

    See regex in use here

    使用定义块

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

    笔记 :此正则表达式使用标志 gmx

    无定义块

    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)*
    • $ 在行尾断言位置