4.4 Von einer regulären Grammatik zu einem endlichen Automaten

In diesem Unterkapitel wollen wir das Gelernte an einem konkreten Beispiel anwenden. Wir beginnen (1) mit einer Beschreibung eines Formats in natürlicher Sprache; (2) basteln uns daraus mit Hilfe des "Baukastenprinzips" eine reguläre Sprache; (3) säubern diese, indem wir Regeln der Form \(X \rightarrow Y\) und \(X \rightarrow a\) eliminieren; (4) bauen einen nichtdeterministischen endlichen Automaten; (5) transformieren diesen in einen deterministischen endlichen Automaten.

1. Beschreibung des Formats in natürlicher Sprache

Unsere Sprache \(L\) soll so ähnlich sein wie die der erlaubten Domainnamen, allerdings mit ein paar Abänderungen, um die obigen Transformationen spannender zu machen. Ein Wort in unserer Sprache besteht aus einer nichtleeren Folge von Labels die jeweils durch einen . separiert sind. Jedes Label ist eine nichtleere Folge von Blöcken (ein nichtleerer String aus Buchstaben und Zahlen), separiert durch : oder - aber niemals durch beides innerhalb eines Blockes. Also:

bla:bla:blue.xyz-12-zx.b:x:yyy:xxx:aaa
ist ein Wort in \(L\), aber
a:b-c.hello
ist kein Wort in \(L\), da das erste Label die Separatoren : und - mischt. Habe ich \(L\) genau genug beschrieben? Stellen wir eine Meta-Frage: Was zählt überhaupt als genaue Beschreibung einer Sprache? Wir können uns dem Mund fusselig reden und Beispiele und Nicht-Beispiele angeben, am Ende aber werden wir irgendwann beginnen, formale Regeln aufzustellen, die unsere Sprache beschreiben - wir werden also im Prinzip eine Grammatik schreiben. Tun wir dies also.

2. Eine reguläre Grammatik

Beginnen wir mit dem Alphabet. Da es 62 alphanumerische Zeichen gibt: a..zA..Z0..9 und wir uns keine unnötige Arbeit machen wollen, beschränken wir uns auf ein Zeichen: a. Dazu kommen die Separatoren :-.. Also: \(\Sigma = \{a,.,:,-\}\). Stellen Sie sich einfach vor, \(a\) stehe für beliebige alphanumerische Zeichen. Sowohl Grammatik als auch Automaten lassen sich einfach anpassen. Wir beginnen ganz unten und schreiben eine Grammatik für Blöcke, also nichtleere Strings aus alphanumerischen Zeichen.

\begin{align*} B & \rightarrow a \ | \ aB \end{align*}

Als nächstes führen wir ein nichtterminales Symbole \(C\) für Labels mit : ein und ein Nichtterminal \(D\) für Labels mit -. Wir wählen die Buchstaben \(C,D\), weil : auf Englisch colon und - dash heißt. \(C\)-Labels können wir uns nach dem Baukastenprizip bauen, in dem wir Theorem 4.1.14 anwenden. Wir fügen zur "End-Produktion" \(B \rightarrow a\) eine weiter Produktion \(B \rightarrow a:B\) hinzu und tun das gleiche für \(B \rightarrow b\). Allerdings benennen wir \(B\) in \(C\) um, damit keine Verwechslungsgefahr mit dem ursprünglichen \(B\) aufkommt. Das gleiche machen wir für \(D\).

\begin{align*} C & \rightarrow a \ | \ aC \ | \ a{:}C \\ D & \rightarrow a \ | \ aD \ | \ a{-}D \\ T & \rightarrow C \ | D \end{align*}

Von \(L\) lassen sich nun also alle Labels ableiten. Wir brauchen nun zum Schluss wieder eine Folge von \(L\), mit . separiert, müssen also wieder Theorem 4.1.14 anwenden, dieses mal auf die von \(T\) erzeugte Sprache. Im Ergebnis benennen wir das Startsymbol in \(S\) um.

\begin{align*} S & \rightarrow C \ | \ D \\ C & \rightarrow a \ | \ aC \ | \ a{:}C \ | a{.}S \ \\ D & \rightarrow a \ | \ aD \ | \ a\text{-}D \ | \ a{.}S \\ \end{align*}

Um eine "richtig" reguläre Sprache zu erhalten, entzerren wir die erweitert regulären Produktionen wie \(C \rightarrow a{:}C\). Dafür brauchen wir neue Symbole \(C', D', S'\):

\begin{align*} S & \rightarrow C \ | \ D \\ C & \rightarrow a \ | \ aC \ | \ aC'\ | \ aS' \\ C' & \rightarrow {:}C \\ D & \rightarrow a \ | \ aD \ | \ a D' \ | \ aS' \\ D' & \rightarrow \text{-}D \\ S' & \rightarrow {.}S \end{align*}

3. Die reguläre Grammatik säubern

Wir wollen nun alle Produktionen der Form \(Y \rightarrow x\) eliminieren. Hierfür nehmen wir uns ein neues Nichtterminal \(E\) und ersetzen \(Y \rightarrow x\) durch \(Y \rightarrow xE\) und fügen die Produktion \(E \rightarrow \epsilon\) hinzu.

\begin{align*} S & \rightarrow C \ | \ D \\ C & \rightarrow aE \ | \ aC \ | \ aC'\ | \ aS' \\ C' & \rightarrow {:}C \\ D & \rightarrow aE \ | \ aD \ | \ a D' \ | \ aS' \\ D' & \rightarrow \text{-}D \\ S' & \rightarrow {.}S \\ E & \rightarrow \epsilon \end{align*}

In einem zweiten Schritt wollen wir die Produktionen \(S \rightarrow C\) und \(S \rightarrow D\) eliminieren, sodass wir nur noch Produktionen der From \(X \rightarrow aY\) und \(E \rightarrow \epsilon\) haben. Wir gehen vor wie in Theorem 4.1.7 beschrieben. Wir ersetzen \(S \rightarrow C\) also durch alle Produktionen der Form \(S \rightarrow \alpha\), wobei \(\alpha\) eine Wortform ist, die sich aus \(C\) ableiten lässt und nicht nur aus einem einzelnen Nichtterminal besteht; dies trifft glücklicherweise auf alle rechten Seiten der \(C\)-Produktionen zu; gleiches gilt für \(D\). Wir erhalten:

\begin{align*} S & \rightarrow aE \ | \ aC \ | \ aC'\ | \ aS' \ | \ aD \ | \ a D' \\ C & \rightarrow aE \ | \ aC \ | \ aC'\ | \ aS' \ \\ C' & \rightarrow {:}C \\ D & \rightarrow aE \ | \ aD \ | \ a D' \ | \ aS' \ \\ D' & \rightarrow \text{-}D \\ S' & \rightarrow {.}S \\ E & \rightarrow \epsilon \end{align*}

4. Einen nichtdeterministischen endlichen Automaten bauen

Dies sollte nun einfach sein. Wir erschaffen Zustände \(S, C, C', D, D', S', E\) und übersetzen jeden Grammatik-Pfeil in einen Automaten-Pfeil.

Ich habe die Zeichen .:- rot unterlegt, weil man sie sonst kaum erkennen würde in dem Automaten.

5. Den nichtdeterministischen Automaten in einen deterministischen umwandeln

Unser nichtdeterministischer endlicher Automat hat Zustandsmenge \(Q = \{S, S', C, C', D, D', E\}\), also insgesamt sieben Zustände. Wenn wir genau nach Buch vorgingen, müssten wir den endlichen Automaten auf der Zustandsmenge \(2^Q\) definieren, er hätte also \(2^7 = 128\) viele Zustände. Das wäre jetzt für einen Rechner kein Problem, aber in diesem vorlesungsskript doch etwas ungünstig. Wir gehen lazy vor, erschaffen Zustände in \(2^Q\) also nur dann, wenn wir sie brauchen. Wir beginnen mit dem Zustand \(\{S\}\) und legen dann an jeden Zustand Kanten an, jeweils mit \(a, :, -, . \) beschriftet, und erschaffen, falls nötig, dabei neue Zustände. In der folgenden Animation sehen Sie manchmal den mit einem Kreuz markierten Fehlerzustand (trap state). Die akzeptierenden Zustände sind mit fettem Rand markiert.

Der nichtdeterministische Automat

Dieser Automat hat deutlich weniger also 128 Zustände, nämlich mit nur sieben genau so viele wie der nichtdeterministische (es ist Zufall, dass beide gleich viele Zustände haben; messen Sie dieser Tatsache keine Bedeutung bei). Wir könnten nun von diesem Automaten ausgehend wiederum eine reguläre Grammtik bauen, die in diesem Falle sogar einfacher und klarer wäre als die ursprüngliche. Wenn wir erweitert reguläre Grammatiken erlauben, so können wir den deterministischen Automaten besonders konzise in eine Grammatik fassen:

\begin{align*} S & \rightarrow aT \\ T & \rightarrow {.}S \ | \ aT \ | \ {:}aC \ | \ \text{-}a D \\ C & \rightarrow aC \ | \ {:}aC \ | \ {.}S \\ D & \rightarrow aD \ | \ \text{-}aD \ | \ {.}S \end{align*}

Die Zustände des deterministischen Automaten beschreiben im Prinzip das, was wir uns merken müssen, wenn wir so einen String "parsen": Zustand \( \{C',C,E,D,D',S'\}\), der in der Grammatik dann zum Nichtterminal \(T\) wird, bedeutet beispielsweise das Label hat schon begonnen, wir wissen aber noch nicht, ob es eines mit : oder eines mit - ist. Der Zustand \(\{C',C,E,S'\}\) bzw. das Nichtterminal \(C\) heißt dann wir sind innerhalb eines Labels mit :.

Übungsaufgabe Erinnern Sie sich an Aufgabe 4.3.1. Hier sehen Sie einen nichtdeterministischen endlichen Automaten für die Sprache \(L_4 \cup L_6\), also die Sprache aller Wörter \(1^n\) für ein \(n\), das durch 4 oder 6 teilbar ist. Da unser Alphabet \(\Sigma = \{1\}\) eh nur aus einem Zeichen besteht, habe ich auf die Beschriftung der Kanten verzichtet.

Dieser Automat hat 11 Zustände. Sein Potenzmengenautomat hätte also \(2^{11} = 2048\) Zustände. Führen Sie die Konstruktion lazy durch, indem Sie vom Startzustand \(\{S\}\) ausgehend die Folgezustände konstruieren. Wieviele Zustände bekommen Sie?