.NET正则expression式中的“组”和“捕获”有什么区别?

对于.NET的正则expression式语言,我对“组”和“捕获”之间的区别有些模糊。 考虑下面的C#代码:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([AZ])\}$"); 

我期望这会导致一个字母'Q'的单个捕获,但是如果我打印返回的MatchCollection的属性,我看到:

 matches.Count: 1 matches[0].Value: {Q} matches[0].Captures.Count: 1 matches[0].Captures[0].Value: {Q} matches[0].Groups.Count: 2 matches[0].Groups[0].Value: {Q} matches[0].Groups[0].Captures.Count: 1 matches[0].Groups[0].Captures[0].Value: {Q} matches[0].Groups[1].Value: Q matches[0].Groups[1].Captures.Count: 1 matches[0].Groups[1].Captures[0].Value: Q 

这到底是怎么回事? 我知道整场比赛还有一个获胜,但是这些小组是怎么进来的呢? 为什么不matches[0].Captures包含字母“Q”的捕获?

你不会是第一个模糊的人。 以下是着名的杰弗里·弗里德尔( Jeffrey Friedl )对此的评论(第437页):

根据你的观点,这要么增加一个有趣的新维度的比赛结果,或增加混乱和膨胀。

还有:

Group对象和Capture对象之间的主要区别在于,每个Group对象都包含一个Captures集合,该集合表示该组在匹配过程中的所有中间匹配,以及该组匹配的最终文本。

几页之后,这是他的结论:

在通过.NET文档并真正理解这些对象的添加之后,我对他们有着复杂的感受。 一方面,这是一个有趣的创新[…]另一方面,它似乎增加了function的效率负担[…]在大多数情况下不会被使用

换句话说:它们非常相似,但是偶尔发生,你会发现它们的用处。 在你长出另一个灰胡子之前,你甚至可能会喜欢捕捉…


既然以上都没有,另一篇文章中的话也没有真正回答你的问题,可以考虑一下。 捕捉作为一种历史追踪器。 当正则expression式匹配时,它从左到右通过串(忽略回溯一会),当它遇到一个匹配的捕获括号时,它将存储在$x (x是任何数字),比如说$1

正常的正则expression式引擎,当捕获圆括号将被重复时,将扔掉当前的$1 ,并将用新值replace它。 不是.NET,它将保留这个历史并将其放置在Captures[0]

如果我们改变你的正则expression式如下:

 MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[AZ]\})+"); 

你会注意到第一个Group将有一个Captures (第一个组总是整个匹配,即等于$0 ),第二个组将保存{S} ,即只有最后一个匹配组。 然而,如果你想要find另外两个捕获,那么这里就是Captures ,它们在Captures ,其中包含{Q} {R}{S}所有中间捕获。

如果你想知道如何从多次捕捉中获得,那么只能显示最后一次匹配,而不是显示在string中的单个捕捉,你必须使用Captures

最后一个问题的最后一个字:总的匹配总是有一个总捕获,不要混合个人组。 捕捉只在组内才有意思

从MSDN 文档 :

捕获属性的实际效用是将量词应用于捕获组,以便该组捕获单个正则expression式中的多个子string。 在这种情况下,Group对象包含有关上次捕获的子string的信息,而Captures属性包含有关该组捕获的所有子string的信息。 在以下示例中,正则expression式\ b(\ w + \ s *)+。 匹配一段以句号结束的句子。 组(\ w + \ s *)+捕获集合中的单个单词。 由于Group集合仅包含有关最后捕获的子string的信息,因此它会捕获句子中的最后一个单词“sentence”。 但是,由捕获属性返回的集合中可以find该组捕获的每个单词。

一个小组就是我们与正则expression式中的小组相关联的东西

 "(a[zx](b?))" Applied to "axb" returns an array of 3 groups: group 0: axb, the entire match. group 1: axb, the first group matched. group 2: b, the second group matched. 

除了这些只是“被俘”的群体。 非捕获组(使用'(?:'语法不在这里表示。

 "(a[zx](?:b?))" Applied to "axb" returns an array of 2 groups: group 0: axb, the entire match. group 1: axb, the first group matched. 

捕获也是我们与“捕获的群体”相关联的。 但是当多次应用一个量词时,只有最后一个匹配被保留为该组的匹配。 捕获数组存储所有这些匹配。

 "(a[zx]\s+)+" Applied to "ax az ax" returns an array of 2 captures of the second group. group 1, capture 0 "ax " group 1, capture 1 "az " 

至于你的最后一个问题 – 在考虑这个之前,我会想到,捕获将是他们所属组织所捕获的数组。 相反,它只是组的一个别名[0] .Captures。 很无用..

这可以用一个简单的例子来解释(和图片)。

((\d)+):((\d)+)(am|pm) ,使用Mono交互式csharp匹配3:10pm

 csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)"). > Groups.Cast<Group>(). > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g); { "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" } 

那么1在哪里? 在这里输入图像描述

由于在第四组上有多个匹配的数字,所以如果我们引用这个组,我们只是“最后一次匹配”(用一个隐式的ToString() )。 为了暴露中间匹配,我们需要更深入地参考所Captures属性:

 csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)"). > Groups.Cast<Group>(). > Skip(4).First().Captures.Cast<Capture>(). > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c); { "[0] 1", "[1] 0" } 

在这里输入图像描述

由这篇文章提供 。

想象一下,你有以下的文本inputdogcatcatcat和一个模式像dog(cat(catcat))

在这种情况下,您有3个组,第一个( 主要组 )对应于匹配。

匹配== dogcatcatcat和Group0 == dogcatcatcat

Group1 == catcatcat

Group2 == catcat

那么这是关于什么的?

让我们考虑使用Regex类在C#(.NET)中编写的一个小例子。

 int matchIndex = 0; int groupIndex = 0; int captureIndex = 0; foreach (Match match in Regex.Matches( "dogcatabcdefghidogcatkjlmnopqr", // input @"(dog(cat(...)(...)(...)))") // pattern ) { Console.Out.WriteLine($"match{matchIndex++} = {match}"); foreach (Group @group in match.Groups) { Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}"); foreach (Capture capture in @group.Captures) { Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}"); } captureIndex = 0; } groupIndex = 0; Console.Out.WriteLine(); } 

输出

 match0 = dogcatabcdefghi group0 = dogcatabcdefghi capture0 = dogcatabcdefghi group1 = dogcatabcdefghi capture0 = dogcatabcdefghi group2 = catabcdefghi capture0 = catabcdefghi group3 = abc capture0 = abc group4 = def capture0 = def group5 = ghi capture0 = ghi match1 = dogcatkjlmnopqr group0 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group1 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group2 = catkjlmnopqr capture0 = catkjlmnopqr group3 = kjl capture0 = kjl group4 = mno capture0 = mno group5 = pqr capture0 = pqr 

我们来分析一下第一场比赛( match0 )。

正如你所看到的,有三个小组group3group4group5

  group3 = kjl capture0 = kjl group4 = mno capture0 = mno group5 = pqr capture0 = pqr 

这些组(3-5)是由于主模式子模式 (...)(...)(...) (dog(cat(...)(...)(...)))

group3值对应于捕获( capture0 )。 (如在组group4和组group5的情况下)。 这是因为没有(...){3}那样的组重复


好吧,让我们考虑另一个例子,其中有一个组重复

如果我们修改从(dog(cat(...)(...)(...)))(dog(cat(...){3})) ,你会注意到有下面的组重复(...){3}

现在输出已经改变了:

 match0 = dogcatabcdefghi group0 = dogcatabcdefghi capture0 = dogcatabcdefghi group1 = dogcatabcdefghi capture0 = dogcatabcdefghi group2 = catabcdefghi capture0 = catabcdefghi group3 = ghi capture0 = abc capture1 = def capture2 = ghi match1 = dogcatkjlmnopqr group0 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group1 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group2 = catkjlmnopqr capture0 = catkjlmnopqr group3 = pqr capture0 = kjl capture1 = mno capture2 = pqr 

再次,我们来分析一下第一场比赛( match0 )。

由于(...){3} 重复{n},其中n> = 2 ),没有更多的次要群组 group4group5 ,它们被合并成一个单独的group group3

在这种情况下, group3值对应于它的capture2 (换句话说,最后一次捕获 )。

因此,如果您需要所有3个内部捕获( capture0capture0capture2 ),则必须循环访问该组的Captures集合。

包括:注意你devise你的模式组的方式。 (...)(...)(...){2}(.{3}){2}等。


希望这将有助于揭示捕捉团体匹配之间的差异。