使用 RegEx 平衡匹配括号

Posted

技术标签:

【中文标题】使用 RegEx 平衡匹配括号【英文标题】:Using RegEx to balance match parenthesis 【发布时间】:2011-12-15 10:43:09 【问题描述】:

我正在尝试创建一个 .NET RegEx 表达式,以正确平衡我的括号。我有以下正则表达式:

func([a-zA-Z_][a-zA-Z0-9_]*)\(.*\)

我要匹配的字符串是这样的:

"test -> funcPow((3),2) * (9+1)"

应该发生的是正则表达式应该匹配从funcPow 到第二个右括号的所有内容。它应该在第二个右括号之后停止。相反,它一直匹配到最后一个右括号。正则表达式返回这个:

"funcPow((3),2) * (9+1)"

它应该返回这个:

"funcPow((3),2)"

对此的任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

正则表达式绝对可以做到平衡括号匹配。这可能很棘手,并且需要一些更高级的正则表达式功能,但并不难。

例子:

var r = new Regex(@"
    func([a-zA-Z_][a-zA-Z0-9_]*) # The func name

    \(                      # First '('
        (?:                 
        [^()]               # Match all non-braces
        |
        (?<open> \( )       # Match '(', and capture into 'open'
        |
        (?<-open> \) )      # Match ')', and delete the 'open' capture
        )+
        (?(open)(?!))       # Fails if 'open' stack isn't empty!

    \)                      # Last ')'
", RegexOptions.IgnorePatternWhitespace);

平衡匹配组有几个功能,但在此示例中,我们仅使用捕获删除功能。 (?&lt;-open&gt; \) ) 行将匹配 ) 并删除之前的“打开”捕获。

最棘手的一行是(?(open)(?!)),所以让我解释一下。 (?(open) 是一个条件表达式,仅在存在“打开”捕获时才匹配。 (?!) 是一个总是失败的否定表达式。因此,(?(open)(?!)) 表示“如果有打开的捕获,则失败”。

Microsoft's documentation 也很有帮助。

【讨论】:

我更改了 [^()]* # Match all non-braces 行,所以它匹配 () 里面什么都没有【参考方案2】:

使用平衡组,它是:

Regex rx = new Regex(@"func([a-zA-Z_][a-zA-Z0-9_]*)\(((?<BR>\()|(?<-BR>\))|[^()]*)+\)");

var match = rx.Match("funcPow((3),2) * (9+1)");

var str = match.Value; // funcPow((3),2)

(?&lt;BR&gt;\()|(?&lt;-BR&gt;\)) 是一个Balancing Group(我用来命名的BRBrackets)。这样更清楚(?&lt;BR&gt;\()|(?&lt;-BR&gt;\)) 也许,让\(\) 更“明显”。

如果你真的很讨厌自己(以及世界/你的同事)使用这些东西,我建议使用 RegexOptions.IgnorePatternWhitespace 并在任何地方“洒”空白 :-)

【讨论】:

我认为你错过了最后一个关键部分,(?(BR)(?!)) @ScottRippey 没有。) 结束后还有其他表达式。 OP问题非常精确。他想要funcsomething(),而不是解析整个表达式。所以我找到的第一个“不平衡”括号是我的子表达式的右括号。 funcPow((3),2) * (9+1) -&gt; funcPow((3),2) 哦,我意识到(?(BR)(?!)) 只是为了确保左大括号有一个右大括号。微软网站:“最后的子表达式 (?(Open)(?!)) 表示输入字符串中的嵌套结构是否正确平衡” 这是一个很好的讨论 :-) 我们想法一致。太像了现在我必须摧毁你。对不起。 只是为了记录,包括(?(BR)(?!))和不包括它之间的区别在于,没有它,表达式将匹配直到并包括最后一个右括号如果没有 enough 右括号。有了它,整个表达式将不匹配【参考方案3】:

正则表达式仅适用于 Regular Languages。这意味着正则表达式可以找到“a 和 b 的任意组合”之类的东西。(abbabbabaaa 等)但是他们找不到“n a's, one b's , n a's".(a^n b a^n) 正则表达式不能保证第一组a匹配第二组a。

因此,它们无法匹配相同数量的左括号和右括号。编写一个一次遍历字符串一个字符的函数很容易。有两个计数器,一个用于打开paren,一个用于关闭。如果 opening_paren_count != closing_parent_count 返回 false,则在遍历字符串时递增指针。

【讨论】:

可能是这样,但正则表达式几乎可以用于任何类型的文本,只要您了解它们的局限性。递归/平衡模式很难看,而且 (IMO) 很少值得努力,但它们受到许多正则表达式风格的支持。【参考方案4】:
func[a-zA-Z0-9_]*\((([^()])|(\([^()]*\)))*\)

您可以使用它,但如果您使用 .NET,可能会有更好的选择。

这部分你已经知道了:

 func[a-zA-Z0-9_]*\( --weird part-- \)

--weird 部分-- 部分只是意味着; ( 允许任何字符 .| 任何部分 (.*) 存在任意多次 )*。唯一的问题是,你不能匹配任何字符.,你必须使用[^()]来排除括号。

(([^()])|(\([^()]*\)))*

【讨论】:

您应该指定这只适用于一层嵌套。 @ScottRippey:如果该函数中有一个函数,它仍然有效。该|条件处理。你能举一个这个正则表达式会提供错误匹配的例子吗? 它工作正常,并且完全按照 OP 的要求执行,所以这是一个很好的答案。但是,它被硬编码为仅匹配一级嵌套,因此无法匹配:func(a(b(c)d)e)。目前尚不清楚 OP 是否需要这个。

以上是关于使用 RegEx 平衡匹配括号的主要内容,如果未能解决你的问题,请参考以下文章

括号匹配

Boost:regex_search - 括号之间的匹配字符串

贪心算法(10):括号的平衡配对问题

Python regex - 忽略括号作为索引?

javascript regex - 字符类中的括号和括号

正则表达式:小括号中括号大括号的区别