Loading AI tools
Z Wikipedii, wolnej encyklopedii
Algorytm Earleya – algorytm służący do analizy składniowej na podstawie dowolnej gramatyki bezkontekstowej. Stosuje się go między innymi do przetwarzania języków naturalnych[1][2], gdyż inaczej niż większość algorytmów analizy składniowej działa również z gramatykami niejednoznacznymi. Korzysta się też z niego przy tworzeniu interpreterów i kompilatorów języków programowania, zwłaszcza języków specjalizowanych, ponieważ szkielety projektowe oparte na tym algorytmie ułatwiają szybkie tworzenie prototypów (RAD)[3][4].
Górne ograniczenie czasu analizy n symboli terminalnych przez parser oparty na tym algorytmie rośnie proporcjonalnie do n3 w ogólnym przypadku, do n2 dla gramatyk jednoznacznych i do n dla sporej klasy gramatyk bezkontekstowych, w tym większości gramatyk języków programowania.
Algorytm ten opracował w 1968 roku Jay Earley z Carnegie Mellon University w swojej pracy doktorskiej[5] promowanej przez Roberta W. Floyda. W 1970 roku Earley opublikował go w Communications of the ACM w artykule[6], który w 1983 roku zaliczono do 21 najbardziej znaczących publikacji w ćwierćwieczu istnienia tego czasopisma[7].
Algorytm Earleya należy, podobnie jak algorytm CYK, do klasy tablicowych metod analizy składniowej (ang. chart parsers). Stosuje się w nim wstępującą analizę składniową z lewa na prawo na podstawie zstępujących przewidywań. Wyniki algorytmu powstają na podstawie wcześniejszych wyników częściowych, zgodnie z techniką programowania dynamicznego.
Analiza składniowa z zastosowaniem tego algorytmu operuje na zbiorze sytuacji Earleya (ang. Earley items) lub krótko sytuacji, czyli produkcji gramatyki z dodaną kropką i dwoma indeksami. Podczas działania algorytmu sytuacje Earleya odzwierciedlają dotychczas zastosowane produkcje oraz służą do przewidywania kolejnych produkcji. Po przeanalizowaniu poprawnego ciągu wejściowego powinna powstać sytuacja, która odzwierciedla jego wyprowadzenie z symbolu startowego gramatyki.
Sytuacje Earleya mają postać [A →h X0 … Xp−1 •i Xp … Xq−1], gdzie produkcja A → X0 … Xq−1 należy do zbioru produkcji danej gramatyki. Indeksy przy strzałce →h i kropce •i spełniają nierówności 0 ≤ h ≤ i ≤ n (jeśli symbole terminalne ponumerować od t0 do tn−1), a kropka może leżeć gdziekolwiek pomiędzy początkiem a końcem prawej strony produkcji A → X0 … Xq−1 włącznie, czyli 0 ≤ p ≤ q. Indeks h określa pierwszy symbol terminalny wchodzący do drzewa rozbioru danej produkcji, indeks i – pierwszy nierozpatrzony jeszcze symbol terminalny, a położenie kropki oznacza postęp analizy danej produkcji. Ujmując rzecz formalnie, sytuacja Earleya [A →h X0 … Xp−1 •i Xp … Xq−1] oznacza, że algorytm przeanalizował podciąg t0 … ti−1 jako część formy zdaniowej t0 … th−1X0 … Xq−1α do symbolu Xp−1 włącznie (małe litery greckie α, β, γ, ... oznaczają dowolne, także puste ciągi symboli terminalnych lub nieterminalnych).
Na początku działania algorytmu zbiór sytuacji zawiera jeden element [S’ →0 •0 S], gdzie S oznacza właściwy symbol startowy danej gramatyki, a S’ – pomocniczy symbol startowy spoza zbioru symboli gramatyki. Następnie dopóty, dopóki ten zbiór rośnie, algorytm dodaje do niego nowe sytuacje na podstawie wejściowych symboli terminalnych i sytuacji już należących do zbioru. Jeżeli nowo utworzona sytuacja już należy do tego zbioru, nie zmienia się go. Do powstawania nowych sytuacji prowadzą trzy rodzaje działań:
Wyprowadzenie wejściowego ciągu symboli terminalnych t0, … , tn−1 z symbolu startowego danej gramatyki istnieje wtedy i tylko wtedy, gdy algorytm utworzył sytuację Earleya [S’ →0 S •n].
Dla „pikogramatyki” języka angielskiego
i wejściowego ciągu symboli TheDet blackAdj catN ateV aDet whiteAdj mouseN algorytm tworzy następujący zbiór sytuacji Earleya:
Sytuacja Earleya | Powód dodania do zbioru sytuacji |
---|---|
[S’ →0 •0 S] | inicjalizacja |
[S →0 •0 NP VP] | przewidziano S w sytuacji [S’ →0 •0 S] |
[NP →0 •0 Det N] | przewidziano NP w sytuacji [S →0 •0 NP VP] |
[NP →0 •0 Det Adj N] | przewidziano NP w sytuacji [S →0 •0 NP VP] |
[NP →0 Det •1 N] | wczytano Det w sytuacji [NP →0 •0 Det N] |
[NP →0 Det •1 Adj N] | wczytano Det w sytuacji [NP →0 •0 Det Adj N] |
[NP →0 Det Adj •2 N] | wczytano Adj w sytuacji [NP →0 Det •1 Adj N] |
[NP →0 Det Adj N •3] | wczytano N w sytuacji [NP →0 Det Adj •2 N] |
[S →0 NP •3 VP] | uzupełniono NP w sytuacji [S →0 •0 NP VP] |
[VP →3 •3 V] | przewidziano VP w sytuacji [S →0 NP •3 VP] |
[VP →3 •3 V NP] | przewidziano VP w sytuacji [S →0 NP •3 VP] |
[VP →3 V •4] | wczytano V w sytuacji [VP →3 •3 V] |
[VP →3 V •4 NP] | wczytano V w sytuacji [VP →3 •3 V NP] |
[S →0 NP VP •4] | uzupełniono VP w sytuacji [S →0 NP •3 VP] |
[NP →4 •4 Det N] | przewidziano NP w sytuacji [VP →3 V •4 NP] |
[NP →4 •4 Det Adj N] | przewidziano NP w sytuacji [VP →3 V •4 NP] |
[S’ →0 S •4] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[NP →4 Det •5 N] | wczytano Det w sytuacji [NP →4 •4 Det N] |
[NP →4 Det •5 Adj N] | wczytano Det w sytuacji [NP →4 •4 Det Adj N] |
[NP →4 Det Adj •6 N] | wczytano Adj w sytuacji [NP →4 Det •5 Adj N] |
[NP →4 Det Adj N •7] | wczytano N w sytuacji [NP →4 Det Adj •6 N] |
[VP →3 V NP •7] | uzupełniono NP w sytuacji [VP →3 V •4 NP] |
[S →0 NP VP •7] | uzupełniono VP w sytuacji [S →0 NP •3 VP] |
[S’ →0 S •7] | uzupełniono S w sytuacji [S’ →0 •0 S] |
Ostatnia sytuacja Earleya [S’ →0 S •7] odpowiada pełnemu rozbiorowi wszystkich siedmiu symboli ciągu wejściowego. Sytuacja Earleya [S’ →0 S •4] oznacza, że pierwsze cztery symbole tego ciągu również tworzą poprawne zdanie w danej gramatyce.
W implementacjach algorytmu Earleya zamiast jednego zbioru sytuacji korzysta się z listy zbiorów i pomija wewnątrz sytuacji indeks nierozpatrzonego symbolu wejściowego. W i-tym zbiorze przechowuje się sytuacje postaci [A →h α • η]. Spotyka się też notację [A → α • η, h] i inne. Algorytm przegląda wówczas po kolei zbiory z tej listy, dzięki czemu przyrostowo bada symbole wejściowe ti w kolejności rosnących indeksów i.
Wewnątrz zbiorów sytuacje Earleya przegląda się zazwyczaj w kolejności ich dodawania, każdą jeden raz. Wymaga to jednak modyfikacji algorytmu, jeśli ma on poprawnie działać dla gramatyk zawierających produkcje z pustą prawą stroną, o czym poniżej. Aby wydajnie przeglądać sytuacje jednego zbioru, powinny one tworzyć kolejkę, aby zaś przy próbie dodania sytuacji do kolejki wydajnie sprawdzać, czy kolejka już ją zawiera, sytuacje z każdej kolejki powinny dodatkowo należeć do tablicy asocjacyjnej. Wydajne uzupełnianie sytuacji, w których kropka nie leży na końcu produkcji, wymaga jeszcze jednej tablicy asocjacyjnej, przechowującej jako wartości listy takich sytuacji, a jako klucze – pary (Ap, i), złożone z symbolu leżącego w tych sytuacjach bezpośrednio za kropką i z indeksu nierozpatrzonego symbolu terminalnego. Ponadto, by wydajnie przewidywać nowe sytuacje, z każdym symbolem nieterminalnym powinno się związać listę wszystkich produkcji, po których lewej stronie stoi ten symbol.
Jeśli jako wymienione powyżej tablice asocjacyjne zastosować tablice mieszające, to krok polegający na tworzeniu nowej sytuacji Earleya i próbie poszerzenia o nią zbioru sytuacji można wykonać w oczekiwanym czasie O(1).
Earley wskazał w swoim artykule[6], że w ogólnym przypadku:
W tym samym artykule wykazano również, że dla gramatyk jednoznacznych działanie uzupełniania wykonuje w i-tym zbiorze sytuacji O(i) kroków, co owocuje kwadratową złożonością algorytmu, oraz że klasa gramatyk, dla których algorytm Earleya działa w czasie liniowym, obejmuje gramatyki, dla których liczbę sytuacji w każdym zbiorze ogranicza z góry pewna stała. Do tej klasy należą prawie wszystkie gramatyki LR(k), oprócz niektórych gramatyk prawostronnie rekursywnych, więc także gramatyki większości języków programowania.
Algorytm Earleya działa szczególnie wydajnie z gramatykami o produkcjach lewostronnie rekursywnych. Jako przykład posłuży gramatyka
użyta do analizy ciągu aaa. Algorytm Earleya tworzy wówczas następujące sytuacje:
Sytuacja Earleya | Powód dodania do zbioru sytuacji |
---|---|
[S’ →0 •0 S] | inicjalizacja |
[S →0 •0 Sa] | przewidziano S w sytuacji [S’ →0 •0 S], a potem w sytuacji [S →0 •0 Sa] |
[S →0 •0 a] | przewidziano S w sytuacji [S’ →0 •0 S], a potem w sytuacji [S →0 •0 Sa] |
[S →0 a •1] | wczytano a w sytuacji [S →0 •0 a] |
[S’ →0 S •1] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[S →0 S •1 a] | uzupełniono S w sytuacji [S →0 •0 Sa] |
[S →0 Sa •2] | wczytano a w sytuacji [S →0 S •1 a] |
[S’ →0 S •2] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[S →0 S •2 a] | uzupełniono S w sytuacji [S →0 •0 Sa] |
[S →0 Sa •3] | wczytano a w sytuacji [0S → S •2 a] |
[S’ →0 S •3] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[S →0 S •3 a] | uzupełniono S w sytuacji [S →0 •0 Sa] |
Liczba sytuacji z każdą wartością indeksu przy kropce wynosi 3, więc algorytm działa w czasie liniowym.
Dla porównania użycie do analizy tego samego ciągu aaa gramatyki prawostronnie rekursywnej
powoduje powstanie następujących sytuacji Earleya:
Sytuacja Earleya | Powód dodania do zbioru sytuacji |
---|---|
[S’ →0 •0 S] | inicjalizacja |
[S →0 •0 aS] | przewidziano S w sytuacji [S’ →0 •0 S] |
[S →0 •0 a] | przewidziano S w sytuacji [S’ →0 •0 S] |
[S →0 a •1 S] | wczytano a w sytuacji [S’ →0 •0 aS] |
[S →0 a •1] | wczytano a w sytuacji [S’ →0 •0 a] |
[S →1 •1 aS] | przewidziano S w sytuacji [S →0 a •1 S] |
[S →1 •1 a] | przewidziano S w sytuacji [S →0 a •1 S] |
[S’ →0 S •1] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[S →1 a •2 S] | wczytano a w sytuacji [S’ →1 •1 aS] |
[S →1 a •2] | wczytano a w sytuacji [S’ →1 •1 a] |
[S →2 •2 aS] | przewidziano S w sytuacji [S →1 a •2 S] |
[S →2 •2 a] | przewidziano S w sytuacji [S →1 a •2 S] |
[S →0 aS •2] | uzupełniono S w sytuacji [S’ →0 a •1 S] |
[S’ →0 S •2] | uzupełniono S w sytuacji [S’ →0 •0 S] |
[S →2 a •3 S] | wczytano a w sytuacji [S’ →2 •2 aS] |
[S →2 a •3] | wczytano a w sytuacji [S’ →2 •2 a] |
[S →3 •3 aS] | przewidziano S w sytuacji [S →2 a •3 S] |
[S →3 •3 a] | przewidziano S w sytuacji [S →2 a •3 S] |
[S →1 aS •3] | uzupełniono S w sytuacji [S’ →1 a •2 S] |
[S →0 aS •3] | uzupełniono S w sytuacji [S’ →0 a •1 S] |
[S’ →0 S •3] | uzupełniono S w sytuacji [S’ →0 •0 S] |
Liczba sytuacji o danej wartości indeksu przy kropce rośnie liniowo ze wzrostem tego indeksu, zatem dla tej gramatyki algorytm działa z kwadratową złożonością czasową.
Jeśli algorytm Earleya rozpatruje sytuacje z i-tego zbioru jednokrotnie, w kolejności ich dodawania, to może działać niepoprawnie dla gramatyk, które zawierają produkcje o pustej prawej stronie (zwane też ε-produkcjami, bo ε tradycyjnie oznacza pusty ciąg symboli gramatyki). Przy uzupełnianiu sytuacji [E →i •i], która odpowiada pustej produkcji E → ε, trzeba przejrzeć niepełny jeszcze i-ty zbiór. Jeśli sytuacja [A →h α •i Eη] zostanie do niego dodana po uzupełnieniu sytuacji [E →i •i], to uzupełnianie nigdy nie doda sytuacji [A →h αE •i η]. Nie zostaną też dodane sytuacje bezpośrednio i pośrednio z niej wynikające. Może to spowodować odrzucenie poprawnego ciągu wejściowego, jak pokazuje poniższy przykład użycia gramatyki
do analizy pustego ciągu ε. W zbiorze sytuacji Earleya na czerwono zaznaczono te sytuacje, których algorytm nie dodał do zbioru, choć powinien.
Sytuacja Earleya | Powód dodania do zbioru sytuacji |
---|---|
[S’ →0 •0 S] | inicjalizacja |
[S →0 •0 E A A A] | przewidziano S w sytuacji [S’ →0 •0 S] |
[E →0 •0] | przewidziano E w sytuacji [S →0 •0 E A A A] |
[S →0 E •0 A A A] | uzupełniono E w sytuacji [S →0 •0 E A A A] |
[A →0 •0 E] | przewidziano A w sytuacji [S →0 E •0 A A A] |
[A →0 E •0] | nie uzupełniono E w sytuacji [A →0 •0 E] |
[S →0 E A •0 A A] | nie uzupełniono A w sytuacji [S →0 E •0 A A A] |
[S →0 E A A •0 A] | nie uzupełniono A w sytuacji [S →0 E A •0 A A] |
[S →0 E A A A •0] | nie uzupełniono A w sytuacji [S →0 E A A •0 A] |
[S’ →0 S •0] | nie uzupełniono S w sytuacji [S’ →0 •0 S] |
Opublikowano kilka rozwiązań tego problemu:
Opisany powyżej algorytm tylko rozpoznaje, czy dany ciąg symboli terminalnych stanowi poprawne zdanie danej gramatyki bezkontekstowej (taki algorytm nazywa się po angielsku recognizer), ale nie buduje jego drzewa składni. Zaproponowano kilka sposobów tworzenia na jego podstawie właściwego parsera. Komplikację stanowi liczba drzew rozbioru, potencjalnie wykładnicza w stosunku do rozmiaru danych wejściowych, a dla gramatyk z cyklami nawet nieskończona. Istnieją jednak sposoby kodowania wszystkich drzew rozbioru w strukturach danych o rozmiarze wielomianowym względem długości ciągu wejściowego.
Poprawne drzewa rozbioru ciągu aaa | |
Niepoprawne drzewa rozbioru ciągu aaa |
W artykule Earleya[6] podano, jak przekształcić algorytm rozpoznawania w parser przez dodanie do wystąpień symboli nieterminalnych wewnątrz prawych stron produkcji w sytuacjach Earleya zbioru wskaźników do sytuacji, które spowodowały uzupełnienie tych symboli: przy każdym uzupełnianiu, powodującym powstanie sytuacji [B →g βA •i γ], tworzy się wskaźnik od wystąpienia symbolu A w tej sytuacji do sytuacji [A →h α •i]. M. Tomita[11] podał jednak kontrprzykład: dla gramatyki
i ciągu wejściowego aaa parser zaproponowany przez Earleya tworzy nie tylko poprawne drzewa rozbioru ciągu aaa, ale też niepoprawne drzewa rozbioru ciągów aa i aaaa.
Można temu zaradzić, dodając do sytuacji [B →g βA •i γ] powstałej w wyniku uzupełniania nie jeden, lecz parę wskaźników: do sytuacji [B →g β •h Aγ] oraz do sytuacji [A →h α •i]. Gdyby naiwnie skorzystać z tego pomysłu przez dodanie tej pary wskaźników do zestawu etykiet odróżniających sytuacje, to rząd czasu działania algorytmu mógłby przekroczyć n3, jak pokazuje przykład, pochodzący z artykułu M. Johnsona[12]: dla gramatyki
algorytm Earleya z tak zdefiniowanymi sytuacjami analizuje ciąg wejściowy a … a (n + 1 powtórzeń symbolu a, przy czym n > m) w czasie Ω(nm). Od W.A. Łapszyna[13] pochodzi pomysł przechowywania zbiorów takich par wskaźników jako wartości tablicy asocjacyjnej, której klucze to sytuacje Earleya, oraz algorytm budowy drzew rozbioru na podstawie grafu sytuacji połączonych tymi wskaźnikami. Wówczas złożoność czasowa samego parsera bez budowania drzew rozbioru nie przekracza rzędu n3. E. Scott[14] opublikowała natomiast algorytm, który w czasie O(n3) przetwarza graf sytuacji na taką wersję znanego z parserów GLR współdzielonego upakowanego lasu analizy (ang. shared packed parse forest), w której każdy węzeł ma najwyżej dwóch synów.
Na innej zasadzie opiera się propozycja D. Grunego i C.J.H. Jacobsa[15]: tworzenie drzew rozbioru ze zbioru sytuacji za pomocą parsera Ungera.
Operacja przewidywania w algorytmie Earleya może korzystać z podglądu (ang. lookahead). Działa ona wówczas następująco: „jeśli istnieje sytuacja [A →h α •i Bη], to dla każdej produkcji B → β dodaj sytuację [B →i •i β], o ile zbiór FIRST ciągu symboli β zawiera symbol terminalny ti”. Jak zwykle przy podglądzie, najwygodniej na końcu analizowanego ciągu symboli ustawić wartownika tn = $ spoza zbioru symboli terminalnych.
W oryginalnym artykule Earleya[6] zaproponowano stosowanie podglądu w operacji uzupełniania. Przy uzupełnianiu, inaczej niż przy przewidywaniu, nie da się z góry wyznaczyć zbioru dopuszczalnych następników, jeśli nie brać pod uwagę jego mniej wydajnej i ograniczonej wersji opartej na zbiorach FOLLOW. Wydajny podgląd przy uzupełnianiu polega na następujących modyfikacjach algorytmu:
Można też stosować podgląd więcej niż jednego symbolu terminalnego.
Warianty algorytmu Earleya z podglądem różnej liczby symboli przy przewidywaniu i uzupełnianiu badali M. Bouckaert, A. Pirotte i M. Snelling[16]. W ich symulacjach najlepszy okazał się podgląd jednego symbolu przy przewidywaniu, który zmniejszał liczbę sytuacji o 20–50% w stosunku do wersji bez podglądu, natomiast narzut stosowania jakiegokolwiek podglądu przy uzupełnianiu przewyższał zyski z niego płynące. Wiele implementacji algorytmu Earleya wcale nie używa podglądu, dzięki czemu mogą bezpośrednio korzystać z danej gramatyki, pomijając jej wstępne przetwarzanie służące wyznaczeniu zbiorów FIRST.
W artykule F.C.N. Pereiry i D.H.D. Warrena[17] pokazano, jak uogólnić tablicowe metody analizy składniowej na dowolne formalizmy gramatyczne oparte na unifikacji, również kontekstowe. Zapoczątkował on falę artykułów opisujących wersje algorytmu Earleya dla formalizmu unifikacji PATR-II[18], gramatyk przyłączania drzew (ang. tree adjoining grammar)[19][20], gramatyk S-atrybutywnych (ang. S-attribute grammar)[21], gramatyk hipergrafowych (ang. hypergraph grammar)[22], gramatyk sekwencyjnie indeksowanych (ang. sequentially indexed grammars)[23] itd. Technika zbiorów magicznych (ang. magic sets)[24] w dedukcyjnych bazach danych również opiera się na ideach Pereiry i Warrena.
S.L. Graham i M.A. Harrison[25] zwrócili uwagę na wspólne cechy algorytmu Earleya i algorytmu CYK i opracowali wraz z W.R. Ruzzo[26] algorytm analizy składniowej, stanowiący hybrydę tych dwóch algorytmów.
J. Aycock i N. Horspool[27][9] podali, jak skonstruować podobny do automatu LR(0) deterministyczny automat skończony, który parsuje dane wejściowe kilkukrotnie szybciej od tradycyjnych implementacji algorytmu Earleya.
A. Päseler[28] opracowała wariant algorytmu Earleya, która zamiast listy symboli terminalnych analizuje kratę słów (ang. word lattice). Kraty słów znajdują zastosowanie przy rozpoznawaniu mowy i analizie tekstów pisanych bez odstępów między wyrazami, np. w językach Dalekiego Wschodu.
A. Stolcke[29] opublikował algorytm, który zwraca najbardziej prawdopodobny rozbiór składniowy wejściowego ciągu symboli dla danej probabilistycznej gramatyki bezkontekstowej.
Wersje algorytmu Earleya, opracowane przez G. Lyona[30] i B. Langa[31], działają dla danych wejściowych o brakujących, nadmiarowych lub nieznanych fragmentach.
W wikibooks zamieszczono kod źródłowy w Pythonie parsera Earleya bez podglądu, przetwarzającego puste produkcje według pomysłu Earleya[6] i tworzącego drzewa rozbioru zgodnie z ideą Łapszyna[13]. Program wypisuje kolejno tworzone sytuacje oraz wszystkie drzewa rozbioru ciągu wejściowego, zapisane w notacji nawiasowej.
Program ten znajduje cztery drzewa rozbioru niejednoznacznego zdania time flies like an arrow. Narysowane np. za pomocą programu phpSyntaxTree[32], drzewa te wyglądają następująco:
muchy czasu lubią strzałę | czas leci jak strzała | mierz czas much podobnych do strzały | mierz czas much jak strzałę/jak strzała | |
Drzewa rozbioru zdania time flies like an arrow |
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.