Jak to działa?
Spójrz na teorię automatów
Krótko mówiąc, każde wyrażenie regularne ma równoważny automat skończony i może zostać skompilowane i zoptymalizowane do automatu skończonego. Zaangażowane algorytmy można znaleźć w wielu książkach kompilatorów. Algorytmy te są używane przez programy uniksowe, takie jak awk i grep.
Jednak większość współczesnych języków programowania (Perl, Python, Ruby, Java (i JVM), C #) nie korzysta z tego podejścia. Używają rekurencyjnego podejścia do cofania, które kompiluje wyrażenie regularne w drzewo lub sekwencję konstrukcji reprezentujących różne podgrupy wyrażenia regularnego. Większość współczesnych składni „wyrażeń regularnych” oferuje odsyłacze wsteczne spoza grupy języków regularnych (nie mają reprezentacji w automatach skończonych), które można w trywialny sposób zastosować w rekurencyjnym podejściu wstecznym.
Optymalizacja zwykle daje bardziej wydajną maszynę stanu. Na przykład: rozważ aaaab | aaaac | aaaad, zwykły programista może uzyskać prostą, ale mniej wydajną implementację wyszukiwania (porównując trzy łańcuchy osobno) w ciągu dziesięciu minut; ale zdając sobie sprawę, że jest to równoważne z aaaa [bcd], lepsze wyszukiwanie można przeprowadzić, wyszukując pierwsze cztery „a”, a następnie testując 5. znak na [b, c, d]. Proces optymalizacji był jednym z moich domowych zadań kompilatora wiele lat temu, więc zakładam, że ma to miejsce także w większości nowoczesnych silników wyrażeń regularnych.
Z drugiej strony maszyny stanowe mają pewną przewagę, gdy akceptują ciągi, ponieważ zajmują więcej miejsca w porównaniu z „trywialną implementacją”. Zastanów się nad programem, który usuwa znaki cytowania z ciągów SQL, to znaczy: 1) zaczyna się i kończy pojedynczymi znakami cudzysłowu; 2) pojedyncze cudzysłowy są poprzedzane dwoma kolejnymi pojedynczymi cudzysłowami. Tak więc: input ['a' ']] powinno dać wynik [a']. W maszynie stanów kolejne znaki pojedynczego cudzysłowu są obsługiwane przez dwa stany. Te dwa stany służą do zapamiętania historii wprowadzania, dzięki czemu każdy znak wejściowy jest przetwarzany dokładnie tylko raz, jak pokazano poniżej:
...
S1->'->S2
S1->*->S1, output *, * can be any other character
S2->'->S1, output '
S2->*->END, end the current string
Tak więc, moim zdaniem, wyrażenie regularne może być wolniejsze w niektórych trywialnych przypadkach, ale zwykle szybsze niż ręcznie spreparowany algorytm wyszukiwania, biorąc pod uwagę fakt, że optymalizacja nie może być niezawodnie wykonana przez człowieka.
(Nawet w trywialnych przypadkach, takich jak wyszukiwanie ciągu, inteligentny silnik może rozpoznać pojedynczą ścieżkę na mapie stanów i zredukować tę część do prostego porównania ciągu i uniknąć zarządzania stanami.)
Określony silnik z frameworka / biblioteki może być powolny, ponieważ silnik wykonuje wiele innych rzeczy, których programista zwykle nie potrzebuje. Przykład: klasa Regex w .NET tworzy zestaw obiektów, w tym Dopasuj, Grupy i Przechwyty.