一尘不染

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

c#

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

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

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


阅读 683

收藏
2020-05-19

共1个答案

一尘不染

据我所知,平衡组是.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
在他的回答中提供了此现场演示

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

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

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

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

2020-05-19