Dziś jest środa, 19 listopada 2008 roku (z kalendarza...)

O wyrażeniach regularnych

Icon

26.04.2007, 15:23

PHP

Komentarze (8)

Powrót

Wyrażenia regularne to jedno z najprzydatniejszych każdemu programiście narzędzi. Z jego pomocą sprawdzenie, czy jakiś tekst spełnia określone reguły, sprowadza się do utworzenia nieskomplikowanego wzorca dopasowań. Jednak jak każda użyteczna rzecz, bywa ono czasem nadużywane lub stosowane niewłaściwie tam, gdzie najskuteczniejsze okazują się proste, algorytmiczne metody.

Osoby aktywnie udzielające się na programistycznych forach mają zapewne nawet kilka historyjek o próbujących stosować wyrażenia regularne wszędzie, gdzie się da. Osobiście widziałem propozycję wykorzystania funkcji preg_match() tylko do sprawdzenia, czy ciąg A pojawia się w tekście, zupełnie jakby nie istniał przeznaczony do tego celu strpos(). Drugi gatunek to ludzie traktujący wyrażenia regularne jak język programowania, co jest grubą przesadą. preg_match() nie jest w stanie obsłużyć co bardziej zaawansowanych dopasowań i w niektórych sytuacjach trzeba częściowy rezultat obróbki przekazać np. do PHP, by ten zajął się resztą.

Na bazie mego ostatniego doświadczenia chciałbym dołączyć teraz jeszcze jedną grupę: kiedy wyrażenie regularne działa, wygląda na poprawnie użyte, generuje prawidłowy rezultat, ale po pewnym czasie okazuje się, że lepiej, gdyby go nie było. Problem pojawił się w parserze Open Power Template niedługo po premierze wersji 1.1.1, która zawiera m.in. poprawiony błąd z obsługą przestrzeni nazw w atrybutach. Poprawka wymagała lekkiej przebudowy głównego wyrażenia regularnego dzielącego szablon na tokeny - chodziło o to, by była w nim na sztywno zapisana lista przestrzeni nazw, które OPT może parsować. Jednak po kilku dniach zacząłem odbierać wiadomości, że kompilator w trybie XML nie kończy pracy z powodu... przekroczenia 8-megabajtowego limitu pamięci. Ciężko uwierzyć? Mi również, ale okazało się to prawdą. Wyrażenie regularne zaczęło generować ogromną tablicę wynikową o wymiarze NxZ, gdzie N to w przybliżeniu ilość znaków, a Z to ilość grup w wyrażeniu (zależna oczywiście od liczby włączonych ograniczników). Każdy znak zrzucany był do tej tablicy jako pojedynczy wiersz, co dawało ogromne pustki w tablicy - jakieś 95% wszystkich pól zawierało puste wartości, więc ich deklaracja niepotrzebnie "zjadała" pamięć. System wyrażeń regularnych Perla zaimplementowany w PHP nie miał żadnego lekarstwa na tę bolączkę. Żadna z flag nie oferowała możliwości pozbycia się już na etapie przetwarzania pustych komórek, więc siłą rzeczy trzeba było poszukać innego rozwiązania. Podjąłem kolejną próbę zmuszenia OPT do wyłapywania statycznego tekstu jako pojedynczego bloku zamiast literka po literce. Jak zwykle zakończyła się ona niepowodzeniem.

Nie wszystko jednak stracone. Fragment wyrażenia przechwytujący statyczny tekst usunąłem całkowicie, przez co kompilator zaczął wychwytywać same znaczniki OPT we właściwej kolejności, ale bez otaczającego je tekstu. Pozornie taki wynik na nic się nam nie przyda, chyba że przy szablonach kompilacyjnych. Jednak przecież źródło dalej jest załadowane do pamięci, czemu więc by z niego nie skorzystać jakoś? Stąd już prosta droga do banalnego, ale skutecznego algorytmu wykorzystującego funkcję strpos() oraz substr():

  1. Ustaw $offset na 0.
  2. Pobierz pierwszy token z tablicy. Sprawdź funkcją strpos() jego położenie ($i).
  3. Wytnij tekst od $offset do $i i zarejestruj jako węzeł tekstowy.
  4. Wykonaj: $offset += $i + strlen($token) (czyli przesuwamy się za właśnie skopiowany tekst i przetworzony token.
  5. Przetwórz token.
  6. Jeśli są jeszcze tokeny, skocz do (2).
  7. Jeśli nie ma, z pozostałego statycznego kodu w szablonie zrób ostatni węzeł tekstowy i zakończ.

Innymi słowy, wycinanie statycznego tekstu zrzucamy na barki PHP, wyrażeniom regularnym pozostawiając samo odnalezienie znaczników. Za skuteczność niech świadczy rezultat: zużycie pamięci na poziomie 20 kb zamiast kilku megabajtów. (dla co poniektórych nadmienię, że funkcja strpos() ma też trzeci parametr $offset oznaczający miejsce w tekście, od którego rozpocząć poszukiwanie - w powyższym algorytmie właśnie z tego korzystam).

Podsumowując: nie zawsze wyrażenie regularne okazuje się najskuteczniejszym sposobem wyciągnięcia z tekstu potrzebnych nam informacji. Niektóre znacznie lepiej jest uzyskać tradycyjnymi metodami, które choć mają nieco większą długość, nie wymagają nie wiadomo jakiej mocy/wiedzy pamięci. A tak na marginesie, skoro już jesteśmy przy OPT - dostępna jest nowa wersja (1.1.2), która usuwa problem z pamięcią.

Powrót

Komentarze

Napisał Bartosz Olchówka w czwartek, 26 kwietnia 2007 o 16:52

No proszę, a ten zamiast zakuwać do matury, to jakimiś offsetami się bawi (-;. no, ale nie odbiegając od tematu - bardzo słuszna uwaga. Sam czasem zastanawiam się, czy aby nie nadużywam tych wyrażeń regularnych, które są oczywiście niezmiernie wygodne i łatwe do opanowania, ale... cóż, coś za coś. 8 MB zresztą to nie jest aż tak wiele, ostatnio słuchałem prelekcji dot. Ruby on Rails (i porównanie hostowania tego typu aplikacji oraz tych napisanych w PHP). Jedna aplikacja w RoR cały czas zajmuje ponoć kilkadziesiąt MB pamięci (dopóki nie zostanie wyłączona)... No i cóż przy RoR jest ten Twój problem z wyrażeniami i zbyt dużą tablicą? :)

Powodzenia w maju.

Napisał Zyx w czwartek, 26 kwietnia 2007 o 17:57

Jak na serwerze jest ustalony limit 8 MB, to dla takiego programisty JEST to poważny problem :D.

Ad. matury - thx. Matma to swoją drogą, ale czas przy kompie poświęcam głównie na tegoroczne Diversity, gdzie tym razem trzeba także angielską wersję przygotować. Tłumaczenie własnych tekstów, z moim umiłowaniem do sophisticated language structures w artykułach to niezłe wyzwanie i trening przed egzaminem :).

Napisał Wielkie G w czwartek, 26 kwietnia 2007 o 22:28

I oto nasze wspaniałe preg_match :) Ogólnie każde wyrażenie preg_* można przetłumaczyć na właśnie takie uproszczone. Przy prostych wyrażeniach różnicy za bardzo nie widać, ale przy skomplikowanym parsowaniu w systemie szablonów różnica jest jak widać wręcz kolosalna...

Napisał eRiZ w piątek, 27 kwietnia 2007 o 16:14

Hmm, skoro przetwarzasz XML, to czemu nie wykorzystasz do tego celu parsera tylko męczysz regexpy?

Napisał Zyx w piątek, 27 kwietnia 2007 o 17:14

To jest tryb emulacji XML, który polega na tym, że są XML-owe ograniczniki podłożone, natomiast sens ich użycia pod kątem zgodności z regułami XML już nie jest sprawdzany. Parser XML będzie w OPT 2.0.0, a i też według autorskiego projektu, ponieważ gotowy raczej nie będzie potrafił wykonać narzuconych mu zadań :P.

Napisał Azrael Nightwalk w piątek, 27 kwietnia 2007 o 22:13

Regexpy rządzą! ;)
http://xkcd.com/c208.html

Napisał Michał Środek w sobotę, 5 maja 2007 o 15:07

Hmm jakoś nie chce mi się wierzyć aby nie dało się dobrze napisać tego za pomoca wyrażeń regularnych ;) chociaż przyznam, że to nie lada sztuka ;). Jako fan regexow nieraz siedziałem po kilka dni aby wymyślec dobre wyrażenie ;).

Co do dodatkowych elementow w tablicy, moze wystarczylo uzyc (?: ?

Napisał Zyx w niedzielę, 6 maja 2007 o 20:32

Być może, ja tam tyle cierpliwości nie mam, zwłaszcza że ludzie czekali na łatkę :)

Strona 1 z 1 :: 1

Skomentuj

NickInformacja
E-mailTylko do użytku wewnętrznego.
WWWNie zapomnij o http://
LayoutNapisz tu, czy widzisz dzienny czy nocny layout.
WpisFormatowanie wiki
Internauto, pamiętaj! Wolność to nie samowola - dbaj o kulturę wypowiedzi oraz dyskusji w sieci.

Na Zyxist.com panuje swoboda wyrażania opinii oraz krytyki pod dowolnym adresem. Jedyny warunek: musi być ona kulturalna i rzeczowa. Na chamstwo, prostactwo lub jawne obrażanie kogokolwiek nie ma tu miejsca i takie komentarze są bardzo szybko usuwane. Jeśli zamierzasz polemizować z treścią wpisu, wpierw uważnie ją przeczytaj.

© Tomasz "Zyx" Jędrzejewski 2005 - 2008 | Wykonanych zapytań: 2 | Serwer wirtualny zapewnia