Data publikacji w serwisie:

Wyrażenia regularne, czyli jak znaleźć wiele igieł w stogu siana

Jedną z głównych zalet reprezentowania dokumentów w postaci cyfrowej jest łatwość wyszukiwania w nich wyrazów i fraz. W wersji papierowej księgi “Gra o tron” próba odnalezienia pierwszego wystąpienia imienia bohaterki Daenerys może przypominać “poszukiwanie igły w stogu siana”. A w wersji cyfrowej? Wystarczy użyć funkcji “Znajdź”. Równie łatwo jest zrealizować proces zastępowania – stosując globalnie polecenie “Zamień”, możemy na przykład wszystkie wystąpienia imienia Daenerys podmienić na pisownię Denerys.

Funkcja “Zamień / Zastąp” nie pozwala jednak odszukać prostych wzorców, które człowiek z łatwością spostrzega gołym okiem. Wprawnemu czytelnikowi wystarczy bowiem krótkie spojrzenie na stronę tekstu, by bez większego trudu wychwycić na niej wszelkie wystąpienia jakiejkolwiek daty, czy też dowolnego adresu pocztowego bądź mailowego. Jak natomiast dokonać tego samego za pomocą funkcji “Znajdź”? Nie jest to niestety wcale takie łatwe!

Wyszukiwaniu i zastępowaniu różnorodnych fragmentów tekstu o regularnej budowie służy mechanizm określany mianem wyrażeń regularnych.

Co to jest wyrażenie regularne?

Wyrażenie regularne to – prosto rzecz ujmując – wzorzec reprezentujący zbiór wszystkich napisów, które do niego pasują. Dla wskazanego wyrażenia regularnego odpowiednio skonstruowany mechanizm komputerowy potrafi wyszukać w tekście wszystkie pasujące do niego napisy.

  1. Wyrażeniem regularnym może być dowolny znak, który (jeśli nie jest znakiem specjalnym – porównaj poniżej) reprezentuje wyłącznie sam siebie. Gdy na przykład wyrażenie regularne ma postać litery “a”, mechanizm obsługujący to wyrażenie szuka w tekście pierwszego wystąpienia wskazanej litery (np. Ola ma kota).
  2. Wyrażeniem regularnym może być też ciąg znaków, który również reprezentuje wyłącznie sam siebie. Jeśli wyrażeniem regularnym jest ciąg liter: “kot”, mechanizm szuka w tekście dokładnie takiego zapisu (np. Ola ma kota).
  3. Znak specjalny * w wyrażeniu regularnym reprezentuje powtórzenie poprzedzającego go znaku dowolną liczbę razy lub też brak takiego powtórzenia bądź całkowity brak  wystąpienia powyższego znaku. Na przykład do wyrażenia regularnego “hur*a*” pasują napisy: hu, hua, hur, hura, hurra, hurraa itd.
  4. Fragmenty wyrażeń regularnych można grupować za pomocą nawiasów. Na przykład do wyrażenia regularnego “hu(ra)*” pasują napisy: hu, hura, hurara itd.
  5. Znak specjalny | w wyrażeniu regularnym reprezentuje alternatywę. Na przykład do wyrażenia regularnego “Kowalsk(i|a)” pasują napisy: Kowalski oraz Kowalska.

Błędy i pomyłki

W praktyce zdarza się, że program wyszukujący napisy pasujące do danego wyrażenia regularnego działa jednak nie do końca tak, jak byśmy tego oczekiwali. Jak to możliwe? Zapewne autor wyrażenia regularnego popełnił w tym przypadku błąd, a najczęstszą przyczyną zaistniałej pomyłki jest błędne zrozumienie priorytetu operacji.

Priorytet operacji

W przypadku wyrażeń regularnych priorytet operacji przedstawia się następująco:

  1. grupowanie za pomocą nawiasów: ()
  2. znak specjalny *
  3. połączenie (ciąg) znaków
  4. znak specjalny |

Do wyrażenia regularnego “(ha)*” pasują zatem napisy: ha, haha, hahaha itd., gdyż najpierw grupujemy znaki h oraz a za pomocą nawiasów, a dopiero potem wykonujemy powtórzenie za pomocą gwiazdki. Do wyrażenia regularnego “ha*” pasują natomiast napisy h, ha, haa, haaa itd., ponieważ najpierw stosujemy operację powtórzenia dla znaku a, a dopiero potem łączymy wynik z pozostałym ciągiem znaków.

Do wyrażenia regularnego “Kowalski|a” pasują z kolei napisy: Kowalski oraz a (ale już nie napis Kowalska), gdyż połączenie znaków wiąże mocniej niż alternatywa.

Równoważność wyrażeń regularnych

Różne wyrażenia regularne mogą reprezentować te same zbiory napisów. Na przykład wyrażenie “kot|pies”  jest równoważne wyrażeniu “pies|kot”, a wyrażenie (kot)* jest równoważne wyrażeniu ((kot)*)*. Wydaje się, że taka własność wyrażeń regularnych jest cechą bardzo korzystną – można bowiem dzięki niej na różne sposoby wyrazić polecenie odszukania odpowiedniego tekstu (tak jak przy rozwiązywaniu zadania matematycznego dopuszcza się rozmaite metody uzyskania poprawnego wyniku). Kłopot w tym, że autorowi wyrażenia regularnego może wydawać się, iż stosowany przez niego zapis jest równoważny z innym, podczas gdy tak naprawdę wcale nie jest (np. wyrażenie regularne “Kowalski|a” nie jest równoważne z wyrażeniem regularnym “Kowalski|Kowalska”).

Ułatwienia i rozszerzenia

Wymienione powyżej operacje wyczerpują podstawową (tj. matematyczną) definicję wyrażeń regularnych. Informatycy posunęli się jednak nieco dalej niż matematycy, wprowadzając cały szereg innych działań, które można stosować w przypadku posługiwania się wyrażeniami regularnymi. Najpopularniejsze z nich to: klasy znaków, znak kropki, kwantyfikatory oraz kotwice.

Klasy znaków

Klasy znaków – zapisywane w nawiasach kwadratowych – to inny sposób reprezentacji alternatywy. Na przykład do wyrażenia “[AEO]la” pasują napisy Ala, Ela i Ola. Do wzorca “[A-Z]la” pasują dodatkowo: Bla, Cla, Dla itd.

Można również zdefiniować tzw. klasy negatywne. Wyrażenie “[^pl]” (ze znakiem karetki po nawiasie otwierającym) dopasowuje wszystkie znaki oprócz pl. Tak więc wzorzec regularny “[^pl]asem]” w tekście:

Idzie Jassem lasem, wymachując pasem, ale tylko czasem.

dopasuje wyłącznie fragment zasem z wyrazu czasem.

Klasy znaków można również zapisywać skrótowo; na przykład “\d” oznacza dowolną cyfrę, “\w” oznacza dowolną literę, a “\s” – dowolny biały znak (spacja, tabulator lub podział wiersza).

Znak kropki

Kropka jest znakiem specjalnym. Zastosowana w wyrażeniu specjalnym dopasowuje dowolny znak. Tak więc do wyrażenia regularnego “r.k” dopasują się napisy: rak, rok oraz ryk, ale także na przykład napis: r5k.

Jeśli z kolei chcemy, aby wyrażenie regularne reprezentowało znak specjalny (np. kropkę), musimy taki znak poprzedzić symbolem \. Na przykład posługując się wyrażeniem regularnym: “ai\.pwn\.pl”, możemy odnaleźć wystąpienie adresu mailowego pracownika naszej firmy.

Kwantyfikatory

Kwantyfikatory służą do wyszukiwania powtórzeń, a jednym z nich jest wspomniany już wcześniej znak specjalny gwiazdki.

Znak specjalny + oznacza co najmniej jedno powtórzenie. Na przykład wyrażenie “hur+a+ dopasuje napisy hura, hurra, hurraa itd., ale nie dopasuje napisu hu.

Znak specjalny ? oznacza z kolei zero lub jedno wystąpienie. Wyrażenie ‘’2020 r\.?’’ dopasowuje zarówno zapis zakończony kropką, jak i jej pozbawiony.

Kwantyfikatory mogą także ograniczać liczbę powtórzeń. Wyrażenie regularne: \d{4} oznacza wystąpienie dokładnie czterech cyfr, a wyrażenie: \w{5,10}  – wyraz o długości od pięciu do dziesięciu znaków.

Kotwice

Kotwice ograniczają miejsce wystąpienia napisu w tekście.

Znak specjalny ^ informuje o tym, że dopasowany napis musi znajdować się na początku tekstu.

Znak specjalny $ informuje o tym, że dopasowany napis musi znajdować się na końcu tekstu.

Wyrażenie regularne “^\d+$” dopasowuje zatem wyłącznie teksty składające się wyłącznie z cyfr.

Kotwica \b oznacza granicę pomiędzy wyrazami. Do wyrażenia “\bkot\b” nie zostanie dopasowany żaden fragment tekstu “Ala ma kota”, gdyż w sformułowaniu tym po wyrazie kot nie występuje granica wyrazów.

Flagi

Przy obsłudze wyrażeń regularnych przydają się także flagi, które rozszerzają możliwości przeszukiwania tekstów. Na przykład flaga i oznacza, że do wyrażenia regularnego mają pasować również napisy różniące się wielkością litery od wzorca, a flaga g – że mechanizm stosujący wyrażenia regularne ma odszukać wszystkie napisy pasujące do wzorca, a nie tylko pierwszy z nich.

Flaga nie jest częścią wyrażenia regularnego. Informację o tym, że dana flaga ma być stosowana, użytkownik mechanizmu przeszukującego teksty podaje oddzielnie (w różny sposób w zależności od rozwiązania).

Przykłady popularnych wyrażeń regularnych

([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))

Powyższe wyrażenie regularne dopasowuje najbardziej popularny aktualnie format i zakres dat. Zgodnie z nim data powinna rozpoczynać się od cyfry 1 lub 2 ([12]), po której następują trzy dowolne cyfry (\d{3}). Potem występuje łącznik (-), a po nim określenie miesiąca: jest to albo napis dwucyfrowy zaczynający się od zera (oprócz 00), albo jeden z napisów: 10, 11, 12 (0[1-9]|1[0-2]). Po kolejnym łączniku (-) następuje deklaracja dnia miesiąca  – zbudowana jest ona z dwóch cyfr, z których albo pierwsza jest zerem, a druga należy do zakresu od 1 do 9 (0[1-9]), albo pierwsza ma wartość 1 lub 2, a druga dowolną ([12]\d), albo też pierwsza jest trójką, a druga  – zerem lub jedynką (3[01]).

\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b

Powyższe wyrażenie – przy zastosowaniu flagi i – dopasuje większość adresów mailowych. Adres mailowy zaczyna się zgodnie z nim od co najmniej jednego wystąpienia litery, cyfry lub dowolnego innego dozwolonego znaku (b[A-Z0-9._%+-]+), po czym wystąpić musi znak @, a po nim co najmniej jeden napis zakończony kropką ([A-Z0-9.-]+\.), po którym stać będzie oznaczenie domeny składające się z co najmniej dwóch liter ([A-Z]{2,}).

Jak sprawdzić wyrażenie regularne?

Istnieje cały szereg narzędzi on-line umożliwiających sprawdzenie, czy skonstruowane przez nas wyrażenie regularne “działa” dokładnie tak, jak powinno. Jedno z nich udostępnione zostało na stronie: regex101.com.

W okienku edycyjnym umieszczamy nasze wyrażenie regularne, a w polu tekstowym – tekst, który ma zostać przeszukany. Narzędzie podświetla w powyższym tekście pierwsze wystąpienie napisu zgodnego z testowanym przez nas wyrażeniem regularnym:

Zauważmy, że w powyższym przykładzie mechanizm wyszukiwania stosuje flagę i (włączoną uprzednio poprzez kliknięcie w prawym rogu okienka edycyjnego). Wyłączenie tej flagi spowoduje, że adres mailowy nie zostanie znaleziony:

Podsumowanie

Jednym z głównych powodów, dla których teksty coraz częściej przechowywane są w formie cyfrowej, jest łatwość wyszukiwania w nich informacji. O ile odszukanie w stogu siana pojedynczej igły (czyli konkretnego napisu) jest zadaniem stosunkowo łatwym, a co za tym idzie, funkcję tego typu udostępnia praktycznie każdy edytor tekstów, o tyle odnalezienie w nim wielu igieł (czyli wszystkich napisów określonego typu) nie jest już takie banalne.

Wyrażenia regularne są obecnie najbardziej popularnym mechanizmem wyszukiwania w tekstach napisów określonego typu. Warto zatem na pewno ten mechanizm poznać – choćby w podstawowym zakresie, przedstawionym w niniejszym wpisie.