web-dev-qa-db-ja.com

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

.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」のキャプチャを含めますか?

149
Nick Meyer

あなたはそれについてファジーな最初の人ではありません。以下は、有名な Jeffrey Friedl がそれについて言っていることです(ページ437+):

あなたの見解に応じて、興味深い新しい次元を試合結果に追加するか、混乱と肥大化を追加します。

そしてさらに:

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

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

.NETのドキュメントを読み、これらのオブジェクトが追加するものを実際に理解した後、私はそれらについて複雑な気持ちになりました。一方で、それは興味深い革新です[..]一方で、大部分のケースで使用されない機能の効率の負担[..]を追加するようです

言い換えれば、これらは非常に似ていますが、ときどき発生する場合、それらの使用法を見つけることができます。別の灰色のひげを生やす前に、キャプチャを好むこともあります...


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

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

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

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

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

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

最後の質問の最後の言葉:合計マッチには常に合計キャプチャが1つあります。これを個々のグループと混在させないでください。キャプチャはグループ内でのみ興味深いものです

117
Abel

グループは、正規表現でグループに関連付けたものです

"(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への単なるエイリアスです。かなり役に立たない..

18
Gerard ONeill

MSDNから ドキュメント

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

14
pmarflee

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

_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

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" }
_

enter image description here

この記事 の礼儀。

10
Eric Smith

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

この場合、3つのグループがあり、最初のグループ(major group)は一致に対応します。

一致== 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)だけを分析しましょう。

ご覧のとおり、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)だけを分析しましょう。

マイナーグループgroup4group5は、(...){3}のためにありません繰り返し{n}ここでn> = 2)それらは1つの単一グループgroup3にマージされました。

この場合、group3値はcapture2に対応します(最後のキャプチャ、言い換えれば)。

したがって、3つの内部キャプチャ(capture0capture1capture2)をすべて必要とする場合、グループのCapturesコレクションを循環する必要があります。

包含とは、パターンのグループを設計する方法に注意を払うことです。 (...)(...)(...){2}、または(.{3}){2}などのように、グループの仕様をどのような動作が引き起こすかを事前に考える必要があります。


うまくいけばCapturesGroups、およびMatchesの違いを明らかにするのに役立つでしょう。

4
AndreyWD