Wiadomo, że parser rekursywny może wymagać czasu wykładniczego w niektórych przypadkach; Czy ktokolwiek może wskazać mi próbki, gdzie to się dzieje? Szczególnie interesuje się przypadkami dla PEG (to jest z priorytetowymi wyborami).O złożoności parserów rekurencyjnych
Odpowiedz
To dlatego, że możesz skończyć analizując te same rzeczy (sprawdź tę samą regułę w tej samej pozycji) wiele razy w różnych gałęziach rekursji. To trochę tak, jakby obliczać n-tą liczbę Fibonacciego przy użyciu rekurencji.
Grammar:
A -> xA | xB | x
B -> yA | xA | y | A
S -> A
Input:
xxyxyy
Parsing:
xA(xxyxyy)
xA(xyxyy)
xA(yxyy) fail
xB(yxyy) fail
x(yxyy) fail
xB(xyxyy)
yA(yxyy)
xA(xyy)
xA(yy) fail
xB(yy) fail
x(yy) fail
xB(xyy)
yA(yy)
xA(y) fail
xB(y) fail
x(y) fail
xA(yy) fail *
x(xyy) fail
xA(yxyy) fail *
y(yxyy) fail
A(yxyy)
xA(yxyy) fail *
xB(yxyy) fail *
x(yxyy) fail *
x(xyxyy) fail
xB(xxyxyy)
yA(xyxyy) fail
xA(xyxyy) *
xA(yxyy) fail *
xB(yxyy) fail *
...
*
- gdzie możemy analizować regułę w tym samym miejscu, w którym mamy już analizowany go w innej branży. Gdybyśmy zapisali wyniki - które reguły zawiodłyby przy których pozycjach - dowiedzielibyśmy się, że xA (xyxyy) zawodzi za drugim razem i nie przejdziemy ponownie przez całe poddrzewo. Nie chciałem napisać wszystkiego, ale widać, że wielokrotnie będzie powtarzać te same poddrzewa.
Kiedy to się stanie - gdy masz wiele nakładających się transformacji. Priorytetowy wybór nie zmienia rzeczy - jeśli reguła najniższego priorytetu kończy się jako jedyna poprawna (lub żadna nie jest poprawna), musisz sprawdzić wszystkie reguły.
Każdy analizator składający się z góry w dół, włącznie z rekurencyjnym zejściem, może teoretycznie stać się wykładniczy, jeśli kombinacja danych wejściowych i gramatyki jest taka, że wymagana jest duża liczba ścieżek wstecz. Dzieje się tak, jeśli gramatyka jest taka, że wybory determinujące są umieszczane na końcu długich sekwencji. Na przykład, jeśli masz symbol taki jak &, co oznacza "wszystkie poprzednie minusy są w rzeczywistości plusses", a następnie mają dane takie jak "(((((a - b) - c) - d) - e &)" następnie parser musi przejść do tyłu i zmień wszystkie plusy na minusy. Jeśli zaczniesz tworzyć wyrazy zagnieżdżone wzdłuż tych linii, możesz utworzyć efektywny, nie kończący się zestaw danych wejściowych.
Musisz zdać sobie sprawę z tego, że wkraczasz tutaj w kwestię polityczną, ponieważ w rzeczywistości większość normalnych gramatyk i zestawów danych nie jest taka, jednak istnieje wiele osób, które systematycznie odradzają rekursywne zejście, ponieważ nie jest ono łatwe do zrobienia RD automatycznie. Wszystkie wczesne parsery to LALR, ponieważ są DUŻO łatwiejsze do zrobienia automatycznie niż RD. Stało się tak dlatego, że wszyscy po prostu napisali LALR i zepsutą RD, ponieważ w dawnych czasach jedynym sposobem na stworzenie RD było ręczne kodowanie. Na przykład, jeśli czytasz smoczą książkę, zobaczysz, że Aho & Ullman pisze tylko jeden akapit na RD, i jest to w zasadzie tylko ideologiczne zdanie: "RD jest złe, nie rób tego".
Oczywiście, jeśli zaczniesz ręczne kodowanie RD (tak jak ja), przekonasz się, że są one dużo lepsze niż LALR z różnych powodów. W dawnych czasach zawsze można było powiedzieć kompilatorowi, który miał ręcznie zakodowane RD, ponieważ miał znaczące komunikaty o błędach z dokładnością lokalizacji, podczas gdy kompilatory z LALR-em pokazywałyby błąd występujący jak 50 linii od miejsca, w którym faktycznie był. Wiele się zmieniło od dawnych czasów, ale powinieneś zdać sobie sprawę, że kiedy zaczynasz czytać FUD na RD, to pochodzi z długiej, długiej tradycji werbalnej niszczenia RD w "pewnych kręgach".
- 1. Uproszczenie złożoności Big-O tego wykładniczego algorytmu
- 2. Redukcja złożoności kodu o (n^3) C++
- 3. `złożoność złożoności
- 4. Obciążenie rekurencyjnych lambdas
- 5. Algorytm złożoności czasu Euclida
- 6. Biblioteka łączników parserów z wyboru (haskell)
- 7. Narzędzie do obliczania skomplikowanej złożoności kodu Java w czasie O?
- 8. Minimalna wartość maksymalnych wartości w podsegmentach ... w złożoności O (n)
- 9. Znaczenie średniej złożoności przy korzystaniu z notacji Big-O
- 10. Określanie złożoności dla funkcji rekursywnych (notacja Big O)
- 11. Czy kombinatory parserów mogą być wydajne?
- 12. Analiza algorytmów (złożoności)
- 13. Zmniejszenie złożoności cykllomatycznej
- 14. Obliczanie złożoności cyklicznej
- 15. Dopasowywanie wzorców na sukcesach parserów w Scali
- 16. Zmniejszanie kodu złożoności dla GWT
- 17. Błąd złożoności renderowania Matplotlib Agg
- 18. Efektywna metoda pythonowa dla równań rekurencyjnych
- 19. Zrozumienie Obliczanie złożoności czasu dla algorytmu Dijkstra
- 20. Najprostszym sposobem na usunięcie dwukierunkowych relacji rekurencyjnych?
- 21. Apache Thrift zawiedzie generowania rekurencyjnych konstrukcjom
- 22. Python: Mapa funkcję nad rekurencyjnych iterables
- 23. Scala: Jak łączyć kombinatory parserów z różnych obiektów
- 24. Testy jednostkowe w celu weryfikacji złożoności czasu
- 25. wzorce projektowe dla konwersji rekurencyjnych algorytmów iteracyjnych do nich
- 26. Jak sprawdzić pokrycie kodu testami dla metod o określonym poziomie złożoności w Javie
- 27. Jak wstawić element na posortowanej liście połączonej w ramach złożoności o stałym czasie?
- 28. Czy są jakieś narzędzia, które mogą określić analizę kodu wykonawczego dla złożoności Big-O?
- 29. Struktura i składnia zapytań dla dokumentów rekurencyjnych w MongoDB?
- 30. Czy PHPUnit ma wbudowaną funkcję porównywania tablic rekurencyjnych?
Tylko wtedy, gdy cofną się. Jeśli nadal przechodzą od lewej do prawej w prawdziwym stylu "LL (1)", powinny to być * O (N) *. – EJP
@ EJP, oczywiście. Ale nawet wycofanie się w większości przypadków nie wprowadza wykładniczej złożoności. Próbuję dowiedzieć się lepiej, w jakich okolicznościach tak się dzieje. – fithu
Nie wszystkie parser rekursywny może wykazywać wykładnicze zachowanie. Na przykład ANTLR 4 tworzy parsery rekurencyjne z opcją [pół-] o priorytecie, ale jest najgorszym przypadkiem O (n⁴) (dowód jest częścią pracy, nad którą obecnie pracuję). –