Czym są grupy równoważenia wyrażeń regularnych?

Właśnie czytałem pytanie o to, jak uzyskać dane w podwójnych nawiasach klamrowych (to pytanie ), a potem ktoś wspomniał o grupach równoważących. Nadal nie jestem do końca pewien, czym są i jak z nich korzystać.

Czytałem Równoważenie definicji grupy, ale wyjaśnienie jest trudne do naśladowania, a ja nadal jestem dość zdezorientowany w kwestiach, które wspomniałem.

Mógłby ktoś po prostu wyjaśnić czym są grupy balansujące i w jaki sposób są przydatne?

Author: Community, 2013-06-09

2 answers

Z tego, co wiem, grupy równoważące są unikalne dla. NET smaku regex.

Na Bok: Powtórzone Grupy

Po pierwsze, musisz wiedzieć ,że. NET jest (znowu, o ile wiem) jedynym regex flavor, który pozwala uzyskać dostęp do wielu przechwytywań jednej grupy przechwytywania(Nie w backreferences, ale po zakończeniu meczu).

Aby zilustrować to na przykładzie, rozważ wzór

(.)+

I łańcuch "abcd".

We wszystkich innych smakach regex, przechwytywanie grupy 1 da po prostu jeden wynik: d (Uwaga, pełne dopasowanie będzie oczywiście abcd zgodnie z oczekiwaniami). Dzieje się tak, ponieważ każde nowe użycie grupy przechwytywania zastępuje poprzednie przechwytywanie.

. NET z drugiej strony pamięta je wszystkie. I robi to w stosie. Po dopasowaniu powyższego wyrażenia regex jak

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

Przekonasz się, że

m.Groups[1].Captures

Jest CaptureCollection, którego elementy odpowiadają czterem ujęciom

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

Gdzie liczba jest indeksem do CaptureCollection. Więc w zasadzie za każdym razem, gdy grupa jest używana ponownie, nowe Przechwytywanie jest popychane na stos.

Robi się ciekawiej, jeśli używamy nazwanych grup przechwytywania. Ponieważ. NET pozwala na wielokrotne użycie tej samej nazwy, możemy napisać regex jak

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

Aby uchwycić dwa słowa w tej samej grupie. Ponownie, za każdym razem, gdy napotkana jest grupa o określonej nazwie, Przechwytywanie jest popychane na jej stos. Tak więc stosowanie tego wyrażenia regularnego do wejścia "foo bar" i sprawdzanie

m.Groups["word"].Captures

We znajdź dwa ujęcia

0: "foo"
1: "bar"

To pozwala nam nawet wcisnąć rzeczy na jeden stos z różnych części wyrażenia. Mimo to, jest to po prostu funkcja. NET, która umożliwia śledzenie wielu przechwyceń, które są wymienione w tym CaptureCollection. Ale powiedziałem, że ta kolekcja to stos . Więc możemy zrobić z tego coś?

Enter: Balancing Groups

Okazuje się, że możemy. Jeśli użyjemy grupy podobnej do (?<-word>...), to ostatnie przechwycenie jest usuwane ze stosu word jeśli podwyrażenie ... pasuje. Jeśli więc zmienimy nasze poprzednie wyrażenie na
(?<word>\w+)\W+(?<-word>\w+)

Następnie druga grupa spowoduje przechwycenie pierwszej grupy, a na końcu otrzymamy pusty CaptureCollection. Oczywiście ten przykład jest dość bezużyteczny.

Ale jest jeszcze jeden szczegół do składni minus: jeśli stos jest już pusty, grupa nie powiedzie się (niezależnie od jej podpatrywki). Możemy wykorzystać to zachowanie do liczenia poziomów zagnieżdżania - i stąd pochodzi nazwa grupy równoważenia od (i gdzie robi się ciekawie). Powiedzmy, że chcemy dopasować łańcuchy, które są poprawnie nawiasowane. Wciskamy każdy nawias otwierający na stosie, a dla każdego nawiasu zamykającego dodajemy po jednym ujęciu. Jeśli natkniemy się na jeden nawias zamykający za dużo, będzie on próbował pop pusty stos i spowodować, że wzorzec się nie powiedzie: {]}

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

Mamy więc trzy alternatywy w powtórzeniu. Pierwsza alternatywa konsumuje wszystko, co nie jest nawiasem. Druga alternatywa pasuje ( s podczas wepchnięcie ich na stos. Trzecia alternatywa dopasowuje ) s podczas wyskakiwania elementów ze stosu (jeśli to możliwe!).

Uwaga: dla jasności, sprawdzamy tylko, czy nie ma niezrównanych nawiasów! Oznacza to, że ciąg znaków nie zawierający nawiasów będzie zgodny z , ponieważ nadal jest poprawny składniowo(w niektórych składniach, w których musisz dopasować nawiasy). Jeśli chcesz zapewnić co najmniej jeden zestaw nawiasów, po prostu dodaj lookahead (?=.*[(]) zaraz po ^.

Ten wzór nie jest jednak doskonały (lub całkowicie poprawny).

Finał: Wzory Warunkowe

Jest jeszcze jeden haczyk: nie zapewnia to, że stos jest pusty na końcu łańcucha (stąd (foo(bar) będzie ważny). . NET (i wiele innych smaków) ma jeszcze jedną konstrukcję, która nam tutaj pomaga: szablony warunkowe. Ogólna składnia to

(?(condition)truePattern|falsePattern)

Gdzie {[29] } jest opcjonalne - jeśli zostanie pominięte false-case zawsze będzie pasować. Warunek może być wzorem lub nazwą grupy przechwytywania. Skupię się na tej drugiej sprawie. Jeśli jest to nazwa grupy przechwytywania, to truePattern jest używana wtedy i tylko wtedy, gdy stos przechwytywania dla danej grupy nie jest pusty. To znaczy, warunkowy wzorzec jak (?(name)yes|no) brzmi " jeśli name dopasowało i przechwyciło coś (co nadal jest na stosie), użyj wzorzec yes w przeciwnym razie użyj wzorzec no".

Więc na końcu powyższego wzoru możemy dodać coś w stylu (?(Open)failPattern), co powoduje, że cały wzorzec nie działa, jeśli Open-stos nie jest pusty. Najprostszą rzeczą, aby wzorzec bezwarunkowo zawieść jest (?!) (pusty negatywny wygląd). Mamy więc nasz ostateczny wzór:

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

Zauważ, że ta składnia warunkowa nie ma nic wspólnego z grupami równoważącymi, ale konieczne jest wykorzystanie ich pełnej mocy.

Stąd, niebo jest granicą. Możliwe jest wiele bardzo wyrafinowanych zastosowań i są pewne Gotcha, gdy używany w połączeniu z innymi funkcjami.Net-Regex, takimi jak lookbehinds o zmiennej długości (, których sam musiałem się ciężko nauczyć). Głównym pytaniem jest jednak zawsze: czy Twój kod jest nadal możliwy do utrzymania podczas korzystania z tych funkcji? Musisz to dobrze udokumentować i mieć pewność, że każdy, kto nad tym pracuje, jest również świadomy tych funkcji. W przeciwnym razie może być lepiej, po prostu chodzenie łańcuch ręcznie znak po znaku i liczenia poziomów zagnieżdżenia w liczba całkowita.

Dodatek: o co chodzi ze składnią (?<A-B>...)?

Napisy do tej części trafiają do Kobi (zobacz jego odpowiedź poniżej, aby uzyskać więcej szczegółów).

Teraz z wszystkimi powyższymi, możemy sprawdzić, czy łańcuch jest poprawnie nawiasowany. Ale byłoby to o wiele bardziej użyteczne, gdybyśmy mogli rzeczywiście uzyskać (zagnieżdżone) przechwytywania dla wszystkich treści tych nawiasów. Oczywiście możemy pamiętać otwieranie i zamykanie nawiasów w oddzielnym stosie przechwytywania, który nie jest opróżniony, a następnie wykonać kilka ekstrakcja podłańcuchów na podstawie ich pozycji w osobnym kroku.

Ale. Net zapewnia jeszcze jedną wygodną funkcję: jeśli użyjemy (?<A-B>subPattern), nie tylko przechwytywanie jest wyskakiwane ze stosu B, ale także wszystko pomiędzy przechwytywaniem B a obecną grupą jest wypychane na stos A. Więc jeśli użyjemy takiej grupy do nawiasów zamykających, podczas gdy pojawimy się poziomy zagnieżdżania z naszego stosu, możemy również wypchnąć zawartość pary na inny stos:

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

Kobi dostarczył to Live-Demo w swojej odpowiedzi

Więc biorąc wszystkie te rzeczy razem możemy:

  • Zapamiętaj dowolnie wiele ujęć
  • Walidacja zagnieżdżonych struktur
  • Przechwytywanie każdego poziomu zagnieżdżania

Wszystko w jednym wyrażeniu regularnym. Jeśli to nie ekscytujące... ;)

Niektóre zasoby, które uznałem za pomocne, gdy po raz pierwszy dowiedziałem się o oni:

 160
Author: Martin Ender,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:17:43

Tylko mały dodatek do doskonałej odpowiedzi M. Buettnera:

O co chodzi ze składnią (?<A-B>)?

(?<A-B>x) subtelnie różni się od (?<-A>(?<B>x)). Skutkują tym samym przepływem sterowania*, ale oni pojmują inaczej.
Na przykład spójrzmy na wzór dla wyważonych aparatów ortodontycznych:

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

Pod koniec meczu mamy zrównoważony ciąg, ale to wszystko, co mamy - nie wiemy gdzie szelki są Ponieważ B stosu jest pusty. Ciężka praca silnika dla nas przepadła.
(przykład NA Storm Regex)

(?<A-B>x) jest rozwiązaniem tego problemu. Jak? To nie przechwytuje x do $A: przechwytuje zawartość pomiędzy poprzednim przechwyceniem B a bieżącą pozycją.

Użyjmy go w naszym wzorze:

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

To uchwyciłoby na $Content struny między szelkami( i ich pozycjami), dla każdej pary po drodze.
Na string {[11] }będą cztery ujęcia: 3, 6 ,4 5 {6} , i 1 2 {3} {4 5 {6}} 7 - znacznie lepsze niż nic lub } } } }.
(przykład-kliknij kartę table i spójrz na ${Content}, przechwytuje)

W rzeczywistości może być używany bez balansowania w ogóle: (?<A>).(.(?<Content-A>).) przechwytuje dwa pierwsze znaki, mimo że są one oddzielone grupami.
(lookahead jest tu częściej używany, ale nie zawsze się skaluje: może powielać Twoje logika.)

(?<A-B>) jest mocną cechą-daje dokładną kontrolę nad swoimi ujęciami. Miej to na uwadze, gdy próbujesz wydobyć więcej ze swojego wzorca.

 37
Author: Kobi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-09 17:52:18