什么是正则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式匹配,那么最后的捕获将从堆栈word
popup。 所以,如果我们改变我们以前的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)
,不仅从栈B
popup一个捕获,而且popup的B
和这个当前组捕获之间的所有内容都被压入堆栈A
因此,如果我们使用这样的组合来closures括号,在从堆栈中popup嵌套层次的同时,我们也可以将这个对的内容推送到另一个堆栈上:
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi在他的回答中提供了这个现场演示
所以把所有这些东西放在一起我们可以:
- 请记住任意多的捕获
- validation嵌套结构
- 捕获每个嵌套级别
所有在一个单一的正则expression式。 如果这不令人兴奋…;)
当我第一次了解到这些资源时,我发现了一些有用的资源:
- http://blog.stevenlevithan.com/archives/balancing-groups
- MSDN平衡组
- MSDN上的条件模式
- http://kobikobi.wordpress.com/tag/balancing-group/ (略有学术性,但有一些有趣的应用)
除了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 – 它让你精确控制你的捕捉。 记住,当你试图让你的模式更多。