我只是在阅读一个有关如何在双花括号内获取数据的问题(此问题),然后有人提出了平衡组。我仍然不太清楚它们是什么以及如何使用它们。
我通读了Balancing Group Definition,但是很难理解,而且我对我提到的问题仍然很困惑。
有人可以简单地解释一下平衡组是什么以及它们如何有用吗?
Answers:
据我所知,平衡组是.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 在回答中提供了此现场演示
因此,将所有这些东西放在一起,我们可以:
全部在单个正则表达式中。如果那不令人兴奋...;)
当我第一次了解它们时,发现一些有用的资源:
只是Buettner出色回答的一小部分:
(?<A-B>)
语法如何处理?(?<A-B>x)
与稍有不同(?<-A>(?<B>x))
。它们导致相同的控制流*,但捕获方式不同。
例如,让我们看一下平衡括号的模式:
(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))
在比赛结束时,我们确实有一个平衡的琴弦,但这就是我们所拥有的-我们不知道花括号在哪里,因为B
堆栈是空的。引擎为我们所做的辛苦工作已经消失了。
(以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
-大大优于没有或}
}
}
}
。
(示例-单击table
选项卡并查看${Content}
,捕获)
实际上,可以完全不平衡地使用它:(?<A>).(.(?<Content-A>).)
捕获前两个字符,即使它们被组分开。
(提前查找在这里更常用,但并不总是可以扩展:它可能会重复您的逻辑。)
(?<A-B>)
是一项强大的功能-它使您可以精确控制拍摄内容。当您尝试从模式中获得更多收益时,请记住这一点。
|'[^']*'
在正确的位置添加:example。如果您还需要转义字符,请参见此处的示例:(用于匹配C#字符串文字的正则表达式)[ stackoverflow.com/a/4953878/7586]。