Właśnie czytałem pytanie o to, jak uzyskać dane w podwójnych nawiasach klamrowych ( to pytanie ), a potem ktoś poruszył grupy równoważące. Nadal nie jestem do końca pewien, czym one są i jak ich używać.
Przeczytałem definicję grupy balansującej , ale wyjaśnienie jest trudne do zrozumienia i nadal jestem dość zdezorientowany w pytaniach, które wspomniałem.
Czy ktoś mógłby po prostu wyjaśnić, czym są grupy równoważące i do czego są przydatne?
c#
.net
regex
balancing-groups
To NIE jest.
źródło
źródło
Odpowiedzi:
O ile wiem, grupy równoważące są unikalne dla smaku regex .NET.
Poza tym: powtarzające się grupy
Po pierwsze, musisz wiedzieć, że .NET jest (znowu, o ile wiem) jedynym typem wyrażenia regularnego, który umożliwia dostęp do wielu przechwyceń jednej grupy przechwytywania (nie w odwołaniach wstecznych, ale po zakończeniu dopasowania).
Aby zilustrować to przykładem, rozważ wzór
i sznurek
"abcd"
.we wszystkich innych odmianach wyrażeń regularnych grupa przechwytywania
1
da po prostu jeden wynik:d
(uwaga, pełne dopasowanie będzie oczywiścieabcd
zgodne z oczekiwaniami). Dzieje się tak, ponieważ każde nowe użycie grupy przechwytywania zastępuje poprzednie przechwytywanie.Z drugiej strony .NET pamięta je wszystkie. I robi to w stosie. Po dopasowaniu powyższego wyrażenia regularnego, takiego jak
Match m = new Regex(@"(.)+").Match("abcd");
znajdziesz to
m.Groups[1].Captures
To element,
CaptureCollection
którego elementy odpowiadają czterem przechwyceniom0: "a" 1: "b" 2: "c" 3: "d"
gdzie liczba jest indeksem do
CaptureCollection
. Zasadniczo więc za każdym razem, gdy grupa jest ponownie używana, na stos odkładany jest nowy bicie.Staje się bardziej interesujące, jeśli używamy nazwanych grup przechwytywania. Ponieważ .NET pozwala na wielokrotne używanie tej samej nazwy, moglibyśmy napisać wyrażenie regularne, takie jak
aby umieścić dwa słowa w tej samej grupie. Ponownie, za każdym razem, gdy napotkana jest grupa o określonej nazwie, przechwycenie jest odkładane na jej stos. Więc stosując to wyrażenie regularne do danych wejściowych
"foo bar"
i sprawdzającm.Groups["word"].Captures
znajdujemy dwa ujęcia
0: "foo" 1: "bar"
To pozwala nam nawet umieszczać rzeczy na jednym stosie z różnych części wyrażenia. Ale nadal jest to tylko funkcja .NET, która umożliwia śledzenie wielu przechwyceń, które są wymienione w tym artykule
CaptureCollection
. Ale powiedziałem, ta kolekcja to stos . Więc czy możemy z tego wyskoczyć ?Enter: Balancing Groups
Okazuje się, że możemy. Jeśli użyjemy grupy podobnej do grupy
(?<-word>...)
, to ostatnie przechwycenie jest zdejmowane ze stosu,word
jeśli podwyrażenie...
pasuje. Więc jeśli zmienimy nasze poprzednie wyrażenie naNastępnie druga grupa wyskoczy z przechwytywania pierwszej grupy, a my
CaptureCollection
na końcu otrzymamy pusty . Oczywiście ten przykład jest dość bezużyteczny.Ale jest jeszcze jeden szczegół dotyczący składni minus: jeśli stos jest już pusty, grupa zawodzi (niezależnie od jej pod-wzorca). Możemy wykorzystać to zachowanie do liczenia poziomów zagnieżdżenia - i stąd pochodzi nazwa grupy równoważącej (i stąd robi się interesująca). Powiedzmy, że chcemy dopasować ciągi, które są poprawnie umieszczone w nawiasach. Wsuwamy każdy nawias otwierający na stos i usuwamy po jednym przechwyceniu dla każdego nawiasu zamykającego. Jeśli napotkamy jeden nawias zamykający za dużo, spróbuje zdjąć pusty stos i spowoduje niepowodzenie wzorca:
Mamy więc trzy możliwości w powtórzeniu. Pierwsza alternatywa pochłania wszystko, co nie jest nawiasem. Druga alternatywa dopasowuje
(
s, wpychając je na stos. Trzecia alternatywa pasuje do)
s podczas zdejmowania elementów ze stosu (jeśli to możliwe!).Uwaga: dla wyjaśnienia sprawdzamy tylko, czy nie ma niedopasowanych nawiasów! Oznacza to, że łańcuch nie zawierający w ogóle nawiasów będzie pasował, ponieważ nadal są one poprawne składniowo (w niektórych składniach, w których trzeba dopasować nawiasy). Jeśli chcesz zapewnić co najmniej jeden zestaw nawiasów, po prostu dodaj znak wyprzedzenia
(?=.*[(])
tuż po^
.Ten wzór nie jest jednak doskonały (ani całkowicie poprawny).
Finał: wzorce warunkowe
Jest jeszcze jeden haczyk: nie gwarantuje to, że stos jest pusty na końcu łańcucha (stąd
(foo(bar)
byłby prawidłowy). NET (i wiele innych odmian) ma jeszcze jedną konstrukcję, która pomaga nam tutaj: wzorce warunkowe. Ogólna składnia togdzie
falsePattern
jest opcjonalne - jeśli zostanie pominięte, zawsze będzie pasować. Warunek może być wzorcem lub nazwą grupy przechwytywania. Skoncentruję się tutaj na tym drugim przypadku. Jeśli jest to nazwa grupy przechwytywania,truePattern
jest używana wtedy i tylko wtedy, gdy stos przechwytywania dla tej konkretnej grupy nie jest pusty. Oznacza to, że wzorzec warunkowy, taki jak(?(name)yes|no)
reads, "jeśliname
dopasował i przechwycił coś (co nadal jest na stosie), użyj wzorca, wyes
przeciwnym razie użyj wzorcano
".Więc na końcu powyższego wzorca moglibyśmy dodać coś takiego,
(?(Open)failPattern)
co powoduje niepowodzenie całego wzorca, jeśliOpen
-stack nie jest pusty. Najprostszą rzeczą, która powoduje bezwarunkowe niepowodzenie wzorca, jest(?!)
(puste negatywne spojrzenie w przód). Mamy więc nasz ostateczny wzór:Zauważ, że ta warunkowa składnia nie ma per se nic wspólnego z równoważeniem grup, ale konieczne jest wykorzystanie ich pełnej mocy.
Stąd tylko niebo jest granicą. Możliwych jest wiele bardzo wyrafinowanych zastosowań i są pewne pułapki w połączeniu z innymi funkcjami .NET-Regex, takimi jak lookbehinds o zmiennej długości ( których sam musiałem się nauczyć ). Jednak główne pytanie zawsze brzmi: czy twój kod jest nadal możliwy do utrzymania podczas korzystania z tych funkcji? Musisz to naprawdę dobrze udokumentować i mieć pewność, że każdy, kto nad nim pracuje, jest również świadomy tych funkcji. W przeciwnym razie może być lepiej, po prostu przechodząc przez ciąg ręcznie znak po znaku i licząc poziomy zagnieżdżenia w liczbie całkowitej.
Dodatek: O co chodzi ze
(?<A-B>...)
składnią?Kredyty za tę część należą do Kobi (zobacz jego odpowiedź poniżej, aby uzyskać więcej informacji).
Teraz, mając wszystko powyższe, możemy sprawdzić, czy łańcuch jest poprawnie umieszczony w nawiasach. Byłoby jednak o wiele bardziej przydatne, gdybyśmy mogli faktycznie uzyskać (zagnieżdżone) przechwytywania dla wszystkich zawartości tych nawiasów. Oczywiście moglibyśmy zapamiętać otwieranie i zamykanie nawiasów w osobnym stosie przechwytywania, który nie jest opróżniany, a następnie w oddzielnym kroku wykonać pewne wyodrębnianie podciągów na podstawie ich pozycji.
Ale .NET zapewnia jeszcze jedną wygodną funkcję: jeśli używamy
(?<A-B>subPattern)
, nie tylko przechwytywanie jest usuwane ze stosuB
, ale także wszystko między tym przechwyceniemB
a bieżącą grupą jest wypychane na stosA
. Więc jeśli użyjemy takiej grupy jako nawiasów zamykających, podczas zdejmowania poziomów zagnieżdżenia z naszego stosu, możemy również wypchnąć zawartość pary na inny stos:Kobi dostarczył to Live-Demo w swojej odpowiedzi
Biorąc wszystkie te rzeczy razem, możemy:
Wszystko w jednym wyrażeniu regularnym. Jeśli to nie jest ekscytujące ...;)
Niektóre zasoby, które okazały się pomocne, gdy po raz pierwszy się o nich dowiedziałem:
źródło
Tylko mały dodatek do doskonałej odpowiedzi M. Buettnera:
O co chodzi ze
(?<A-B>)
składnią?(?<A-B>x)
różni się nieco od(?<-A>(?<B>x))
. Dają ten sam przepływ sterowania * , ale wychwytują inaczej.Na przykład spójrzmy na wzór dla zrównoważonych szelek:
Pod koniec dopasowania mamy zrównoważony ciąg, ale to wszystko, co mamy - nie wiemy, gdzie są nawiasy klamrowe, ponieważ
B
stos jest pusty. Ciężka praca, jaką wykonał za nas silnik, minęła.( przykład na Regex Storm )
(?<A-B>x)
jest rozwiązaniem tego problemu. W jaki sposób? To nie uchwycićx
w$A
: to oddaje zawartość między poprzednim wychwytywaniaB
i aktualnej pozycji.Wykorzystajmy to w naszym wzorze:
Spowoduje to przechwycenie
$Content
sznurków między klamrami (i ich pozycji) dla każdej pary po drodze.Dla łańcucha
{1 2 {3} {4 5 {6}} 7}
nie byłoby cztery zrzuty:3
,6
,4 5 {6}
, i1 2 {3} {4 5 {6}} 7
- znacznie lepiej niż nic lub}
}
}
}
.( przykład - kliknij
table
zakładkę i spójrz${Content}
, przechwytuje )W rzeczywistości można go używać bez balansowania w ogóle:
(?<A>).(.(?<Content-A>).)
przechwytuje pierwsze dwa znaki, nawet jeśli są oddzielone grupami.(Lookahead jest tutaj częściej używany, ale nie zawsze jest skalowany: może powielać twoją logikę).
(?<A-B>)
to mocna cecha - daje ci dokładną kontrolę nad twoimi zbiórek. Miej to na uwadze, gdy próbujesz wyciągnąć więcej ze swojego wzoru.źródło
|'[^']*'
we właściwym miejscu: przykład . Jeśli potrzebujesz również znaków ucieczki, jest tutaj przykład: (Regex dla pasujących literałów ciągu C #) [ stackoverflow.com/a/4953878/7586] .