什么是正则expression式平衡组?

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

我通读了“ 平衡组定义” ,但是这个解释很难理解,对于我提到的问题我还是很困惑。

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

据我所知,平衡组对于.NET的正则expression式来说是独一无二的。

旁白:重复的小组

首先,你需要知道.NET是(就我所知而言)唯一的正则expression式,它允许你访问单个捕获组的多个捕获(不是在反向引用中,而是在匹配完成之后)。

为了用一个例子来说明这一点,考虑这个模式

(.)+ 

和string"abcd"

在所有其他的正则expression式中,捕获组1只会产生一个结果: d (注意,完全匹配当然是abcd预期的)。 这是因为捕获组的每次新用途都会覆盖之前的捕获。

.NET另一方面记住他们。 它在一个堆栈中这样做。 匹配上面的正则expression式之后

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

你会发现

 m.Groups[1].Captures 

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

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

数字是CaptureCollection的索引。 所以基本上每次再次使用组时,都会将新的捕获压入堆栈。

如果我们使用命名的捕获组,它会变得更有趣。 因为.NET允许重复使用相同的名称,所以我们可以编写一个正则expression式

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

将两个单词捕获到同一个组中。 同样,每次遇到具有特定名称的组时,都会将捕获压入堆栈。 所以应用这个正则expression式来input"foo bar"并检查

 m.Groups["word"].Captures 

我们find两个捕获

 0: "foo" 1: "bar" 

这使我们甚至可以从expression式的不同部分将东西推到单个堆栈上。 但是,这只是.NET能够跟踪此CaptureCollection中列出的多个捕获的function。 但我说,这个集合是一个堆栈 。 那么我们可以从中popup一些东西吗?

input:平衡组

事实certificate我们可以。 如果我们使用像(?<-word>...) ,那么如果子expression式匹配,那么最后的捕获将从堆栈wordpopup。 所以,如果我们改变我们以前的expression式

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

然后第二组将popup第一组的抓图,最后我们会收到一个空的CaptureCollection 。 当然,这个例子是相当无用的。

但是还有一个更多的细节:如果堆栈已经是空的,则组失败(不pipe子模式如何)。 我们可以利用这种行为来计算嵌套级别 – 这就是名称平衡组来自哪里(以及哪里有趣)。 假设我们想要匹配加括号的string。 我们推动堆栈中的每个左括号,并为每个右括号popup一个捕获。 如果我们遇到一个右括号太多,它会尝试popup一个空栈,导致模式失败:

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

所以我们有三个select重复。 第一个select消耗的东西不是括号。 第二个select匹配(同时将它们推入堆栈,第三个select匹配)同时popup堆栈中的元素(如果可能的话)。

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

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

压轴:条件模式

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

 (?(condition)truePattern|falsePattern) 

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

所以在我们上面的模式结束时,我们可以添加类似于(?(Open)failPattern)东西,如果Open Stack不是空的,这会导致整个模式失败。 使模式无条件失败的最简单的事情是(?!) (一个空的负向预测)。 所以我们有最后的模式:

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

请注意,这种条件语法本身没有任何与平衡群体相关的事情,但是有必要利用它们的全部function。

从这里,天空是极限。 许多非常复杂的用法是可能的,并且与其他.NET-Regex特性(比如可变长度lookbehinds)( 我必须自己学习困难的方法 )结合使用时会遇到一些问题。 然而,主要问题总是:使用这些function时,您的代码是否仍然可以维护? 你需要把它logging得很好,并确保每个工作的人都知道这些function。 否则,你可能会变得更好,只需逐字符地逐个string,然后在一个整数中计算嵌套级别。

附录: (?<AB>...)语法是什么?

这个部分的积分去Kobi(更多细节见他的答案)。

现在,使用上述所有方法,我们可以validationstring是否被正确加括号。 但是,如果我们实际上可以获得(嵌套的)所有括号内容的捕获,将会更有用。 当然,我们可以记住在一个单独的未被清空的捕获栈中打开和closures括号,然后根据它们的位置在一个单独的步骤中进行一些子串提取。

但是.NET在这里提供了一个更方便的function:如果我们使用(?<AB>subPattern) ,不仅从栈Bpopup一个捕获,而且popup的B和这个当前组捕获之间的所有内容都被压入堆栈A 因此,如果我们使用这样的组合来closures括号,在从堆栈中popup嵌套层次的同时,我们也可以将这个对的内容推送到另一个堆栈上:

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

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

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

  • 请记住任意多的捕获
  • validation嵌套结构
  • 捕获每个嵌套级别

所有在一个单一的正则expression式。 如果这不令人兴奋…;)

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

除了M. Buettner的出色答案之外,

什么是(?<AB>)语法的处理?

(?<AB>x)(?<-A>(?<B>x)) αA (?<-A>(?<B>x)) αB (?<AB>x)略微不同。 他们导致相同的控制stream* ,但他们捕捉不同。
例如,让我们看一下平衡括号的模式:

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

在比赛结束时,我们有一个平衡的string,但这是我们所有的 – 我们不知道大括号是在哪里 ,因为B栈是空的。 引擎为我们所做的努力已经消失了。
( 例如正则expression式风暴 )

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

让我们在我们的模式中使用它:

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

这将捕获到$Content中的大括号(及其位置)之间的string,对于每一对。
对于string{1 2 {3} {4 5 {6}} 7} ,会有四个捕获:3,6,4 4 5 {6}1 2 {3} {4 5 {6}} 7 -比什么都好,或者} } } }
( 例如 – 单击table标签并查看${Content} ,捕获 )

实际上,它可以在没有任何平衡的情况下使用:( (?<A>).(.(?<Content-A>).)捕获前两个字符,即使它们被组分隔开。
(在这里比较常用的是一个前瞻,但它并不总是规模:它可能会重复你的逻辑。)

(?<AB>)是一个强大的function – 它让你精确控制你的捕捉。 记住,当你试图让你的模式更多。