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


161

对于.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”的捕获?

Answers:


126

您不会是第一个对此感到困惑的人。这就是著名的杰弗里·弗里德尔(Jeffrey Friedl)所说的(第437+页):

根据您的观点,它可以为比赛结果添加一个有趣的新维度,也可以使您感到困惑和膨胀。

进一步:

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

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

在了解了.NET文档并真正理解了这些对象的内容之后,我对它们有了不同的看法。一方面,这是一个有趣的创新[..];另一方面,它似乎增加了大多数情况下不会使用的功能的效率负担[..]。

换句话说:它们非常相似,但是偶尔会碰巧有它们的用处。在长出另一头灰色胡须之前,您甚至可能喜欢Captures ...


由于以上内容和其他帖子中的内容均未真正回答您的问题,请考虑以下内容。将Captures视为一种历史跟踪器。当正则表达式匹配时,它从左到右遍历字符串(暂时忽略回溯),当遇到匹配的捕获括号时,它将存储在$x(x是任何数字)中$1

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

如果我们将您的正则表达式更改为如下所示:

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

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

如果您想知道如何从多重捕获(仅显示最后一个匹配项与字符串中明显存在的单个捕获项)中获得收益,则必须使用Captures

关于您的最后一个问题的最后一句话:总比赛总有一个总夺球,不要将其与各个组混合使用。捕获仅在小组内部有趣


1
a functionality that won't be used in the majority of cases我想他错过了船。在短期内(?:.*?(collection info)){4,20}将效率提高数百%。

1
@sln,不确定您指的是什么,他是谁(弗里德尔?)。您给出的示例似乎与此讨论或所使用的表达式无关。此外,非贪婪的量词仅比贪婪的量词更有效,并且需要了解输入集和仔细的性能测试。
亚伯

@Abel-我从一个被标记为重复的问题降落在这里。我看到弗里德尔被引用。这篇文章很旧,需要更新以使其保持现代。只有使用Dot Net才能做到这一点,这是与大多数其他工具不同的地方。细分:量化的非捕获整体组示例(?:..)+。将所有内容延迟匹配.*?到子捕获表达式(组)。继续吧。在一次匹配中,一个组集合会沉淀出一系列所需的东西。接下来无需查找,也无需重新进入即可使其速度提高10至20倍或更多倍。

1
@sln,这个问题是关于其他问题的,尤其是关于其他正则表达式引擎中未找到的.net功能(组与捕获,请参见标题)。我在这里看不到任何过时的内容,.net仍然可以正常工作,实际上,.net的这一部分并没有改变很长时间。性能不是问题的一部分。是的,非捕获分组速度更快,但是这里的主题恰恰相反。为什么在线上和弗里德尔(Friedl)的书中都解释了贪婪比懒惰要快的原因,但这里有OT。也许另一个问题(哪个?)不是真正的重复?
亚伯

2
@Abel-我知道我一直在说,但您一直没听到。我对弗里德尔的这一发言表示不满a functionality that won't be used in the majority of cases。实际上,它是正则表达式领域中最受欢迎的功能。懒/贪心?这与我的评论有什么关系?它允许具有可变数量的捕获缓冲区。它可以在单个匹配中扫描整个字符串。如果.*?(dog)找到的第一个dog,然后(?:.*?(dog))+将找到所有 dog在一场比赛中整个的字符串中。性能显着提高。

20

组是我们与正则表达式中的组相关联的

"(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.

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

"(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的别名。真没用


清晰的解释(y)
Ghasan

19

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

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在哪里? 在此处输入图片说明

由于在第四组上有多个匹配的数字,因此,如果我们引用该组(即带有隐式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" }

在此处输入图片说明

本文礼貌。


3
好文章。一张图片胜过千言万语。
AlexWei

你是明星。
mikemay

14

从MSDN 文档

当将量词应用于捕获组时,Captures属性才真正发挥作用,以便该组在单个正则表达式中捕获多个子字符串。在这种情况下,Group对象包含有关最后捕获的子字符串的信息,而Captures属性包含有关该组捕获的所有子字符串的信息。在下面的示例中,正则表达式\ b(\ w + \ s *)+。匹配以句点结尾的整个句子。组(\ w + \ s *)+捕获集合中的单个单词。因为Group集合仅包含有关最后捕获的子字符串的信息,所以它捕获句子“句子”中的最后一个单词。但是,该组捕获的每个单词都可以从Captures属性返回的集合中获得。


4

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

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

匹配== dogcatcatcat和组0 ==dogcatcatcat

第一组== catcatcat

组2 == 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)。

没有较小的组 group4group5由于(...){3} 重复{n},其中n> = 2),它们被合并为一个组group3

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

因此,如果你需要的所有3个内捕获(capture0capture1capture2)你必须通过集团的循环Captures收集。

包含是:注意设计图案组的方式。你应该想到的前期是什么行为导致群体的规范,如(...)(...)(...){2}(.{3}){2}等。


希望它将有助于阐明CapturesGroupsMatchs之间的区别。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.