什么是正则表达式平衡组?


91

我只是在阅读一个有关如何在双花括号内获取数据的问题此问题),然后有人提出了平衡组。我仍然不太清楚它们是什么以及如何使用它们。

我通读了Balancing Group Definition,但是很难理解,而且我对我提到的问题仍然很困惑。

有人可以简单地解释一下平衡组是什么以及它们如何有用吗?


我想知道实际上支持多少个正则表达式。
Mike de Klerk

2
@MikedeKlerk至少.NET Regex引擎支持它。
不是。

Answers:


173

据我所知,平衡组是.NET正则表达式风格所独有的。

除了:重复的组

首先,您需要知道。NET(据我所知)是唯一的正则表达式,可让您访问单个捕获组的多个捕获(不在反向引用中,而是在匹配完成之后)。

为了举例说明,请考虑模式

(.)+

和字符串"abcd"

在所有其他正则表达式中,捕获组1将仅产生一个结果:(d请注意,完全匹配当然会abcd如预期的那样)。这是因为捕获组的每次新使用都会覆盖先前的捕获。

另一方面,.NET会记住它们。它是一堆的。匹配上面的正则表达式之后

Match m = new Regex(@"(.)+").Match("abcd");

你会发现

m.Groups[1].Captures

是,CaptureCollection其元素对应于四个捕获

0: "a"
1: "b"
2: "c"
3: "d"

其中的数字是的索引CaptureCollection。因此,基本上每次再次使用该组时,都会将新的捕获推入堆栈。

如果我们使用命名捕获组,它将变得更加有趣。由于.NET允许重复使用相同的名称,因此我们可以编写如下正则表达式

(?<word>\w+)\W+(?<word>\w+)

将两个单词捕获到同一组中。同样,每次遇到具有特定名称的组时,都会将捕获推送到其堆栈中。因此,将此正则表达式应用于输入"foo bar"和检查

m.Groups["word"].Captures

我们发现两个捕获

0: "foo"
1: "bar"

这使我们甚至可以将表达式的不同部分将内容推入单个堆栈中。但是,这仅仅是.NET的功能,它能够跟踪此列表中列出的多个捕获CaptureCollection。但是我说过,这个集合是一个堆栈。那么我们可以从中弹出 东西吗?

输入:平衡组

事实证明我们可以。如果我们使用像这样的组(?<-word>...),那么word如果子表达式...匹配,则从堆栈中弹出最后一个捕获。因此,如果我们将之前的表达式更改为

(?<word>\w+)\W+(?<-word>\w+)

然后,第二组将弹出第一组的捕获,最后我们将收到一个空白CaptureCollection。当然,这个例子是毫无用处的。

但是,减号语法还有一个细节:如果堆栈已经为空,则组失败(无论其子模式如何)。我们可以利用这种行为来计算嵌套级别-这就是名称平衡组的来源(以及有趣之处)。假设我们要匹配正确括号内的字符串。我们将每个左括号插入堆栈,并为每个右括号弹出一个捕获。如果遇到太多的右括号,它将尝试弹出一个空堆栈并导致模式失败:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

因此,我们在重复中有三种选择。第一种选择消耗所有非括号的内容。第二种选择将(s压入堆栈。第三个替代方案)在从堆栈中弹出元素时匹配s(如果可能!)。

注意:为澄清起见,我们仅检查没有不匹配的括号!这意味着完全不包含括号的字符串匹配,因为它们在语法上仍然有效(在某些语法中,您需要括号进行匹配)。如果您要确保至少有一组括号,只需在后面添加一个超前(?=.*[(])^

但是,这种模式并不完美(或完全正确)。

结局:条件模式

还有一个要注意的地方:这不能确保在字符串末尾堆栈为空(因此(foo(bar)将是有效的)。.NET(以及许多其他版本)还有一个可以帮助我们解决问题的结构:条件模式。通用语法是

(?(condition)truePattern|falsePattern)

其中的falsePattern是可选的-如果省略,则错误情况将始终匹配。条件可以是模式,也可以是捕获组的名称。我将在这里集中讨论后一种情况。如果它是捕获组的名称,则truePattern仅当该特定组的捕获堆栈不为空时才使用。也就是说,类似的条件模式(?(name)yes|no)读取为“如果name已匹配并捕获了某些内容(仍在堆栈上,则使用模式,yes否则使用模式no”)。

因此,在上述模式的结尾(?(Open)failPattern),如果Open-stack不为空,我们可以添加类似内容,从而导致整个模式失败。使模式无条件失败的最简单方法是(?!)(空的负向超前)。因此,我们有最终的模式:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

请注意,这种条件语法本身与平衡组无关,但是有必要利用它们的全部功能。

从这里开始,天空才是极限。可能有许多非常复杂的用途,并且与其他.NET-Regex功能(例如变长后视功能)结合使用时,有些陷阱(我必须自己学习很难的方法)。但是,主要问题始终是:使用这些功能时,代码是否仍可维护?您需要很好地记录它,并确保使用它的每个人也都知道这些功能。否则,您可能会更好,只需手动逐个字符地移动字符串并以整数形式计算嵌套级别。

附录:(?<A-B>...)语法是什么?

这部分的功劳归Kobi(有关更多详细信息,请参见下面的答案)。

现在,通过以上所有操作,我们可以验证字符串是否已正确括在括号中。但是,如果我们实际上可以获取(嵌套的)所有这些括号内容的捕获,它将有用得多。当然,我们可以记住在未清空的单独捕获堆栈中打开和关闭括号,然后根据它们在单独步骤中的位置进行一些子字符串提取。

但是.NET在这里提供了另一个便利功能:如果使用(?<A-B>subPattern),不仅从堆栈B弹出捕获,而且从弹出捕获到B当前组之间的所有内容都被压入堆栈A。因此,如果我们使用这样的组作为右括号,则在从堆栈中弹出嵌套级别时,我们还可以将该对的内容压入另一个堆栈中:

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

Kobi 在回答中提供了此现场演示

因此,将所有这些东西放在一起,我们可以:

  • 任意记住多次捕获
  • 验证嵌套结构
  • 捕获每个嵌套级别

全部在单个正则表达式中。如果那不令人兴奋...;)

当我第一次了解它们时,发现一些有用的资源:


6
此答案已添加到“高级Regex-Fu”下的“ 堆栈溢出正则表达式” FAQ中
aliteralmind 2014年

39

只是Buettner出色回答的一小部分:

(?<A-B>)语法如何处理?

(?<A-B>x)与稍有不同(?<-A>(?<B>x))。它们导致相同的控制流*,但捕获方式不同。
例如,让我们看一下平衡括号的模式:

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

在比赛结束时,我们确实有一个平衡的琴弦,但这就是我们所拥有的-我们不知道花括号在哪里,因为B堆栈是空的。引擎为我们所做的辛苦工作已经消失了。
以Regex Storm为例

(?<A-B>x)是解决该问题的方法。怎么样?它不会捕获x$A:它捕获先前捕获到B的当前位置之间的内容。

让我们在模式中使用它:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

$Content对于沿途的每一对,这将捕获到括号(及其位置)之间的字符串中。
对于字符串{1 2 {3} {4 5 {6}} 7}有好多有四个捕获:364 5 {6},和1 2 {3} {4 5 {6}} 7-大大优于没有} } } }
示例-单击table选项卡并查看${Content},捕获

实际上,可以完全不平衡地使用它:(?<A>).(.(?<Content-A>).)捕获前两个字符,即使它们被组分开。
(提前查找在这里更常用,但并不总是可以扩展:它可能会重复您的逻辑。)

(?<A-B>)是一项强大的功能-它使您可以精确控制拍摄内容。当您尝试从模式中获得更多收益时,请记住这一点。


@FYI,在这个问题的新答案中继续讨论您不喜欢问题。:)
zx81 2014年

我试图找出一种方法来执行平衡的大括号正则表达式检查,并在字符串中转义大括号。例如,以下代码将通过:公共类Foo {private const char BAR ='{'; 私人字串_qux =“ {{{”; }有人这样做吗?
安德森先生

@MrAnderson-您只需要|'[^']*'在正确的位置添加:example。如果您还需要转义字符,请参见此处的示例:(用于匹配C#字符串文字的正则表达式)[ stackoverflow.com/a/4953878/7586]
科比
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.