--Edytuj-- Obecne odpowiedzi zawierają kilka przydatnych pomysłów, ale chcę czegoś bardziej kompletnego, co mogę w 100% zrozumieć i ponownie wykorzystać; dlatego wyznaczyłem nagrodę. Również pomysły, które działają wszędzie, są dla mnie lepsze niż niestandardowa składnia, taka jak\K
To pytanie dotyczy tego, jak mogę dopasować wzorzec z wyjątkiem niektórych sytuacji s1 s2 s3. Podaję konkretny przykład, aby pokazać swoje znaczenie, ale wolę ogólną odpowiedź, którą mogę w 100% zrozumieć, aby móc jej użyć ponownie w innych sytuacjach.
Przykład
Chcę dopasować pięć cyfr, używając, \b\d{5}\b
ale nie w trzech sytuacjach s1 s2 s3:
s1: Nie w wierszu, który kończy się kropką jak to zdanie.
s2: Nigdzie wewnątrz parens.
s3: Nie wewnątrz bloku, który zaczyna się if(
i kończy na//endif
Wiem, jak rozwiązać każdy z s1 s2 s3 z lookahead i lookbehind, szczególnie w C # lookbehind lub \K
w PHP.
Na przykład
s1 (?m)(?!\d+.*?\.$)\d+
s3 z C # lookbehind (?<!if\(\D*(?=\d+.*?//endif))\b\d+\b
s3 z PHP \ K (?:(?:if\(.*?//endif)\D*)*\K\d+
Ale mieszanka warunków sprawia, że moja głowa eksploduje. Jeszcze bardziej zła wiadomość jest taka, że być może będę musiał dodać inne warunki s4 s5 w innym czasie.
Dobra wiadomość jest taka, że nie obchodzi mnie, czy przetwarzam pliki przy użyciu najpopularniejszych języków, takich jak PHP, C #, Python lub pralka mojego sąsiada. :) Jestem prawie początkującym w Pythonie i Javie, ale jestem zainteresowany, aby dowiedzieć się, czy ma rozwiązanie.
Więc przyjechałem tutaj, żeby sprawdzić, czy ktoś wymyśli elastyczny przepis.
Podpowiedzi są w porządku: nie musisz podawać mi pełnego kodu. :)
Dziękuję Ci.
\K
nie ma specjalnej składni php. Proszę rozwinąć i wyjaśnić, co chcesz powiedzieć. Jeśli chcesz nam powiedzieć, że nie potrzebujesz „skomplikowanego” rozwiązania, musisz powiedzieć, co jest dla Ciebie skomplikowane i dlaczego."if("
otwarty paren był zamknięty, nie za pomocą a")"
, ale raczej za"//endif"
:? A jeśli dla s3 naprawdę miałeś na myśli, że klauzula if powinna być zamknięta"//endif)"
:, to wymaganie s3 jest podzbiorem s2.especially in C# lookbehind or \K in PHP
... Ale C # lookbehind nie tylko C # to .NET, więc możesz też narzekać, mówię C # nie .NET :) A w odpowiedzi mówię Ruby, a nie Onigurama, to też jest złe ... Czy jest inny język, który używa PCRE? Nie mówię o Notepad ++ lub narzędziach serwera, to jest pytanie o użycie funkcji w języku Mam nadzieję, że wyjaśnię i przepraszam, jeśli wygląda źleOdpowiedzi:
Hans, wezmę przynętę i wypoleruję moją wcześniejszą odpowiedź. Powiedziałeś, że chcesz „czegoś bardziej kompletnego”, więc mam nadzieję, że nie masz nic przeciwko długiej odpowiedzi - po prostu próbując zadowolić. Zacznijmy od pewnego tła.
Po pierwsze, to doskonałe pytanie. Często pojawiają się pytania dotyczące dopasowywania pewnych wzorców, z wyjątkiem określonych kontekstów (na przykład w bloku kodu lub w nawiasach). Te pytania często prowadzą do dość niewygodnych rozwiązań. Twoje pytanie dotyczące wielu kontekstów jest więc szczególnym wyzwaniem.
Niespodzianka
Zaskakujące jest, że istnieje co najmniej jedno wydajne rozwiązanie, które jest ogólne, łatwe do wdrożenia i przyjemne w utrzymaniu. To działa ze wszystkimi smakami regex , które pozwalają kontrolować grupy przechwytywania w kodzie. I zdarza się, że odpowiada na wiele typowych pytań, które na pierwszy rzut oka mogą brzmieć inaczej niż twoje: „dopasuj wszystko oprócz pączków”, „zamień wszystko oprócz ...”, „dopasuj wszystkie słowa oprócz tych z czarnej listy mojej mamy”, „ignoruj tagi "," dopasuj temperaturę, chyba że są zapisane kursywą "...
Niestety, technika ta nie jest dobrze znana: szacuję, że na dwadzieścia pytań SO, które mogłyby jej użyć, tylko jedno ma odpowiedź, która o niej wspomina - co oznacza, że może jedna na pięćdziesiąt lub sześćdziesiąt odpowiedzi. Zobacz moją wymianę z Kobi w komentarzach. Technika ta jest szczegółowo opisana w tym artykule, który nazywa ją (optymistycznie) „najlepszą sztuczką wyrażeń regularnych wszechczasów”. Nie wdając się w szczegóły, spróbuję dokładnie zrozumieć, jak działa ta technika. Aby uzyskać więcej szczegółów i przykłady kodu w różnych językach, zachęcam do zapoznania się z tym źródłem.
Lepiej znana odmiana
Istnieje odmiana składni specyficzna dla Perla i PHP, która zapewnia to samo. Zobaczysz to w SO w rękach mistrzów regex, takich jak CasimiretHippolyte i HamZa . Powiem ci więcej na ten temat poniżej, ale skupiam się tutaj na ogólnym rozwiązaniu, które działa ze wszystkimi odmianami wyrażeń regularnych (o ile możesz sprawdzić grupy przechwytywania w swoim kodzie).
Kluczowy fakt
W rzeczywistości sztuczka polega na dopasowaniu różnych kontekstów, których nie chcemy (łącząc te konteksty w łańcuch za pomocą
|
OR / alternacji), aby je „zneutralizować”. Po dopasowaniu wszystkich niechcianych kontekstów ostatnia część naprzemienności dopasowuje to, czego chcemy i przechwytuje to do grupy 1.Ogólny przepis to
To będzie pasować
Not_this_context
, ale w pewnym sensie mecz trafia do kosza, ponieważ nie będziemy patrzeć na ogólne mecze: patrzymy tylko na przechwyty w grupie 1.W twoim przypadku, z twoimi cyframi i trzema kontekstami do zignorowania, możemy zrobić:
Zauważ, że ponieważ w rzeczywistości dopasowujemy s1, s2 i s3 zamiast próbować ich uniknąć za pomocą lookarounds, poszczególne wyrażenia dla s1, s2 i s3 mogą pozostać jasne jak dzień. (Są to podwyrażenia po każdej stronie a
|
)Całe wyrażenie można zapisać w ten sposób:
Zobacz to demo (ale skup się na grupach przechwytywania w prawym dolnym panelu).
Jeśli spróbujesz w myślach podzielić to wyrażenie regularne w każdym
|
separatorze, w rzeczywistości jest to tylko seria czterech bardzo prostych wyrażeń.W przypadku smaków obsługujących wolne odstępy jest to szczególnie dobre.
Jest to wyjątkowo łatwe do odczytania i utrzymania.
Rozszerzanie wyrażenia regularnego
Jeśli chcesz zignorować więcej sytuacji s4 i s5, dodaj je w większej liczbie naprzemienności po lewej stronie:
Jak to działa?
Konteksty, których nie chcesz, są dodawane do listy wariantów po lewej stronie: będą pasować, ale te ogólne dopasowania nigdy nie są sprawdzane, więc dopasowanie ich jest sposobem na umieszczenie ich w „koszu na śmieci”.
Jednak żądana zawartość jest przechwytywana do grupy 1. Następnie musisz programowo sprawdzić, czy grupa 1 jest ustawiona, a nie pusta. Jest to trywialne zadanie programistyczne (a później porozmawiamy o tym, jak to się robi), zwłaszcza biorąc pod uwagę, że pozostawia ono proste wyrażenie regularne, które można zrozumieć na pierwszy rzut oka i zmienić lub rozszerzyć zgodnie z wymaganiami.
Nie zawsze jestem fanem wizualizacji, ale ta dobrze pokazuje, jak prosta jest metoda. Każda „linia” odpowiada potencjalnemu dopasowaniu, ale tylko dolna linia jest uwzględniana w grupie 1.
Debuggex Demo
Odmiana Perl / PCRE
W przeciwieństwie do powyższego ogólnego rozwiązania, istnieje odmiana Perla i PCRE, która jest często widoczna w SO, przynajmniej w rękach regex Gods, takich jak @CasimiretHippolyte i @HamZa. To jest:
W Twoim przypadku:
Ta odmiana jest nieco łatwiejsza w użyciu, ponieważ treść dopasowana w kontekstach s1, s2 i s3 jest po prostu pomijana, więc nie musisz sprawdzać przechwyceń grupy 1 (zwróć uwagę, że nawiasy zniknęły). Zapałki zawierają tylko
whatYouWant
Należy zauważyć, że
(*F)
,(*FAIL)
i(?!)
są tym samym. Jeśli chcesz być bardziej niejasny, możesz użyć(*SKIP)(?!)
demo dla tej wersji
Aplikacje
Oto kilka typowych problemów, które ta technika często może łatwo rozwiązać. Zauważysz, że wybór słów może sprawić, że niektóre z tych problemów brzmią inaczej, podczas gdy w rzeczywistości są one praktycznie identyczne.
<a stuff...>...</a>
?<i>
tagiem lub fragmentem kodu javascript (więcej warunków)?Jak zaprogramować przechwyty w grupie 1
Nie zrobiłeś tego, jeśli chodzi o kod, ale do ukończenia ... Kod do inspekcji grupy 1 będzie oczywiście zależał od wybranego języka. W każdym razie nie powinien dodawać więcej niż kilka wierszy do kodu, którego używałbyś do sprawdzania dopasowań.
W razie wątpliwości polecam zajrzeć do sekcji z przykładami kodu we wspomnianym wcześniej artykule, która przedstawia kod dla kilku języków.
Alternatywy
W zależności od złożoności pytania i używanego silnika wyrażeń regularnych istnieje kilka alternatyw. Oto dwa, które można zastosować w większości sytuacji, w tym w wielu warunkach. Moim zdaniem żadna z nich nie jest tak atrakcyjna jak
s1|s2|s3|(whatYouWant)
przepis, choćby dlatego, że zawsze zwycięża przejrzystość.1. Wymień, a następnie dopasuj.
Dobrym rozwiązaniem, które brzmi hakersko, ale działa dobrze w wielu środowiskach, jest dwuetapowa praca. Pierwsze wyrażenie regularne neutralizuje kontekst, który chcesz zignorować, zastępując potencjalnie sprzeczne ciągi. Jeśli chcesz tylko dopasować, możesz zastąpić pustym ciągiem, a następnie uruchomić dopasowanie w drugim kroku. Jeśli chcesz zamienić, możesz najpierw zamienić ciągi, które mają być ignorowane, na coś charakterystycznego, na przykład otoczenie cyfr łańcuchem o stałej szerokości
@@@
. Po tej zamianie możesz zamienić to, czego naprawdę chciałeś, a następnie będziesz musiał przywrócić swoje charakterystyczne@@@
struny.2. Lookarounds.
Twój oryginalny post pokazał, że wiesz, jak wykluczyć pojedynczy warunek za pomocą obejrzeń. Powiedziałeś, że C # jest do tego świetny i masz rację, ale nie jest to jedyna opcja. Formy wyrażeń regularnych .NET znalezione na przykład w C #, VB.NET i Visual C ++, a także wciąż eksperymentalny
regex
moduł do zastąpieniare
w Pythonie, to jedyne dwa znane mi silniki, które obsługują lookbehind o nieskończonej szerokości. Dzięki tym narzędziom jeden warunek w jednym spojrzeniu wstecz może zająć się patrzeniem nie tylko za mecz, ale także na mecz i poza mecz, unikając potrzeby koordynacji z patrzeniem w przód. Więcej warunków? Więcej obejść.Recykling wyrażenia regularnego, które miałeś dla s3 w C #, cały wzorzec wyglądałby tak.
Ale teraz już wiesz, że tego nie polecam, prawda?
Usunięcia
@HamZa i @Jerry zasugerowali, żebym wspomniał o dodatkowej sztuczce w przypadku, gdy chcesz po prostu usunąć
WhatYouWant
. Pamiętasz, że przepis na dopasowanieWhatYouWant
(przechwycenie go do grupy 1) byłs1|s2|s3|(WhatYouWant)
, prawda? Aby usunąć wszystkie wystąpieniaWhatYouWant
, należy zmienić wyrażenie regularne naW przypadku ciągu zastępczego używasz
$1
. To, co się tutaj dzieje, to to, że dla każdegos1|s2|s3
dopasowanego wystąpienia , zamiana$1
zastępuje to wystąpienie sobą (do którego odwołuje się$1
). Z drugiej strony, gdyWhatYouWant
jest dopasowany, jest zastępowany przez pustą grupę i nic więcej - i dlatego jest usuwany. Obejrzyj to demo , dziękuję @HamZa i @Jerry za zasugerowanie tego wspaniałego dodatku.Części zamienne
To prowadzi nas do zamienników, o których pokrótce się poruszę.
(*SKIP)(*F)
wariantu wspomnianego powyżej, aby dokładnie dopasować to, co chcesz, i wykonaj prostą wymianę.Baw się dobrze!
Nie, czekaj, jest więcej!
Ach, nie, zachowam to dla moich wspomnień w dwudziestu tomach, które zostaną wydane wiosną przyszłego roku.
źródło
Tarzan
, ale nie w żadnym miejscu w cudzysłowie. Wyrażenie/no|no|(yes)/
regularne : trick wyglądałoby następująco:/"[^"]*"|Tarzan/
(ignorując znaki ucieczki). To będzie działać na wielu przypadkach, ale nie całkowicie, gdy stosuje się do następnego ważnego tekstu javascript:var bug1 = 'One " quote here. Should match this Tarzan'; var bug2 = "Should not match this Tarzan";
. Sztuczka Rexa działa tylko wtedy, gdy WSZYSTKIE możliwe struktury są dopasowane - innymi słowy - musisz w pełni przeanalizować tekst, aby zagwarantować 100% dokładność.var bug1 = /"[^"]*"|(Tarzan)/gi;
i miał ten sam efekt (a ten drugi przykład z pewnością nie jest przypadkiem skrajnym). Mógłbym przytoczyć wiele innych przykładów, w których ta technika nie działa niezawodnie.(?<!\\)"(?:\\"|[^"\r\n])*+"
Nie wyciągaj dużych dział, chyba że masz ku temu powód. Zasada rozwiązania jest nadal aktualna. Jeśli nie jesteśmy w stanie wyrazić wzoru do umieszczenia po lewej stronie, to inna historia, potrzebujemy innego rozwiązania. Ale rozwiązanie robi to, co reklamuje.Wykonaj trzy różne dopasowania i obsłuż kombinację trzech sytuacji, używając logiki warunkowej w programie. Nie musisz obsługiwać wszystkiego w jednym gigantycznym wyrażeniu regularnym.
EDYCJA: pozwól mi trochę rozszerzyć, ponieważ pytanie stało się bardziej interesujące :-)
Ogólną ideą, którą próbujesz tutaj uchwycić, jest dopasowanie do określonego wzorca wyrażenia regularnego, ale nie wtedy, gdy istnieją pewne inne (może to być dowolna liczba) wzorce obecne w ciągu testowym. Na szczęście możesz skorzystać ze swojego języka programowania: zachowaj proste wyrażenia regularne i po prostu użyj złożonego warunku. Najlepszą praktyką byłoby uchwycenie tego pomysłu w składniku wielokrotnego użytku, więc stwórzmy klasę i metodę, która ją implementuje:
Tak więc powyżej ustawiliśmy ciąg wyszukiwania (pięć cyfr), wiele ciągów wyjątków (twoje s1 , s2 i s3 ), a następnie spróbujemy dopasować je do kilku ciągów testowych. Wydrukowane wyniki powinny być takie, jak pokazano w komentarzach obok każdego testu.
źródło
Twoje wymaganie, aby nie było w środku parens, jest niemożliwe do spełnienia we wszystkich przypadkach. Mianowicie, jeśli w jakiś sposób możesz znaleźć
(
po lewej i)
po prawej stronie, nie zawsze oznacza to, że jesteś w środku parens. Na przykład.(....) + 55555 + (.....)
- nie wewnątrz parenów jeszcze są(
i)
na lewo i na prawoTeraz możesz uważać się za sprytnego i szukać
(
w lewo tylko wtedy, gdy nie napotkasz)
wcześniej i odwrotnie, po prawej. To nie zadziała w tym przypadku:((.....) + 55555 + (.....))
- pareny wewnętrzne, mimo że są zamykane)
i(
na lewo i na prawo.Nie można dowiedzieć się, czy jesteś wewnątrz parenów, używając wyrażenia regularnego, ponieważ wyrażenie regularne nie może policzyć, ile parenów zostało otwartych i ile zamkniętych.
Rozważ to łatwiejsze zadanie: używając wyrażenia regularnego, dowiedz się, czy wszystkie (prawdopodobnie zagnieżdżone) pareny w łańcuchu są zamknięte, czyli dla każdego
(
, co musisz znaleźć)
. Dowiesz się, że jest to niemożliwe do rozwiązania, a jeśli nie możesz tego rozwiązać za pomocą wyrażenia regularnego, nie możesz dowiedzieć się, czy słowo znajduje się wewnątrz parens dla wszystkich przypadków, ponieważ nie możesz znaleźć w jakiejś pozycji w ciągu, jeśli wszystkie poprzedzające(
mają odpowiednik)
.źródło
Hans, jeśli nie masz nic przeciwko, użyłem pralki twojego sąsiada o nazwie perl :)
Edytowano: poniżej pseudokodu:
Biorąc pod uwagę plik input.txt:
Oraz skrypt validator.pl:
Wykonanie:
źródło
Nie jestem pewien, czy to ci pomoże, czy nie, ale zapewniam rozwiązanie, biorąc pod uwagę następujące założenia -
Jednak wziąłem pod uwagę również następujące -
if(
bloków.OK, oto rozwiązanie -
Użyłem C #, a wraz z nim MEF (Microsoft Extensibility Framework), aby zaimplementować konfigurowalne parsery. Pomysł polega na tym, że do analizy składniowej należy użyć jednego parsera oraz listy konfigurowalnych klas walidatora, aby sprawdzić poprawność wiersza i zwrócić wartość true lub false na podstawie walidacji. Następnie możesz dodać lub usunąć dowolny walidator w dowolnym momencie lub dodać nowy, jeśli chcesz. Do tej pory zaimplementowałem już dla S1, S2 i S3, o których wspominałeś, sprawdź klasy w punkcie 3. Musisz dodać klasy dla s4, s5, jeśli będziesz potrzebować w przyszłości.
Najpierw utwórz interfejsy -
Następnie pojawia się czytnik plików i sprawdzanie -
Potem pojawia się implementacja poszczególnych warcabów, nazwy klas są oczywiste, więc nie sądzę, aby potrzebowały więcej opisów.
Program -
Do testów wziąłem przykładowy plik @ Tiago,
Test.txt
który miał następujące linie -Daje wynik -
Nie wiem, czy to ci pomogło, czy nie, bawiłem się dobrze, bawiąc się tym .... :)
Najlepsze w tym jest to, że aby dodać nowy warunek, wystarczy podać implementację
IPatternMatcher
, zostanie on automatycznie wywołany, a tym samym zostanie zweryfikowany.źródło
To samo, co @ zx81,
(*SKIP)(*F)
ale z użyciem negatywnego potwierdzenia antycypowania.PRÓBNY
W Pythonie zrobiłbym to z łatwością,
Wynik:
źródło