提问者:小点点

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


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

我通读了平衡组的定义,但解释很难理解,我仍然对我提到的问题感到困惑。

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


共2个答案

匿名用户

据我所知,平衡小组是独一无二的。NET的regex风格。

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

为了举例说明这一点,考虑模式

(.)+

和字符串"abcd"

在所有其他regex风格中,捕获组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>\w+)\W+(?<-word>\w+)

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

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

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

所以我们有三个重复的选择。第一种选择消耗了所有不是括号的东西。第二个选项匹配s,同时将它们推送到堆栈上。第三个选项匹配s,同时从堆栈中弹出元素(如果可能的话!)。

注意:为了澄清,我们只是检查没有不匹配的括号!这意味着不包含括号的字符串将匹配,因为它们在语法上仍然有效(在某些语法中,您需要括号来匹配)。如果您想确保至少有一组括号,只需添加一个lookfirst(?=.*[(])就在^之后。

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

还有一个问题:这不能确保堆栈在字符串末尾是空的(因此(foo(bar)将是有效的)。NET(和许多其他风格)还有一个结构可以帮助我们:条件模式。一般语法是

(?(condition)truePattern|falsePattern)

其中FalsePattern是可选的-如果省略,false大小写将始终匹配。条件可以是模式,也可以是捕获组的名称。我将在这里重点讨论后一种情况。如果它是捕获组的名称,则当且仅当该特定组的捕获堆栈不为空时才使用truePattern。也就是说,像(?(name)yes|no)读取"如果name匹配并捕获了某些东西(仍然在堆栈上),则使用模式yes否则使用模式no"。

因此,在上述模式的末尾,我们可以添加类似于(?如果打开堆栈不是空的,则导致整个模式失败。使模式无条件失败的最简单的事情是(?!)(一个空的否定前瞻)。所以我们有了最后的模式:

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

请注意,此条件语法本身与平衡组无关,但有必要充分利用它们的功能。

从这里开始,天空就是极限。许多非常复杂的用途是可能的,当与其他用途结合使用时,会有一些陷阱。NET-Regex功能,如可变长度lookbehinds(这是我自己学习的艰难方式)。然而,主要问题始终是:当使用这些功能时,您的代码仍然可以维护吗?你需要很好地记录它,并确保每个从事它的人也意识到这些特性。否则,您可能会更好,只需逐个字符手动遍历字符串,并以整数计算嵌套级别。

这部分的学分归Kobi所有(更多细节见下面的答案)。

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

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

Kobi在他的回答中提供了这个现场演示

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

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

都在一个正则表达式中。如果这不令人兴奋的话……;)

当我第一次了解这些资源时,我发现它们很有帮助:

  • http://blog.stevenlevithan.com/archives/balancing-groups

匿名用户

只是对M.Buettner极好的回答的一点补充:

(?

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

在匹配结束时,我们确实有一个平衡的字符串,但这就是我们所拥有的全部——我们不知道大括号在哪里,因为B堆栈是空的。
(Regex Storm上的例子)

(?

让我们在模式中使用它:

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

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

事实上,它可以在完全不平衡的情况下使用:(?

(?