Apa perbedaan antara "grup" dan "tangkapan" dalam ekspresi reguler .NET?

161

Saya agak kabur tentang apa perbedaan antara "grup" dan "tangkapan" ketika datang ke bahasa ekspresi reguler .NET. Pertimbangkan kode C # berikut:

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

Saya berharap ini menghasilkan tangkapan tunggal untuk huruf 'Q', tetapi jika saya mencetak properti yang dikembalikan MatchCollection, saya melihat:

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

Apa sebenarnya yang terjadi di sini? Saya mengerti bahwa ada juga tangkapan untuk seluruh pertandingan, tetapi bagaimana cara grup masuk? Dan mengapa tidak matches[0].Capturesmenyertakan tangkapan untuk huruf 'Q'?

Nick Meyer
sumber

Jawaban:

126

Anda tidak akan menjadi orang pertama yang kabur tentang hal itu. Inilah yang dikatakan oleh Jeffrey Friedl yang terkenal tentang hal itu (halaman 437+):

Bergantung pada pandangan Anda, itu menambah dimensi baru yang menarik pada hasil pertandingan, atau menambah kebingungan dan kembung.

Dan selanjutnya:

Perbedaan utama antara objek Grup dan objek Capture adalah bahwa setiap objek Grup berisi koleksi Capture yang mewakili semua pertandingan perantara oleh grup selama pertandingan, serta teks akhir yang cocok dengan grup.

Dan beberapa halaman kemudian, inilah kesimpulannya:

Setelah melewati dokumentasi .NET dan benar-benar memahami apa yang ditambahkan objek-objek ini, saya merasa campur aduk tentangnya. Di satu sisi, ini merupakan inovasi yang menarik [..] di sisi lain, tampaknya menambah beban efisiensi [..] dari fungsi yang tidak akan digunakan dalam sebagian besar kasus

Dengan kata lain: mereka sangat mirip, tetapi sesekali dan ketika itu terjadi, Anda akan menemukan kegunaan untuk mereka. Sebelum Anda menumbuhkan jenggot kelabu lain, Anda bahkan mungkin menyukai Capture ...


Karena tidak ada jawaban di atas, atau apa yang dikatakan di pos lain yang benar-benar menjawab pertanyaan Anda, pertimbangkan yang berikut ini. Pikirkan Capture sebagai semacam pelacak sejarah. Ketika regex melakukan kecocokan, ia melewati string dari kiri ke kanan (mengabaikan lompatan mundur sesaat) dan ketika bertemu dengan tanda kurung yang cocok, itu akan menyimpannya dalam $x(x menjadi digit apa pun), katakanlah $1.

Mesin regex normal, ketika tanda kurung diulang, akan membuang arus $1dan akan menggantinya dengan nilai baru. Bukan .NET, yang akan menyimpan riwayat ini dan menempatkannya di Captures[0].

Jika kami mengubah regex Anda menjadi seperti berikut:

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

Anda akan melihat bahwa yang pertama Groupakan memiliki satu Captures(grup pertama selalu menjadi seluruh pertandingan, yaitu, sama dengan $0) dan grup kedua akan bertahan {S}, yaitu hanya grup yang cocok terakhir. Namun, dan inilah tangkapannya, jika Anda ingin menemukan dua tangkapan lainnya, mereka masuk Captures, yang berisi semua tangkapan perantara untuk {Q} {R}dan {S}.

Jika Anda pernah bertanya-tanya bagaimana Anda bisa mendapatkan dari multi-tangkapan, yang hanya menunjukkan pertandingan terakhir ke masing-masing tangkapan yang jelas-jelas ada dalam string, Anda harus menggunakan Captures.

Kata terakhir pada pertanyaan terakhir Anda: pertandingan total selalu memiliki satu Capture total, jangan campur dengan masing-masing Grup. Capture hanya menarik di dalam grup .

Abel
sumber
1
a functionality that won't be used in the majority of casesSaya pikir dia ketinggalan kapal. Dalam jangka pendek (?:.*?(collection info)){4,20}meningkatkan efisiensi lebih dari beberapa ratus persen.
1
@ SVN, tidak yakin apa yang Anda maksud dan siapa 'dia' (friedl?). Contoh yang Anda berikan tampaknya tidak terkait dengan diskusi ini, atau dengan ekspresi yang digunakan. Selain itu, penjumlahan yang tidak serakah hanya sangat jarang lebih efisien daripada penjumlah yang serakah, dan membutuhkan pengetahuan tentang rangkaian input dan pengujian kinerja yang cermat.
Abel
@ Bel - Saya mendarat di sini dari duplikat bertanda pertanyaan ini. Saya melihat Friedl dikutip. Posting ini sudah tua dan perlu di-refresh agar tetap modern. Hanya dengan Dot Net hal ini dapat dilakukan, itu yang membedakannya dari kebanyakan orang lain. Hancurkan: Contoh grup keseluruhan non-tangkapan terkuantifikasi (?:..)+. Dengan mudah menyesuaikan apa pun .*?hingga sub ekspresi tangkap (grup). Lanjutkan. Dalam satu pertandingan, koleksi grup mempercepat berbagai hal yang diperlukan. Tidak perlu mencari berikutnya, tidak ada pintu masuk kembali sehingga 10 hingga 20 kali atau lebih cepat.
1
@ sln, pertanyaan ini adalah tentang sesuatu yang lain dan ini secara khusus tentang fitur .net yang tidak ditemukan di mesin regex lainnya (grup vs tangkapan, lihat judul). Saya tidak melihat sesuatu yang ketinggalan jaman di sini, .net masih berfungsi sama, bahkan bagian ini tidak berubah dalam waktu yang lama di .net. Kinerja bukan bagian dari pertanyaan. Ya, pengelompokan yang tidak menangkap lebih cepat, tetapi sekali lagi, subjek di sini adalah sebaliknya. Mengapa serakah lebih cepat daripada malas dijelaskan dalam banyak teks online dan oleh buku friedl, tetapi PL di sini. Mungkin pertanyaan lain (yang mana?) Bukan duplikat yang sebenarnya?
Abel
2
@ Bel - Saya tahu saya terus mengatakannya, tetapi Anda tetap tidak mendengarnya. Saya menerima pernyataan ini dari Friedl a functionality that won't be used in the majority of cases. Bahkan itu adalah fungsi yang paling dicari di tanah regex. Malas / serakah? Apa hubungannya dengan komentar saya? Hal ini memungkinkan memiliki jumlah buffer tangkapan yang bervariasi. Itu dapat menyapu seluruh string dalam satu pertandingan. Jika .*?(dog)menemukan yang pertama dogmaka (?:.*?(dog))+akan menemukan semua dog di seluruh string dalam satu pertandingan. Peningkatan kinerja terlihat.
20

Grup adalah apa yang telah kami kaitkan dengan grup dalam ekspresi reguler

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

kecuali bahwa ini hanya kelompok 'tertangkap'. Grup yang tidak menangkap (menggunakan sintaks '(?:' Tidak diwakili di sini.

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

Capture juga merupakan apa yang telah kami kaitkan dengan 'kelompok yang ditangkap'. Tetapi ketika grup diterapkan dengan quantifier beberapa kali, hanya pertandingan terakhir yang disimpan sebagai pertandingan grup. Array menangkap menyimpan semua pertandingan ini.

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

Adapun pertanyaan terakhir Anda - saya akan berpikir sebelum melihat ini bahwa Capture akan menjadi array dari tangkapan yang dipesan oleh kelompok mereka. Sebaliknya itu hanya alias ke grup [0]. Capture. Cukup tidak berguna ..

Gerard ONeill
sumber
Penjelasan yang jelas (y)
Ghasan
19

Ini dapat dijelaskan dengan contoh sederhana (dan gambar).

Cocok 3:10pmdengan ekspresi reguler ((\d)+):((\d)+)(am|pm), dan menggunakan Mono interaktif 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" }

Jadi dimana 1? masukkan deskripsi gambar di sini

Karena ada beberapa digit yang cocok dengan grup keempat, kami hanya "mendapatkan" pertandingan terakhir jika kami mereferensikan grup (dengan implisit ToString(), yaitu). Untuk mengekspos pertandingan perantara, kita harus masuk lebih dalam dan mereferensikan Capturesproperti pada grup yang dimaksud:

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

masukkan deskripsi gambar di sini

Atas perkenan artikel ini .

Eric Smith
sumber
3
Artikel yang bagus. Sebuah gambar bernilai ribuan kata.
AlexWei
Anda seorang bintang.
mikemay
14

Dari dokumentasi MSDN :

Utilitas nyata properti Capture terjadi ketika kuantifier diterapkan ke grup penangkap sehingga grup tersebut menangkap beberapa substring dalam satu ekspresi reguler. Dalam kasus ini, objek Grup berisi informasi tentang substring yang terakhir ditangkap, sedangkan properti Capture berisi informasi tentang semua substring yang ditangkap oleh grup. Dalam contoh berikut, ekspresi reguler \ b (\ w + \ s *) +. cocok dengan seluruh kalimat yang berakhir dalam suatu periode. Grup (\ w + \ s *) + menangkap setiap kata dalam koleksi. Karena kumpulan Grup hanya berisi informasi tentang substring yang terakhir ditangkap, itu menangkap kata terakhir dalam kalimat, "kalimat". Namun, setiap kata yang ditangkap oleh grup tersedia dari koleksi yang dikembalikan oleh properti Capture.

pmarflee
sumber
4

Bayangkan Anda memiliki input teks dogcatcatcatdan pola seperti berikutdog(cat(catcat))

Dalam hal ini, Anda memiliki 3 grup, yang pertama ( grup utama ) berhubungan dengan pertandingan.

Cocokkan == dogcatcatcatdan Group0 ==dogcatcatcat

Group1 == catcatcat

Group2 == catcat

Jadi tentang apa semua ini?

Mari kita pertimbangkan contoh kecil yang ditulis dalam C # (.NET) menggunakan Regexkelas.

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();
        }

Output :

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

Mari kita analisis pertandingan pertama ( match0).

Seperti yang Anda lihat ada tiga kelompok kecil : group3, group4dangroup5

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

Kelompok-kelompok tersebut (3-5) diciptakan karena ' subpattern ' (...)(...)(...)dari pola utama (dog(cat(...)(...)(...)))

Nilai group3korespondensi dengan tangkapannya ( capture0). (Seperti dalam kasus group4dan group5). Itu karena tidak ada pengulangan kelompok seperti (...){3}.


Ok, mari kita pertimbangkan contoh lain di mana ada pengulangan grup .

Jika kita mengubah pola ekspresi reguler untuk dicocokkan (untuk kode yang ditunjukkan di atas) dari (dog(cat(...)(...)(...)))ke (dog(cat(...){3})), Anda akan melihat bahwa ada adalah sebagai berikut kelompok pengulangan : (...){3}.

Sekarang Output telah berubah:

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

Sekali lagi, mari kita analisis pertandingan pertama ( match0).

Tidak ada lagi grup minor group4 dan group5karena (...){3} pengulangan ( {n} dimana n> = 2 ) mereka telah digabung menjadi satu grup tunggal group3.

Dalam hal ini, group3nilainya sesuai dengan itu capture2( tangkapan terakhir , dengan kata lain).

Jadi jika Anda membutuhkan semua 3 menangkap bagian dalam ( capture0, capture1, capture2) Anda harus siklus melalui kelompok Captureskoleksi.

Kesimpulan adalah: perhatikan cara Anda mendesain grup pola Anda. Anda harus berpikir di muka perilaku apa yang menyebabkan spesifikasi, suka (...)(...), (...){2}atau lainnya (.{3}){2}.


Semoga ini akan membantu menjelaskan perbedaan antara Capture , Grup , dan Pertandingan juga.

AlexMelw
sumber