6.10 Allgemeines Parsing

Wir haben drei Methoden kennengelernt, kontextfreie Sprachen zu parsen: rekursiver Abstieg (mit Demoseite drawManualGrammar.html), die LL-Parser (die die Mengen FIRSTk(X) berechnen, wie auf drawFirstComputation.html demonstriert), und die LR-Parser (die die Teilbäume auf den Stack legen und nach Blüten suchen, hier die Demoseite drawLR0ParserPrefixArithmetic für arithmetische Ausdrücke). Rekursiver Abstieg kann, wenn man nicht vorsichtig ist, in Endlosschleifen landen und kann im Allgemeinen selbst bei einfachen Grammatiken exponentielle Laufzeit aufweisen. LL-Parser und LR-Parser funktionieren schlicht nicht für allgemeine kontextfreie Grammatiken. Standardbeispiel ist die Palindromsprache ohne Kennzeichnung der Mitte: SaSaSbSbSa | b | ϵ Weder LL-Parser noch LR-Parser können bei langen Wörtern wie aaaaaaaaaaaaaabbaaaaaaaaaaaaaa erkennen, wo die Mitte ist. Das muss man aber wissen, denn sonst landet man in einer Sackgasse. Es gilt sogar: jeder Kellerautomat für diese Sprache muss nichtdeterministisch sein (was wir an dieser Stelle weder formal definieren noch beweisen). Noch schlimmer steht es mit Grammatiken wie SAY  |XCAaA | ϵCcC | ϵXaXb |ϵYbYc |ϵ Diese erzeugt die Sprache L={aibjck | i=j oder j=k} Die Grammatik ist mehrdeutig, insbesondere kann jedes Wort der Form aibici auf zwei Weisen abgeleitet werden: via AY und via XC. Man kann sogar zeigen, dass jede äquivalente Grammatik G, die also die gleiche Sprache L erzeugt, mehrdeutig sein muss; man sagt, die Sprache L ist inhärent mehrdeutig.

Für nichtdeterministische oder gar mehrdeutige Grammatiken / Sprachen sind LL- und LR-Parser unbrauchbar. Gibt es eine allgemeine Methode, die für alle Grammatiken funktioniert? Ja, den sogenannten CYK-Algorithmus. Nur leider ist die nicht besonders schnell. Sie hat kubische Laufzeit O(n3), was zwar in der theoretischen Informatik als effizient durchgeht, in der Praxis leider meist unbrauchbar ist.

Chomsky-Normalform

Eine kontextfreie Grammatik ist in Chomsky-Normalform, wenn jede Produktion eine der folgenden Formen hat: XYZXa Eine solche Sprache kann offensichtlich nicht das Wort ϵ ableiten. Daher lassen wir als Sonderregel die Produktion Sϵ zu, verbieten dann aber, dass das Startsymbol S auf der rechten Seite einer Produktion vorkommen kann.
Theorem 6.10.1 Zu jeder kontextfreien Grammatik gibt es eine äquivalente Grammatik in Chomsky-Normalform.

Anstatt hier einen formalen Beweis anzugeben (den Sie sich, wenn Sie wollen, im Lehrbuch oder auf Wikipedia anschauen können), lasse ich Sie lieber anhand einer Übungsaufgabe die Konstruktion von selbst verstehen:

Übungsaufgabe 6.10.1 Finden Sie zu der folgenden kontextfreien Grammatik SA | Bb | CAxyB | B | BCByzC | ACCxzA | AB | ϵ eine äquivalente in Chomsky-Normalform.

Fragen, die Sie sich dabei stellen sollten:

  1. Für welche Nichtterminale gibt es UV? Zeichnen Sie ein Bildchen mit all diesen -Pfeilen.
  2. Von welchen Nichtterminalen können Sie überhaupt Wörter ableiten, also UwΣ? Wie finden Sie das im Allgemeinen heraus?
  3. Welche Nichtterminale können ϵ ableiten, also Uϵ? Wie finden Sie das im Allgemeinen heraus?

Wenn nun G in Chomsky-Normalform vorliegt und wir für ein gegebenes Eingabewort w eine Ableitung G:Sw finden wollen (oder feststellen, dass es keine gibt), so ist die erste Beobachtung, dass eine Linksableitung die Form SABuBuv haben muss, für w=uv. Wenn wir die Unterteilung von w in u und v kennen würden, so könnten wir rekursiv fragen, wie man denn Au und Bv ableitet. Da wir sie aber nicht kennen, also konkret nicht wissen, wie lange u und v sind, können wir alle Möglichkeiten durchprobieren. Da G in Chomsky-Normalform vorliegt, wissen wir, dass |u|1 und |v|1, also 1|u||w|1. Wir probieren also alle n1 möglichen Zerlegungen von w durch. Wenn wir das rekursiv täten, dann würden das eine enorme Laufzeit verursachen. Der Trick besteht darin, Zwischenergebnisse systematisch zu berechnen, um somit Laufzeit zu sparen.

Der CYK-Algorithmus

Die oben skizzierte Idee ist im CYK-Algorithmus konkretisiert (benannt nach John Cocke, Daniel Younger und Tadao Kasami). Für die praktische Anwendung ist dieser weniger relevant. Dafür ist er ein wunderbares Beispiel für einen Algorithmus, der auf dem Prinzip des Dynamic Programing fußt, welches Sie in der Vorlesung Algorithmen und Komplexität im dritten Semester ausführlicher kennenlernen wollen. Wir beschränken uns bei dem folgenden Algorithmus zunächst darauf, die Frage zu beantworten, ob den überhaupt Sw gilt, und interessieren uns erst einmal nicht dafür eine solche Ableitung auch zu finden (in der Algorithmik versteht man das als Entscheidungsproblem, im Gegensatz zu dem allgemeinerin Suchproblem).

Der Entwurf eines Dynamic-Programming-Algorithmus beginnt oft mit der folgenden Frage: Was sind sinnvolle Zwischenergebnisse? In unserem Falle sind Ableitungen der Form Xu sinnvolle Zwischenergebnisse, wenn u ein Teilwort von w ist, also w=v1uv2. Konkret schreiben wir w=w0w1wn1 und definieren w[i:j]:=wiwi+1wj1 und Ni,j:={XN | Xw[i:j]} . Das ist also die Menge der Nichtterminale, die das Teilwort w[i:j] ableiten können. Die "Hauptfrage" ist dann: enthält N0,|w| das Startsymbol S? Der CYK-Algorithmus berechnet nun die Mengen Ni,i+d systematisch für d=1,,n, versucht also, alle Unterwörter der Länge d abzuleiten, beginnend mit d=1, also Ni,i+1. Diese Mengen sind leicht zu bestimmen: (1)Ni,i+1:={XN | Xwi ist eine Produktion in G} . Das gilt nur, weil G in Chomsky-Normalform vorliegt und somit Ableitungen mit mehr als einem Schritt notwendigerweise Wörter mit mehr als einem Zeichen produzieren würden. Nun müssen wir uns Gedanken machen, wie wir Ni,k berechnen, also das Unterwort w[i:k], das Länge ki hat, ableiten, wenn wir bereits wissen, wie wir kürzere Unterwörter herleiten. Die Kernbeobachtung ist: XNi,k, also Xw[i:k] gilt genau dann, wenn es eine Produktion XYZ gibt, so dass dann Yw[i:j]Zw[j:k] gilt. Das Problem ist, wie bereits oben skizziert, dass wir die "Grenze" j nicht kennen. Wir probieren also alle Grenzen aus, und somit (2)Ni,k=j=i+1k1{XN |  es gibt XYZ mit YNi,j und ZNj,k} . Dies können wir mit einer Schleife über j=i+1k1 und einer Schleife über alle Produktionen XYZ berechnen, da wir die Mengen Ni,j und Njk ja bereits kennen, da kj,ji<ki, diese Bereiche also kleinere Unterwörter darstellen.

  • Initialisiere für alle 0i<n die Mengen Ni,i+1:={XN | Xwi ist eine Produktion in G}
  • for l = 2 .. n
    1. for i = 0 .. n-l
      1. k := i+l # wir betrachten das Interval w[i:k] der Länge l
      2. Berechne Ni,k wie in (2). Konkret heißt das:
      3. Initialisiere
      4. for j = i+1 .. k-1:
        1. for all productions :
          1. füge zu hinzu, falls und gilt.
        2. end for all productions
      5. end for j
    2. end for k
  • end for i
  • return True if else return False
  • Was ist die Laufzeit des Algorithmus? Sei die Anzahl der Produktionen in der Form . Die innerste Schleife, also Punkt a, wird jedes Mal mal durchlaufen, und somit wird Zeile a insgesamt ausgeführt. Können Sie eine geschlossene Form für die drei Summen angeben? Leichter geht es, wenn wir erkennen, dass für alle die Ungleichung gilt, und jedes solche Tripel auch drankommt. Wie viele solche Tripel gibt es? Genau viele. Also: die Zeile a wird mal durchlaufen. Also "ungefähr" und "noch ungefährer" . Also:

    Beobachtung 6.10.2 Sei die Länge des Eingabewortes und die Anzahl der Produktionen der Form . Dann ist die Laufzeit des CYK-Algorithmus proportional zu .

    Achtung: wir haben die Kosten für die allererste Zeile vernachlässigt. Überlegen Sie sich, wie viel Laufzeit diese verursachen kann und überzeugen Sie sich, dass dies in den meisten Fällen gegenüber dem Term wohl nicht ins Gewicht fallen wird.

    Demo 6.10.3 Betrachten wir die Grammatik

    und das Eingabewort .

    Wenn wir uns zusätzlich zu jedem noch merken, durch welche Regeln es aufgenommen wurde und für welches man hat, dann können wir den Ableitungsbaum leicht rekonstruieren.