Saya baru saja membaca pertanyaan tentang bagaimana mendapatkan data di dalam kurung kurawal ganda ( pertanyaan ini ), dan kemudian seseorang mengemukakan kelompok penyeimbang. Saya masih belum yakin apa itu dan bagaimana menggunakannya.
Saya membaca Definisi Balancing Group , namun penjelasannya sulit untuk diikuti, dan saya masih cukup bingung dengan pertanyaan yang saya sebutkan.
Bisakah seseorang menjelaskan secara sederhana apa itu kelompok penyeimbang dan bagaimana kelompok itu berguna?
c#
.net
regex
balancing-groups
IniNotALie.
sumber
sumber
Jawaban:
Sejauh yang saya tahu, grup penyeimbang adalah unik untuk rasa regex .NET.
Selain: Grup Berulang
Pertama, Anda perlu tahu bahwa .NET adalah (sekali lagi, sejauh yang saya tahu) satu-satunya ragam regex yang memungkinkan Anda mengakses beberapa tangkapan dari satu grup penangkap (tidak dalam referensi latar tetapi setelah pertandingan selesai).
Untuk mengilustrasikan hal ini dengan sebuah contoh, perhatikan polanya
dan tali itu
"abcd"
.di semua ragam ekspresi reguler lainnya, grup penangkap
1
hanya akan menghasilkan satu hasil:d
(perhatikan, pertandingan lengkap tentu saja akanabcd
seperti yang diharapkan). Ini karena setiap penggunaan baru grup penangkap menimpa tangkapan sebelumnya..NET di sisi lain mengingat semuanya. Dan itu dilakukan dalam tumpukan. Setelah mencocokkan regex seperti di atas
Match m = new Regex(@"(.)+").Match("abcd");
Anda akan menemukannya
m.Groups[1].Captures
Adalah
CaptureCollection
yang elemennya sesuai dengan empat tangkapan0: "a" 1: "b" 2: "c" 3: "d"
dimana nomor tersebut adalah indeks ke dalam
CaptureCollection
. Jadi pada dasarnya setiap kali grup digunakan lagi, tangkapan baru didorong ke tumpukan.Akan lebih menarik jika kita menggunakan grup penangkap bernama. Karena. NET memungkinkan penggunaan berulang dari nama yang sama kita bisa menulis seperti regex
untuk memasukkan dua kata ke dalam kelompok yang sama. Sekali lagi, setiap kali grup dengan nama tertentu ditemukan, tangkapan didorong ke tumpukannya. Jadi menerapkan regex ini ke input
"foo bar"
dan pemeriksaanm.Groups["word"].Captures
kami menemukan dua tangkapan
0: "foo" 1: "bar"
Hal ini memungkinkan kita untuk mendorong sesuatu ke dalam satu tumpukan dari bagian ekspresi yang berbeda. Tapi tetap saja, ini hanya fitur .NET untuk dapat melacak beberapa tangkapan yang tercantum di sini
CaptureCollection
. Tapi saya katakan, koleksi ini bertumpuk . Jadi bisakah kita meletuskan sesuatu darinya?Masuk: Grup Penyeimbang
Ternyata kita bisa. Jika kita menggunakan grup seperti
(?<-word>...)
, maka tangkapan terakhir akan muncul dari tumpukanword
jika subekspresi...
cocok. Jadi jika kita mengubah ekspresi sebelumnya menjadiKemudian grup kedua akan memunculkan tangkapan grup pertama, dan kami akan menerima kosong
CaptureCollection
pada akhirnya. Tentu saja, contoh ini sangat tidak berguna.Tapi ada satu detail lagi pada sintaks minus: jika tumpukan sudah kosong, grup gagal (terlepas dari subpola). Kita dapat memanfaatkan perilaku ini untuk menghitung tingkat bersarang - dan dari sinilah nama grup penyeimbang berasal (dan di mana hal itu menjadi menarik). Katakanlah kita ingin mencocokkan string yang diberi tanda kurung dengan benar. Kami mendorong setiap kurung buka pada tumpukan, dan memunculkan satu tangkapan untuk setiap kurung tutup. Jika kita menemukan satu kurung tutup terlalu banyak, itu akan mencoba memunculkan tumpukan kosong dan menyebabkan pola gagal:
Jadi kami memiliki tiga alternatif dalam pengulangan. Alternatif pertama mengkonsumsi semua yang bukan tanda kurung. Alternatif kedua cocok
(
sambil mendorongnya ke tumpukan. Alternatif ketiga cocok dengan)
s saat memunculkan elemen dari tumpukan (jika memungkinkan!).Catatan: Hanya untuk memperjelas, kami hanya memeriksa bahwa tidak ada tanda kurung yang tidak cocok! Ini berarti bahwa string yang tidak mengandung tanda kurung sama sekali akan cocok, karena mereka masih valid secara sintaks (dalam beberapa sintaks di mana Anda membutuhkan tanda kurung yang cocok). Jika Anda ingin memastikan setidaknya satu set tanda kurung, cukup tambahkan lookahead
(?=.*[(])
tepat setelah^
.Pola ini tidak sempurna (atau seluruhnya benar).
Finale: Pola Bersyarat
Ada satu tangkapan lagi: ini tidak memastikan bahwa tumpukan kosong di akhir string (karenanya
(foo(bar)
akan valid). .NET (dan banyak varian lainnya) memiliki satu konstruksi lagi yang membantu kita di sini: pola bersyarat. Sintaks umumnya adalahdi mana
falsePattern
adalah opsional - jika dihilangkan, kasus palsu akan selalu cocok. Kondisi dapat berupa pola, atau nama grup penangkap. Saya akan fokus pada kasus terakhir di sini. Jika itu adalah nama grup penangkap, makatruePattern
digunakan jika dan hanya jika tumpukan tangkapan untuk grup tertentu itu tidak kosong. Artinya, pola bersyarat seperti(?(name)yes|no)
membaca "jikaname
telah cocok dan menangkap sesuatu (yang masih di tumpukan), gunakan polayes
jika tidak gunakan polano
".Jadi pada akhir pola kita di atas, kita bisa menambahkan sesuatu seperti
(?(Open)failPattern)
yang menyebabkan seluruh pola gagal, jikaOpen
-stack tidak kosong. Hal paling sederhana untuk membuat pola gagal tanpa syarat adalah(?!)
(tampilan negatif kosong). Jadi kami memiliki pola terakhir kami:Perhatikan bahwa sintaksis bersyarat ini tidak ada hubungannya dengan grup penyeimbang tetapi perlu untuk memanfaatkan kekuatan penuh mereka.
Dari sini, langit adalah batasnya. Banyak penggunaan yang sangat canggih dimungkinkan dan ada beberapa gotcha ketika digunakan dalam kombinasi dengan fitur .NET-Regex lainnya seperti lookbehinds panjang-variabel ( yang harus saya pelajari sendiri dengan susah payah ). Namun pertanyaan utamanya selalu: apakah kode Anda masih dapat dipelihara saat menggunakan fitur ini? Anda perlu mendokumentasikannya dengan sangat baik, dan memastikan bahwa setiap orang yang mengerjakannya juga mengetahui fitur-fitur ini. Jika tidak, Anda mungkin lebih baik, cukup berjalan string secara manual karakter demi karakter dan menghitung level bersarang dalam integer.
Tambahan: Ada apa dengan
(?<A-B>...)
sintaks?Penghargaan untuk bagian ini diberikan kepada Kobi (lihat jawabannya di bawah untuk lebih jelasnya).
Sekarang dengan semua hal di atas, kita dapat memvalidasi bahwa string diberi tanda kurung dengan benar. Tapi akan jauh lebih berguna, jika kita benar-benar bisa menangkap (bersarang) untuk semua konten tanda kurung itu. Tentu saja, kami dapat mengingat membuka dan menutup tanda kurung di tumpukan tangkapan terpisah yang tidak dikosongkan, lalu melakukan beberapa ekstraksi substring berdasarkan posisinya dalam langkah terpisah.
Tetapi .NET menyediakan satu lagi fitur kenyamanan di sini: jika kita menggunakan
(?<A-B>subPattern)
, tidak hanya tangkapan yang muncul dari tumpukanB
, tetapi juga segala sesuatu antara tangkapan yang muncul dariB
dan grup saat ini didorong ke tumpukanA
. Jadi jika kami menggunakan grup seperti ini untuk tanda kurung penutup, saat memunculkan level bersarang dari tumpukan kami, kami juga dapat mendorong konten pasangan ke tumpukan lain:Kobi memberikan Live-Demo ini sebagai jawabannya
Jadi dengan menggabungkan semua hal ini kita bisa:
Semuanya dalam satu ekspresi reguler. Jika itu tidak menarik ...;)
Beberapa sumber yang menurut saya berguna ketika saya pertama kali mempelajarinya:
sumber
Hanya sedikit tambahan untuk jawaban bagus M. Buettner:
Apa masalahnya dengan
(?<A-B>)
sintaks?(?<A-B>x)
sedikit berbeda dari(?<-A>(?<B>x))
. Mereka menghasilkan aliran kontrol yang sama * , tetapi menangkap secara berbeda.Misalnya, mari kita lihat pola kawat gigi yang seimbang:
Pada akhir pertandingan kami memiliki string yang seimbang, tetapi hanya itu yang kami miliki - kami tidak tahu di mana kurung kurawal karena
B
tumpukan kosong. Kerja keras yang dilakukan mesin untuk kami hilang.( contoh di Regex Storm )
(?<A-B>x)
adalah solusi untuk masalah tersebut. Bagaimana? Itu tidak menangkapx
ke$A
: itu menangkap konten antara pengambilan sebelumnyaB
dan posisi saat ini.Mari kita gunakan dalam pola kita:
Ini akan menangkap
$Content
string antara kawat gigi (dan posisinya), untuk setiap pasangan di sepanjang jalan.Untuk string
{1 2 {3} {4 5 {6}} 7}
akan ada empat menangkap:3
,6
,4 5 {6}
, dan1 2 {3} {4 5 {6}} 7
- jauh lebih baik daripada apa-apa atau}
}
}
}
.( contoh - klik
table
tab dan lihat${Content}
, menangkap )Faktanya, ini dapat digunakan tanpa menyeimbangkan sama sekali:
(?<A>).(.(?<Content-A>).)
menangkap dua karakter pertama, meskipun dipisahkan oleh grup.(sebuah lookahead lebih umum digunakan di sini tetapi tidak selalu menskalakan: itu mungkin menduplikasi logika Anda.)
(?<A-B>)
adalah fitur yang kuat - ini memberi Anda kontrol yang tepat atas tangkapan Anda. Ingatlah hal itu saat Anda mencoba memaksimalkan pola Anda.sumber
|'[^']*'
di tempat yang tepat: contoh . Jika Anda juga membutuhkan karakter yang lolos, ada contohnya di sini: (Regex untuk pencocokan literal string C #) [ stackoverflow.com/a/4953878/7586] .