Normal ifade Dengeleme Grupları nedir?


92

Ben sadece çift küme parantezinin ( bu soru ) içindeki verilerin nasıl alınacağına dair bir soru okuyordum ve sonra birisi dengeleme grupları oluşturdu. Hala ne olduklarından ve onları nasıl kullanacağımdan tam olarak emin değilim.

Dengeleme Grubu Tanımını okudum , ancak açıklamayı takip etmek zor ve bahsettiğim sorular konusunda hala kafam karışık.

Birisi basitçe dengeleme gruplarının ne olduğunu ve nasıl yararlı olduklarını açıklayabilir mi?


Bunun gerçekte kaç regex engiens tarafından desteklendiğini merak ediyorum.
Mike de Klerk

2
@MikedeKlerk En azından .NET Regex motorunda desteklenir.
NotALie.

Yanıtlar:


175

Bildiğim kadarıyla, dengeleme grupları .NET'in normal ifade çeşidine özgüdür.

Kenara: Tekrarlanan Gruplar

Öncelikle, .NET'in (yine bildiğim kadarıyla) tek bir yakalama grubunun birden çok yakalamasına erişmenizi sağlayan tek normal ifade çeşidi olduğunu bilmeniz gerekir (geri başvurularda değil, maç tamamlandıktan sonra).

Bunu bir örnekle açıklamak için, kalıbı düşünün

(.)+

ve dize "abcd".

diğer tüm normal ifade çeşitlerinde, grup yakalama 1yalnızca bir sonuç verecektir: d(not, tam eşleşme elbette abcdbeklendiği gibi olacaktır ). Bunun nedeni, yakalama grubunun her yeni kullanımının önceki yakalamanın üzerine yazmasıdır.

.NET ise hepsini hatırlar. Ve bunu bir yığın halinde yapar. Yukarıdaki normal ifadeyi aşağıdaki gibi eşleştirdikten sonra

Match m = new Regex(@"(.)+").Match("abcd");

onu bulacaksın

m.Groups[1].Captures

Bir mı CaptureCollectionbunun elemanları gelmektedir dört yakalamaları için

0: "a"
1: "b"
2: "c"
3: "d"

burada sayı CaptureCollection. Yani temelde grup tekrar her kullanıldığında, yığına yeni bir yakalama aktarılıyor.

Adlandırılmış yakalama grupları kullanıyorsak daha ilginç hale geliyor. .NET aynı adın tekrar tekrar kullanılmasına izin verdiğinden, aşağıdaki gibi bir normal ifade yazabiliriz:

(?<word>\w+)\W+(?<word>\w+)

aynı gruba iki kelime dahil etmek için. Yine, belirli bir ada sahip bir grupla her karşılaşıldığında, yığınına bir yakalama itilir. Girişine bu düzenli ifade uygulayarak Yani "foo bar"ve teftiş

m.Groups["word"].Captures

iki yakalama bulduk

0: "foo"
1: "bar"

Bu, ifadenin farklı bölümlerinden nesneleri tek bir yığına bile itmemizi sağlar. Ancak yine de, bu sadece .NET'in burada listelenen birden fazla yakalamayı izleyebilme özelliğidir CaptureCollection. Ama bu koleksiyon bir yığın dedim . Bu yüzden olabilir pop ondan şeyler?

Girin: Dengeleme Grupları

Yapabileceğimiz ortaya çıktı. Benzer bir grup kullanırsak , alt ifade eşleşirse (?<-word>...)son yakalama yığından wordçıkar .... Yani önceki ifademizi şu şekilde değiştirirsek

(?<word>\w+)\W+(?<-word>\w+)

Sonra ikinci grup ilk grubun yakalanmasını açacak CaptureCollectionve sonunda bir boş alacağız . Elbette bu örnek oldukça kullanışsız.

Ancak eksi sözdiziminin bir detayı daha var: Yığın zaten boşsa, grup başarısız olur (alt şablonuna bakılmaksızın). Yuvalama seviyelerini saymak için bu davranışı kullanabiliriz - ve bu, ad dengeleme grubunun geldiği yerdir (ve ilginçleştiği yerdir). Doğru şekilde parantez içine alınmış dizeleri eşleştirmek istediğimizi varsayalım. Her açılış parantezini yığına itiyoruz ve her kapanış parantezi için bir yakalama açıyoruz. Bir kapanış paranteziyle çok fazla karşılaşırsak, boş bir yığın açmaya çalışır ve desenin başarısız olmasına neden olur:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$

Yani tekrarda üç alternatifimiz var. İlk alternatif, parantez olmayan her şeyi tüketir. İkinci alternatif, (onları yığına iterken s ile eşleşir . Üçüncü alternatif ), yığından öğeler çıkarırken s ile eşleşir (mümkünse!).

Not: Açıklığa kavuşturmak için, yalnızca eşleşmemiş parantezlerin olmadığını kontrol ediyoruz! Hiç hiçbir parantez içeren dize olduğunu bu araçlar olacak onlar (Maça sizin parantez gereken bazı sözdiziminde) hala sözdizimsel olarak geçerli olduğundan, uyuyor. Eğer parantez en az bir takım sağlamak istiyorsanız, basitçe bir lookahead eklemek (?=.*[(])sonra sağa ^.

Yine de bu model mükemmel (veya tamamen doğru) değildir.

Final: Koşullu Kalıplar

Bir yakalama daha var: bu, dizinin sonunda yığının boş olmasını sağlamaz (dolayısıyla (foo(bar)geçerli olacaktır). .NET (ve diğer birçok çeşidin) burada bize yardımcı olan bir yapı daha var: koşullu modeller. Genel sözdizimi

(?(condition)truePattern|falsePattern)

burada falsePatternisteğe bağlıdır - atlanırsa yanlış durum her zaman eşleşir. Koşul, bir kalıp veya bir yakalama grubunun adı olabilir. Burada ikinci duruma odaklanacağım. Bir yakalama grubunun adı ise, o zaman truePatternancak ve ancak söz konusu grup için yakalama yığını boş değilse kullanılır. Yani, (?(name)yes|no)"bir nameşeyi eşleştirip yakaladıysa (hala yığında olan), desen yeskullan , yoksa desen kullan no" gibi koşullu bir kalıp .

Yani yukarıdaki modelimizin sonuna, -stack boş (?(Open)failPattern)değilse, tüm desenin başarısız olmasına neden olacak gibi bir şey ekleyebiliriz Open. Deseni koşulsuz olarak başarısız kılacak en basit şey (?!)(boş bir negatif önden bakış). Böylece son modelimiz var:

^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$

Bu koşullu sözdiziminin kendi başına grupların dengelenmesiyle ilgisi olmadığını, ancak grupların tüm gücünü kullanmak gerektiğini unutmayın.

Buradan gökyüzü sınırdır. Pek çok karmaşık kullanım mümkündür ve değişken uzunluklu bakışlar gibi diğer .NET-Regex özellikleriyle birlikte kullanıldığında bazı sorunlar vardır ( ki bunu kendim zor yoldan öğrenmem gerekiyordu ). Ancak asıl soru her zaman şudur: Bu özellikleri kullanırken kodunuz hala korunabilir mi? Bunu gerçekten iyi bir şekilde belgelemeniz ve üzerinde çalışan herkesin de bu özelliklerin farkında olduğundan emin olmanız gerekir. Aksi takdirde, dizeyi karakter karakter manuel olarak yürüterek ve bir tam sayıdaki yuvalama düzeylerini sayarak daha iyi durumda olabilirsiniz.

Eklenti: (?<A-B>...)Sözdiziminde ne var ?

Bu bölüm için krediler Kobi'ye gider (daha fazla ayrıntı için aşağıdaki cevabına bakın).

Şimdi yukarıdakilerin tümü ile, bir dizenin doğru şekilde parantez içine alındığını doğrulayabiliriz. Ancak tüm bu parantez içerikleri için gerçekten yakalamalar (iç içe geçmiş) alabilseydik, çok daha yararlı olurdu. Elbette, parantezleri boşaltılmamış ayrı bir yakalama yığınında açıp kapattığımızı ve daha sonra ayrı bir adımda konumlarına göre bazı alt dize çıkarımlarını yaptığımızı hatırlayabiliriz.

Ancak .NET, burada bir başka kolaylık özelliği daha sağlar: Eğer kullanırsak (?<A-B>subPattern), yalnızca yığından bir yakalama değil B, aynı zamanda bu patlamış yakalama Bile bu mevcut grup arasındaki her şey yığına itilir A. Öyleyse, parantezleri kapatmak için böyle bir grup kullanırsak ve yığınımızdan iç içe geçme seviyelerini çıkarırken, çiftin içeriğini başka bir yığına da itebiliriz:

^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$

Kobi , cevabında bu Canlı Demoyu sağladı

Tüm bunları bir araya getirerek şunları yapabiliriz:

  • Keyfi olarak birçok yakalamayı hatırla
  • İç içe geçmiş yapıları doğrulayın
  • Her yuvalama seviyesini yakalayın

Hepsi tek bir düzenli ifadede. Heyecan verici değilse ...;)

Onları ilk öğrendiğimde faydalı bulduğum bazı kaynaklar:


7
Bu yanıt, "Gelişmiş Regex-Fu" altındaki Yığın Taşması Normal İfadeler SSS bölümüne eklenmiştir .
aliteralmind

40

M. Buettner'ın mükemmel cevabına sadece küçük bir katkı:

(?<A-B>)Sözdizimi ile anlaşma nedir ?

(?<A-B>x)biraz farklıdır (?<-A>(?<B>x)). Aynı kontrol akışıyla * sonuçlanırlar , ancak farklı şekilde yakalarlar .
Örneğin, dengeli diş telleri için bir modele bakalım:

(?:[^{}]|(?<B>{)|(?<-B>}))+(?(B)(?!))

Maçın sonunda dengeli bir dizimiz var, ama elimizde olan tek şey bu - küme parantezlerinin nerede olduğunu bilmiyoruz çünkü Byığın boş. Motorun bizim için yaptığı zor iş gitti.
( Regex Storm'da örnek )

(?<A-B>x)bu sorunun çözümü. Nasıl? O değil yakalamak xiçine $Ao önceki yakalama arasında içerik yakalar: Bve mevcut konumda.

Bunu modelimizde kullanalım:

(?:[^{}]|(?<Open>{)|(?<Content-Open>}))+(?(Open)(?!))

Bu $Content, yol boyunca her bir çift için parantezler (ve konumları) arasındaki dizeleri yakalayacaktır .
Dize için {1 2 {3} {4 5 {6}} 7}olurdu dört yakalar: 3, 6, 4 5 {6}, ve 1 2 {3} {4 5 {6}} 7- çok daha iyi daha hiçbir şey ya } } } }.
( örnek - tablesekmeyi tıklayın ve bakın ${Content}, yakalar )

Aslında, hiç dengelenmeden kullanılabilir: (?<A>).(.(?<Content-A>).)gruplarla ayrılmış olsalar bile ilk iki karakteri yakalar.
(Önden okuma burada daha yaygın olarak kullanılır, ancak her zaman ölçeklenmez: mantığınızı çoğaltabilir.)

(?<A-B>)güçlü bir özelliktir - çekimleriniz üzerinde size tam kontrol sağlar. Düzeninizden daha fazlasını elde etmeye çalışırken bunu aklınızda bulundurun.


@FYI gelen tartışmayı devam sen sevmedim soru bu birinde yeni bir cevap. :)
zx81

Dizelerin içindeki parantezlerden kaçarak dengeli parantez regex kontrolü yapmanın bir yolunu bulmaya çalışıyorum. Örneğin, aşağıdaki kod geçecektir: public class Foo {private const char BAR = '{'; private string _qux = "{{{"; } Bunu kimse yaptı mı?
Bay Anderson

@MrAnderson - Sadece |'[^']*'doğru yere eklemeniz gerekiyor : örnek . Ayrıca çıkış karakterlerine ihtiyacınız varsa, burada bir örnek var: (C # dize değişmezleriyle eşleşen normal ifade) [ stackoverflow.com/a/4953878/7586] .
Kobi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.