在.NET 正则表达式中,“组”和“捕获”有什么区别?

我有点搞不清楚“团队”和“俘虏”到底有什么区别。NET 的正则表达式语言。考虑下面的 C # 代码:

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

我希望这会导致一个字母‘ 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”的截图呢?

35115 次浏览

来自 MSDN 文件:

Capture 属性的真正实用性发生在将量词应用于捕获组以便该组在单个正则表达式中捕获多个子字符串时。在本例中,Group 对象包含有关最后捕获的子字符串的信息,而 Capture 属性包含有关该组捕获的所有子字符串的信息。在下面的示例中,正则表达式 b (w + s *) + 。匹配以句号结尾的整个句子。组(w + s *) + 捕获集合中的单个单词。因为 Group 集合只包含关于最后捕获的子字符串的信息,所以它捕获句子中的最后一个单词,即“判决”。但是,组捕获的每个单词都可以从 Capture 属性返回的集合中获得。

你不会是第一个对此感到困惑的人。以下是著名的 Jeffrey Friedl对此的评论(437页以上) :

根据您的视图,它可以添加 一个有趣的新维度 匹配结果,或增加混乱和 肿胀。

更进一步:

群体之间的主要区别 对象和 Capture 对象是 每个 Group 对象包含一个 代表 所有 中间人匹配的 小组赛,以及 小组匹配的最终文本。

几页之后,他得出了这样的结论:

在通过.NET 之后 文件,实际上 理解这些对象添加了什么, 我对他们的感觉很复杂 一方面,这是一个有趣的 另一方面,创新[ . . ] 似乎增加了效率负担[ . . ] 一个不会被使用的功能 在大多数情况下

换句话说: 它们非常相似,但是偶尔,当它发生的时候,你会发现它们的用处。在你长出另一个白胡子之前,你甚至可能会喜欢上俘虏..。


既然上面的内容和另一篇文章中的内容都不能真正回答你的问题,那么考虑一下下面的内容。把俘虏看作是一种历史追踪器。当正则表达式进行匹配时,它从左到右穿过字符串(暂时忽略回溯) ,当遇到匹配的捕获括号时,它将把它存储在 $x(x 是任何数字)中,比如说 $1

正则表达式引擎在重复捕获括号时,会丢弃当前的 $1并用新值替换它。没有。NET,它将保留此历史记录并将其放置在 Captures[0]中。

如果我们将正则表达式更改为:

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

你会注意到,第一个 Group将有一个 Captures(第一组始终是整个匹配,即,等于 $0) ,第二组将持有 {S},即,只有最后一个匹配组。然而,这里有一个捕获,如果你想找到另外两个捕获,它们在 Captures中,它包含了 {Q} {R}{S}的所有中间捕获。

如果您想知道如何从多次捕获中获取信息,因为多次捕获只显示与字符串中清楚显示的单个捕获的最后一次匹配,那么必须使用 Captures

关于最后一个问题的最后一句话: 总的匹配总是有一个总的捕获,不要把它和单个组混在一起。捕获只有在组 中才有趣。

Group 是我们在正则表达式中与组相关联的内容

"(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]。俘虏。真没用。.

假设您有以下文本输入 dogcatcatcat和类似于 dog(cat(catcat))的模式

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

Match = = dogcatcatcat and Group0 = = dogcatcatcat

第一组 = = catcatcat

第二组 = = 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)。(例如 group4group5)。那是因为 没有集体重复喜欢 (...){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 他们已经合并成一个单一的 group3组。

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

因此,如果您需要所有3个内部捕获(capture0capture1capture2) ,您将不得不通过循环组的 Captures集合。

结论是: 注意你设计模式组的方式。 你应该预先考虑一下是什么行为导致了团队的规范,比如 (...)(...)(...){2}或者 (.{3}){2}等等。


希望它将有助于阐明之间的差异 俘虏团体火柴以及。

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

3:10pm与正则表达式 ((\d)+):((\d)+)(am|pm)匹配,并使用 Mono 交互式 csharp:

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在哪里? enter image description here

因为在第四组上有多个匹配的数字,所以如果我们引用组(使用隐式的 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" }

enter image description here

这篇文章提供。