【问题标题】:What are regular expression Balancing Groups?什么是正则表达式平衡组?
【发布时间】:2013-06-04 22:09:43
【问题描述】:

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

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

谁能简单解释一下什么是平衡组以及它们有什么用处?

【问题讨论】:

  • 我想知道这实际上支持了多少个正则表达式工程师。
  • @MikedeKlerk 至少在 .NET Regex 引擎中受支持。

标签: c# .net regex balancing-groups


【解决方案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 中列出的多个捕获的功能。但我说过,这个集合是一个堆栈。那么我们可以从中pop 东西吗?

输入:平衡组

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

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

然后第二组会弹出第一组的捕获,最后我们会收到一个空的CaptureCollection。当然,这个例子没什么用。

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

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

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

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

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

结局:条件模式

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

(?(condition)truePattern|falsePattern)

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

所以在上述模式的末尾,如果Open-stack 不为​​空,我们可以添加类似(?(Open)failPattern) 的内容,这会导致整个模式失败。使模式无条件失败的最简单方法是(?!)(一个空的否定前瞻)。所以我们有了最终的模式:

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

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

从这里开始,天空就是极限。许多非常复杂的用途是可能的,并且在与其他 .NET-Regex 功能(如可变长度后视 (which I had to learn the hard way myself) 结合使用时存在一些问题。然而,主要问题始终是:使用这些功能时,您的代码是否仍然可维护?你需要很好地记录它,并确保每个使用它的人都知道这些特性。否则你可能会更好,只需手动逐个字符地遍历字符串并以整数计算嵌套级别。

附录:(?&lt;A-B&gt;...) 语法是什么?

这部分的功劳归科比所有(有关详细信息,请参阅下面的答案)。

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

但是.NET在这里提供了一个更方便的特性:如果我们使用(?&lt;A-B&gt;subPattern),不仅会从堆栈B中弹出一个捕获,而且还会从弹出的B捕获和当前组之间的所有内容推送到堆栈A。因此,如果我们使用这样的组作为右括号,同时从堆栈中弹出嵌套级别,我们还可以将对的内容推送到另一个堆栈:

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

Kobi 在他的回答中提供了这个Live-Demo

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

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

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

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

【讨论】:

【解决方案2】:

M. Buettner 的出色回答只是一个小小的补充:

(?&lt;A-B&gt;) 语法是怎么回事?

(?&lt;A-B&gt;x)(?&lt;-A&gt;(?&lt;B&gt;x)) 略有不同。它们产生相同的控制流*,但它们捕获不同。
例如,让我们看一下平衡大括号的模式:

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

在比赛结束时,我们确实有一个平衡的字符串,但这就是我们所拥有的 - 我们不知道大括号在哪里,因为B 堆栈是空的。引擎为我们所做的辛勤工作已经过去了。
(example on Regex Storm)

(?&lt;A-B&gt;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 - 比 nothing} } @987654345 要好得多@}.
(example - click the table tab and look at ${Content}, captures)

事实上,它可以完全不平衡地使用:(?&lt;A&gt;).(.(?&lt;Content-A&gt;).) 捕获前两个字符,即使它们被组分隔。
(前瞻在这里更常用,但它并不总是可扩展的:它可能会重复您的逻辑。)

(?&lt;A-B&gt;) 是一个强大的功能 - 它让您精确 控制您的捕获。当您试图从您的模式中获得更多收益时,请记住这一点。

【讨论】:

  • @FYI,继续从question you didn't like 讨论这个问题的新答案。 :)
  • 我试图找出一种方法来执行平衡大括号正则表达式检查,并在字符串中转义大括号。例如。以下代码将通过: public class Foo { private const char BAR = '{';私有字符串 _qux = "{{{"; } 有人做过吗?
  • @MrAnderson - 您只需在正确的位置添加|'[^']*'example。如果您还需要转义字符,这里有一个示例:(用于匹配 C# 字符串文字的正则表达式)[stackoverflow.com/a/4953878/7586].
猜你喜欢
  • 1970-01-01
  • 2016-12-07
  • 2015-11-04
  • 2015-11-13
  • 2019-01-17
  • 2012-07-30
  • 1970-01-01
  • 1970-01-01
  • 2015-06-09
相关资源
最近更新 更多