我刚刚阅读了一个关于如何在双花括号(this question)中获取数据的问题,然后有人提出了平衡组。我还不太确定它们是什么以及如何使用它们。
我通读了Balancing Group Definition,但解释很难理解,而且我对我提到的问题仍感到困惑。
有人可以简单地解释什么是平衡组以及它们如何有用吗?
答案 0 :(得分:164)
据我所知,平衡组是.NET正则表达式的独特之处。
首先,你需要知道.NET是(据我所知)唯一的正则表达式,它允许你访问单个捕获组的多个捕获(不是在反向引用中,而是在匹配完成后)。 / p>
为了举例说明,请考虑模式
(.)+
和字符串"abcd"
。
在所有其他正则表达式中,捕获组1
只会产生一个结果:d
(注意,完全匹配当然是abcd
正如预期的那样)。这是因为捕获组的每次新用法都会覆盖先前的捕获。
Match m = new Regex(@"(.)+").Match("abcd");
你会发现
m.Groups[1].Captures
是CaptureCollection
,其元素对应于四个捕获
0: "a"
1: "b"
2: "c"
3: "d"
其中number是CaptureCollection
的索引。因此,基本上每次再次使用该组时,都会将新的捕获推送到堆栈中。
如果我们使用命名捕获组,它会变得更有趣。因为.NET允许重复使用相同的名称,所以我们可以写一个像
这样的正则表达式(?<word>\w+)\W+(?<word>\w+)
将两个单词捕获到同一组中。同样,每次遇到具有特定名称的组时,捕获都会被推送到其堆栈中。所以将此正则表达式应用于输入"foo bar"
并检查
m.Groups["word"].Captures
我们找到两个捕获
0: "foo"
1: "bar"
这使我们甚至可以从表达式的不同部分将事物推送到单个堆栈中。但是,这只是.NET能够跟踪此CaptureCollection
中列出的多个捕获的功能。但我说,这个集合是堆栈。那么我们可以 pop 吗?
事实证明我们可以。如果我们使用像(?<-word>...)
这样的组,那么如果子表达式word
匹配,则会从堆栈...
中弹出最后一次捕获。因此,如果我们将之前的表达式更改为
(?<word>\w+)\W+(?<-word>\w+)
然后第二组将弹出第一组的捕获,最后我们将收到一个空的CaptureCollection
。当然,这个例子很没用。
但是还有一个减去语法的细节:如果堆栈已经为空,则组失败(无论其子模式如何)。我们可以利用这种行为来计算嵌套级别 - 这就是名称平衡组来自的地方(以及它变得有趣的地方)。假设我们想匹配正确括号的字符串。我们在堆栈上推动每个左括号,并为每个右括号弹出一个捕获。如果我们遇到一个关闭括号太多,它将尝试弹出一个空堆栈并导致该模式失败:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
所以我们在重复中有三种选择。第一种选择消耗所有不是括号的东西。第二种方法匹配(
,同时将它们推入堆栈。第三个替代方法匹配)
s,同时从堆栈中弹出元素(如果可能!)。
注意:为了澄清,我们只检查没有不匹配的括号!这意味着所有中不包含括号的字符串将匹配,因为它们在语法上仍然有效(在某些语法中,您需要使用括号匹配)。如果您想确保至少有一组括号,只需在(?=.*[(])
之后添加一个前瞻^
。
但这种模式并不完美(或完全正确)。
还有一个问题:这不能确保字符串末尾的堆栈为空(因此(foo(bar)
有效)。 .NET(以及许多其他版本)还有一个结构可以帮助我们:条件模式。一般语法是
(?(condition)truePattern|falsePattern)
其中falsePattern
是可选的 - 如果省略,则false-case将始终匹配。条件可以是模式,也可以是捕获组的名称。我会在这里关注后一种情况。如果它是捕获组的名称,则当且仅当该特定组的捕获堆栈不为空时才使用truePattern
。也就是说,像(?(name)yes|no)
这样的条件模式读取&#34;如果name
匹配并捕获了某些东西(仍在堆栈中),则使用模式yes
否则使用模式{{1 }}&#34;
因此,在我们上面的模式结束时,如果no
- 堆栈不为空,我们可以添加类似(?(Open)failPattern)
的内容,这会导致整个模式失败。使模式无条件失败的最简单方法是Open
(空的负向前瞻)。所以我们有最终模式:
(?!)
请注意,此条件语法本身与平衡组没有任何关系,但它必须充分利用它们。
从这里开始,天空就是极限。许多非常复杂的用途是可能的,当与其他.NET-Regex功能(如可变长度的lookbehinds(which I had to learn the hard way myself))结合使用时,会有一些问题。但问题始终是:使用这些功能时,您的代码是否仍然可维护?您需要非常好地记录它,并确保每个使用它的人都知道这些功能。否则你可能会更好,只需逐个字符地手动操作字符串并计算整数中的嵌套级别。
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
语法有什么用?此部分的学分转到Kobi(有关详细信息,请参阅下面的答案)。
现在有了上述所有内容,我们可以验证字符串是否正确括号。但是,如果我们能够实际获得所有这些括号的(嵌套)捕获,那将会更有用。内容。当然,我们可以记住在没有清空的单独捕获堆栈中打开和关闭括号,然后在单独的步骤中根据它们的位置进行一些子串提取。
但.NET提供了一个更方便的功能:如果我们使用(?<A-B>...)
,不仅会从堆栈(?<A-B>subPattern)
中弹出捕获,而且还会弹出捕获B
和此当前组被推送到堆栈B
。因此,如果我们使用这样的组作为结束括号,同时从我们的堆栈中弹出嵌套级别,我们也可以将该对的内容推送到另一个堆栈:
A
Kobi在他的回答中提供了这个Live-Demo
所以把所有这些东西放在一起我们可以:
全部在一个正则表达式中。如果这不令人兴奋......;)
我第一次了解它们时发现了一些有用的资源:
答案 1 :(得分:38)
只是M. Buettner的优秀答案的一小部分补充:
(?<A-B>)
语法的处理方式是什么? (?<A-B>x)
与(?<-A>(?<B>x))
略有不同。它们导致相同的控制流 * ,但它们捕获的方式不同
例如,让我们看一下平衡大括号的模式:
(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))
在比赛结束时我们有一个平衡的字符串,但这就是我们所拥有的 - 我们不知道其中大括号是因为B
堆栈是空的。引擎为我们所做的艰苦工作已经消失
<子>(example on Regex Storm)子>
(?<A-B>x)
是该问题的解决方案。怎么样?它不将x
捕获到$A
中:它捕获上一次捕获B
与当前位置之间的内容。
让我们在我们的模式中使用它:
(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))
对于沿途的每一对,这将捕获$Content
大括号(及其位置)之间的字符串。
对于字符串{1 2 {3} {4 5 {6}} 7}
,有四个捕获:3
,6
,4 5 {6}
和1 2 {3} {4 5 {6}} 7
- 比 nothing 或}
}
}
}
<子>(example - click the table
tab and look at ${Content}
, captures)子>
事实上,它可以在没有平衡的情况下使用:(?<A>).(.(?<Content-A>).)
捕获前两个字符,即使它们被组分开。
(这里更常用的是先行,但它并不总是缩放:它可能会复制你的逻辑。)
(?<A-B>)
是一个强大的功能 - 它可以让您完全控制您的捕获。当你想要从你的模式中获得更多时,请记住这一点。