.NET正規表現の「グループ」と「キャプチャ」の違いは何ですか?


161

.NETの正規表現言語に関して、 "グループ"と "キャプチャ"の違いは少し曖昧です。次のC#コードを考えます。

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

これにより、文字 'Q'が1回キャプチャされることが期待されますが、返されたのプロパティを出力すると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」の文字のキャプチャが含まれていないのですか?

回答:


126

あなたはそれについて曖昧である最初ではないでしょう。有名なジェフリー・フリードルがそれについて言わなければならないことは以下のとおりです(437ページ以上):

あなたの見方に応じて、それはマッチ結果に興味深い新しい次元を追加するか、混乱と膨らみを追加します。

そしてさらに:

GroupオブジェクトとCaptureオブジェクトの主な違いは、各Groupオブジェクトには、 一致時にグループによるすべての中間一致を表すCaptureのコレクションと、グループに一致する最終テキストが含まれることです。

そして数ページ後、これは彼の結論です:

.NETドキュメンテーションを通り過ぎて、これらのオブジェクトが何を追加するかを実際に理解した後、私はそれらについて複雑な感情を持っています。一方では、それは興味深いイノベーションです[..]一方、大部分のケースでは使用されない機能の効率負担[..]を追加するようです

言い換えると、これらは非常に似ていますが、時折、偶然にもそれらの使用法を見つけることができます。別の灰色のひげを育てる前に、あなたはキャプチャーを好きになるかもしれません...


上記も、他の投稿で述べられていることも、実際にはあなたの質問に答えていないようですので、以下を検討してください。キャプチャを一種の履歴トラッカーと考えてください。正規表現が一致すると、文字列を左から右に(少しの間バックトラックを無視して)処理し、一致するキャプチャ括弧が検出されると、それを$x(xは任意の数字)に格納します$1

通常の正規表現エンジンは、キャプチャする括弧が繰り返される場合、現在の$1値を破棄して新しい値に置き換えます。この履歴を保持して.NETに配置する.NETではありませんCaptures[0]

正規表現を次のように変更した場合:

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

あなたは最初にいることがわかりますGroup1になりますCaptures(、常に全体の一致である第1のグループ、すなわちに等しい$0)と第2のグループが開催する{S}、すなわち最後の一致するグループ。ただし、ここにキャッチがあります。他の2つのキャッチを見つけたい場合は、Capturesにあります。これには、{Q} {R}およびのすべての中間キャプチャが含まれています{S}

文字列内に明確に存在する個々のキャプチャとの最後の一致のみを表示する複数キャプチャからどのように取得できるか疑問に思った場合は、を使用する必要がありますCaptures

最後の質問の最後の言葉:完全一致には常に1つの合計キャプチャがあります。個別のグループと混合しないでください。キャプチャはグループ内でのみ興味深いものです。


1
a functionality that won't be used in the majority of cases彼はボートを逃したと思う。短期的に(?:.*?(collection info)){4,20}は効率が数百パーセント以上向上します。

1
@sln、あなたが何を指しているのか、誰が「彼」なのかわからない(friedl?)。あなたが与える例は、この議論や使用されている表現とは無関係のようです。さらに、貪欲でない数量詞が貪欲な数量詞よりも効率的であることは非常にまれであり、入力セットの知識と慎重なパフォーマンステストが必要です。
アベル

@Abel-私はこれの重複とマークされた質問からここに着陸しました。フリードルが引用されているのがわかります。この投稿は古く、最新の状態に保つには更新する必要があります。これを行うことができるのはDot Netだけであり、それが他のほとんどのものとは別のものです。内訳:数値化された非キャプチャグループ全体の例(?:..)+.*?キャプチャサブ表現(グループ)までのすべてを遅延検索します。そのまま。単一の一致内で、グループコレクションは必要なものだけの配列を生成します。次を見つける必要はありません。再エントランスがないため、10〜20倍以上速くなります。

1
@sln、この質問は他の問題に関するものであり、他の正規表現エンジンにはない.net機能に関するものです(グループとキャプチャ、タイトルを参照)。ここでは時代遅れのものは何もありません。.netはまだ同じように機能しています。実際、この部分は.netで長い間変更されていません。パフォーマンスは問題の一部ではありません。はい、グループ化をキャプチャしない方が高速ですが、ここでも主題は逆です。貪欲が怠惰よりも速い理由は、オンラインやfriedlの本で多くのテキストで説明されていますが、ここではOTです。たぶん他の質問(どれ?)は真の複製ではなかったのですか?
アベル

2
@アベル-私は私がそれを言い続けていることを知っていますが、あなたはそれを聞き続けません。私はフリードルのこの発言に恥ずかしく思いa functionality that won't be used in the majority of casesます。実際、正規表現の土地で最も求められている機能です。怠惰/貪欲?それは私のコメントとどのような関係がありますか?可変量のキャプチャバッファを使用できます。1回の一致で文字列全体をスイープできます。場合.*?(dog)の発見は、最初にdog、次に(?:.*?(dog))+見つけるすべてを dog 1マッチ中に文字列全体に。パフォーマンスの向上が顕著です。

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.

キャプチャは、「キャプチャされたグループ」に関連付けられているものでもあります。ただし、グループに数量詞を複数回適用すると、最後の一致のみがグループの一致として保持されます。キャプチャ配列は、これらのすべての一致を格納します。

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

あなたの最後の質問については-これを調べる前に、Capturesは所属するグループによって順序付けられたキャプチャの配列であると思ったでしょう。むしろ、それはgroups [0] .Capturesの単なるエイリアスです。かなり役に立たない


明確な説明(y)
Ghasan

19

これは簡単な例(および写真)で説明できます。

3:10pm正規表現とのマッチング((\d)+):((\d)+)(am|pm)、およびMono interactiveの使用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はどこにあるのでしょうか。 ここに画像の説明を入力してください

4番目のグループで一致する複数の数字があるため、グループを参照する場合(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 2018

君はスター。
mikemay

14

MSDN ドキュメントから:

Capturesプロパティの実際のユーティリティは、数量詞がキャプチャグループに適用され、グループが単一の正規表現で複数の部分文字列をキャプチャするときに発生します。この場合、Groupオブジェクトには最後にキャプチャされた部分文字列に関する情報が含まれていますが、Capturesプロパティにはグループによってキャプチャされたすべての部分文字列に関する情報が含まれています。次の例では、正規表現\ b(\ w + \ s *)+です。ピリオドで終わる文全体に一致します。グループ(\ w + \ s *)+は、コレクション内の個々の単語をキャプチャします。Groupコレクションには、最後にキャプチャされた部分文字列に関する情報のみが含まれているため、「sentence」という文の最後の単語がキャプチャされます。ただし、グループによってキャプチャされた各単語は、Capturesプロパティによって返されたコレクションから利用できます。


4

次のテキスト入力dogcatcatcatと次のようなパターンがあるとします。dog(cat(catcat))

この場合、3つのグループがあり、最初のグループメジャーグループ)が一致に対応します。

== dogcatcatcatおよびGroup0 ==に一致dogcatcatcat

グループ1 == 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)だけを分析しましょう。

あなたが見ることができるように3つのがあるマイナーなグループはgroup3group4およびgroup5

    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)のみを分析します。

そこにはより多くのないマイナーグループは group4及びgroup5ための(...){3} 反復{n}は、前記N> = 2、それらが一つの群に併合してきました)group3

この場合、group3値はそれcapture2(つまり、最後のキャプチャ)に対応します。

あなたはすべての3つのインナーキャプチャを(必要な場合はこのようにcapture0capture1capture2)あなたは、グループの一巡する必要がありますCapturesコレクション。

結論としては、パターンのグループを設計する方法に注意してください。あなたのような、グループの仕様を引き起こすもの振る舞い先行考えなければならない(...)(...)(...){2}または(.{3}){2}など


うまくいけば、CapturesGroupsMatchesの違いを明らかにするのにも役立ちます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.