Generieren einer Reihe von Zeichenfolgen mit minimaler Länge, die zusammen jede Produktion einer kontextfreien Sprache aufrufen

7

Problem (tl; dr)

Wenn eine kontextfreie Grammatik , finden Sie eine Reihe von Zeichenfolgen, die mindestens einmal durch jede Produktion führen.GG

Wie und wie schnell geht das?

Hintergrund

Ich arbeite an einem Compiler, dessen Parser mit einem ähnlichen Tool wie Yacc + Antlr implementiert ist. Ich habe den größten Teil des Parser-Codes geschrieben und möchte einen Code der Objektsprache generieren, der jede Produktion der Grammatik mindestens einmal aufruft, damit ich ihn dem Parser zuführen und sicherstellen kann, dass nichts falsch ist .

Im Interesse eines guten Testens möchte ich wirklich eine kurze Testdatei, die eine bestimmte Produktion "im Test" hat. Daher möchte ich für jede Produktionsregel eine minimale Zeichenfolge generieren, die den Parser aus dem Startzustand durch die getestete Produktion zu einer Reihe von Terminals.

Mögliche Lösungen

Ich stelle mir vor, dass es eine elegante Lösung mit Graphentheorie gibt, aber ich bin mir nicht ganz sicher, was es ist. Ich möchte nur den Dijkstra-Algorithmus verwenden, um kürzeste Pfade durch eine geeignete Struktur zu finden, aber ich denke, dass eine Zeichenfolge eher durch eine kontextfreie Grammatik in einer Baumstruktur als durch einen Pfad analysiert wird, daher weiß ich nicht, wie das funktioniert .

Ich denke, es könnte eine clevere Möglichkeit geben, es als Netzwerkflussproblem darzustellen. So etwas wie das: Nehmen Sie ein Diagramm, das einen Scheitelpunkt für jedes Symbol (Terminal und Nicht-Terminal) und einen Scheitelpunkt für jede Produktion hat. Wenn ein Nichtterminal eine Produktion hat, fügen Sie der Produktion eine gerichtete Kante vom Nichtterminal hinzu. Wenn eine Produktion ein Symbol erzeugt, fügen Sie dem Symbol eine gerichtete Kante aus der Produktion hinzu. Fügen Sie eine Quelle mit einer Kapazität und hängen Sie sie an den Scheitelpunkt an, der dem Startsymbol entspricht. Fügen Sie eine Spüle mit unendlicher Kapazität hinzu und befestigen Sie sie an jedem Terminal.c

Wenn ein Nichtterminal einen In-Arc mit einer Kapazität , fügen Sie jeder seiner Produktionen mit der Kapazität einen Bogen vom Nonterminal hinzu . Wenn eine Produktion einen In-Arc mit der Kapazität und einen Out-Arc zu Nichtterminals hat, fügen Sie jedem Nonterminal einen Arc mit der Kapazität aus der Produktion hinzu.kkknkn

Führen Sie dann einen Maximum-Flow-Algorithmus im Netzwerk aus und lassen Sie die Produktionen vom Startsymbol zu den Terminals "herunterrinnen". Am Ende sollte ein Flow aus Ihrer Quelle kommen, und Sie können alle Terminals, die Sie mit einem Flow ungleich Null getroffen haben, als Ergebniszeichenfolge zurückgeben. Dann erhalten Sie für jeden Lauf eine Zeitkomplexität von , wobei die Summe der Anzahl der Terminals und Nicht-Terminals in Ihrer Grammatik ist - nicht schlecht.cO(n3)n

Ich bin mir jedoch immer noch nicht sicher, wie dieses Diagramm aussieht: Ich denke, dass es unendlich sein muss, und ich bin mir nicht sicher, ob Sie den maximalen Fluss eines Netzwerks mit unendlichem Fluss finden können. Danach bin ich mir nicht sicher, wie ich eine Produktion aus der Betrachtung "entfernen" soll, damit ich garantiert für jeden Testlauf eine neue bekomme.

Ich habe gegoogelt und konnte nichts finden. Gibt es eine gute Lösung für dieses Problem?

Patrick Collins
quelle
1
Kann die Eingabegrammatik mehrdeutig sein? Wenn ja, zählt eine Zeichenfolge für alle Produktionen in allen Ableitungen? Können wir annehmen, dass die Grammatik reduziert ist, dh keine "Sackgasse" -Regeln enthält (jede Regel ist über das Startsymbol erreichbar und hat eine rechte Seite, die sich aus einem Wort ableiten lässt)?
Raphael
@ Raphael Die spezielle Grammatik, mit der ich arbeite, ist an einigen Stellen mehrdeutig, kann aber bei Bedarf der Einfachheit halber ignoriert werden. Ich möchte lieber eine mehrdeutige Zeichenfolge, die nicht für alle möglichen Ableitungen zählt, da mein Ziel darin besteht, den Parser zu zwingen, jeden Codezweig zu treffen, aber wie ich sage, ist mir klar, dass dies möglicherweise nicht möglich ist. Die Grammatik ist reduziert.
Patrick Collins
Versuchen Sie es rückwärts. Erstellen Sie eine Reihe von Regeln, bei denen die rechte Seite (RHS) eine Terminalzeichenfolge ist (einschließlich des leeren Wortes, bei dem es sich um eine leere Zeichenfolge von Terminals handelt). Schauen Sie sich dann nacheinander alle verbleibenden Regeln an, um festzustellen, ob eine RHS vorhanden ist, die mit den Regeln in Ihrem Satz in eine Terminalzeichenfolge abgeleitet werden kann ... und versuchen Sie, diese selbst zu beenden. Zwei Bemerkungen: Ich bin sehr überrascht, dass Sie einen Parser "ähnlich wie Yacc + Antlr" mit einem so begrenzten Wissen über kontextfreie Grammatiken programmiert haben. Der zweite Punkt ist, dass ich bezweifle, dass Ihr Test ausreicht: Regeln können beim Parsen interagieren.
Babou
Auf jeden Fall ist die Codeabdeckung als sehr grobe Teststrategie bekannt. Was mehr ist, haben Sie eine Programmiersprache mit mehrdeutiger Syntax? Ach je.
Raphael
@ Raphael Mehrdeutige Grammatik für eine Programmiersprache stört mich nicht. Ich befürworte sie seit vielen Jahren. Die US-Verteidigung setzt seit rund 30 Jahren einen ein. Die Technologie hängt jedoch davon ab, wie mehrdeutig und wie mit Mehrdeutigkeiten umgegangen werden soll. Wenn die Technologie Yacc + Antlr-ähnlich sein soll, verhält sich der Parser so, als ob die Sprache eindeutig wäre: wenn erzwingt eine Auswahl. Andere Technologien können die Mehrdeutigkeit beibehalten. Aber ich bin immer noch besorgt darüber, dass alles ein Verständnis erfordert, das in der Frage nicht vorhanden zu sein scheint.
Babou

Antworten:

3

In einer Nussschale

Da ich die Literatur nicht genug kannte, erarbeitete ich eine Lösung, die im nächsten Abschnitt vorgestellt wird, zusammen mit einem Beweis für den schwierigsten Teil. Sobald ich wusste, was gebraucht wurde, konnte ich in der Literatur nach den richtigen Ideen suchen. Hier ist eine kurze Darstellung des Algorithmus, basierend auf der Literatur, die im Wesentlichen dieselbe ist, die ich entwickelt habe.

Das erste, was Sie tun müssen, ist, eine Terminalzeichenfolge mit minimaler Größe zu finden σ(U) für jedes Nicht-Terminal Uder Grammatik. Dies kann erreicht werden, indem Knuths Erweiterung auf Conc- oder Graphen (auch als CF-Grammatiken bezeichnet) und - oder Graphen des Dijkstra-Algorithmus für kürzeste Wege verwendet wird . Beispiel B in Knuths Artikel macht fast das, was benötigt wird.

Eigentlich berechnet Knuth nur die Länge dieser Terminalzeichenfolgen, aber es ist ziemlich einfach, seinen Algorithmus so zu ändern, dass tatsächlich eine solche Terminalzeichenfolge berechnet wird σ(U) für jedes Nicht-Terminal U(wie ich es in meiner eigenen Version unten mache). Wir definieren auchσ(a)=a für jedes Terminal aund wir verlängern σ wie üblich in einen String-Homomorphismus.

Dann betrachten wir einen gerichteten Graphen, in dem Nicht-Terminals die Knoten sind und es einen Bogen gibt (U,V) Wenn es eine Regel gibt UαVβ. Wenn mehrere solcher Regeln den gleichen Bogen erzeugen können(U,V)Wir behalten eine so, dass die Länge |σ(αβ)|ist minimal. Der Bogen ist mit dieser Regel und dieser minimalen Länge gekennzeichnet |σ(αβ)| wird das Gewicht des Bogens.

Schließlich berechnen wir unter Verwendung des Dijkstra-Algorithmus für den kürzesten Pfad den kürzesten Pfad aus dem anfänglichen Nicht-Terminal Szu jedem Nicht-Terminal der Grammatik. Gegeben der kürzeste Weg für ein Nicht-TerminalUkönnen die Regelbezeichnungen auf den Bögen verwendet werden, um eine Ableitung zu erhalten SαUβ. Dann zu jeder Regel des FormularsUγ In der Grammatik ordnen wir die Terminal-Zeichenfolge mit minimaler Größe zu σ(αγβ) die mit dieser Regel abgeleitet werden können.

Um eine geringe Komplexität zu erreichen , werden sowohl der Dijkstra-Algorithmus als auch die Knuth-Erweiterung mit Haufen und AKA-Prioritätswarteschlangen implementiert . Dies gibt für den Dijkstra-Algorithmus eine Komplexität vonO(nlogn+t)und für Knuths Algorithmus eine Komplexität O(mlogn+t), wo sind sie m Grammatikregeln und n Nicht-Terminals und tist die Gesamtlänge aller Regeln. Das Ganze wird seitdem von der Komplexität des Knuth-Algorithmus dominiertmn.

Was folgt, ist meine eigene Arbeit, bevor ich die kurze Antwort oben produzierte.

Ableiten der Lösung aus dem Algorithmus zur Beseitigung nutzloser Symbole.

Dieser Algorithmus hat mehrere Aspekte. Zur besseren Intuition habe ich mich entschieden, es in drei aufeinander folgenden Versionen zu präsentieren, die zunehmend mehr Funktionen einführen. Die erste Version beantwortet die Frage nicht, ist jedoch ein Standardalgorithmus für die Beseitigung nutzloser Symbole, der eine Lösung vorschlägt. Die zweite Version beantwortet die Frage ohne die Minimalitätsbeschränkung. Die dritte Version gibt eine Antwort auf die Frage und erfüllt die Minimalitätsbeschränkung. Diese dritte Lösung wird dann verbessert, indem eine Anpassung an und / oder Diagramme des Dijkstra-Algorithmus für kürzeste Wege verwendet wird .

Das Endergebnis ist ein sehr einfacher Algorithmus, der es vermeidet, bereits durchgeführte Berechnungen zu überdenken. Aber es ist weniger intuitiv und erfordert einen Beweis.

Diese Antwort versucht nur, die Frage zu beantworten, die durch den Kommentar des OP präzisiert wurde: " Für jede Produktionsregel möchte ich eine minimale Zeichenfolge generieren, die den Parser vom Startzustand über die getestete Produktion zu einer Reihe von Terminals führt. "Daher versuche ich nur, eine Reihe von Zeichenfolgen zu erhalten, sodass für jede Regel eine Zeichenfolge in der Gruppe vorhanden ist, die eine der Zeichenfolgen mit minimaler Größe der Sprache ist, die eine Ableitung unter Verwendung der Regel aufweist.

Es muss jedoch beachtet werden, dass die Tatsache, dass eine Zeichenfolge eine Regel "aufruft", dh eine Ableitung unter Verwendung dieser Regel hat, nicht unbedingt bedeutet, dass die Regel von einem Parser berücksichtigt wird, der mit mehrdeutigen Grammatiken arbeitet und Mehrdeutigkeiten willkürlich auflöst. Die Behandlung einer solchen Situation würde wahrscheinlich genauere Kenntnisse des Parsers erfordern und könnte eine komplexere Frage sein.

Der grundlegende Algorithmus

Um diese Frage zu lösen, kann man mit dem klassischen Algorithmus zum Entfernen nutzloser Symbole in kontextfreien Grammatiken beginnen. Es befindet sich in Abschnitt 4.4, S. 88-89, von Hopcroft & Ullman, Ausgabe 1979. Aber die Präsentation hier kann etwas anders sein.

Der Algorithmus zielt genau darauf ab, das Vorhandensein einer solchen Abdeckung zu beweisen, wie vom OP gefordert, und besteht aus zwei Teilen:

  • Lemma 4.1 von H & U, Seite 88: Entfernen aller unproduktiven Nicht-Terminals . Dazu wird versucht, für jedes Terminal eine Terminalzeichenfolge zu finden, von der es abgeleitet werden kann. Eine einfache Möglichkeit, dies zu erklären, lautet wie folgt: Sie erstellen eine MengeProdod produktive Symbole, die Sie mit allen Terminals initialisieren. Dann enthält jede noch nicht verarbeitete Regel alle rechtsseitigen Symbole (RHS)ProdFügen Sie dem Set das linke Terminal (LHS) hinzu Prodund Sie entfernen alle Regeln mit demselben LHS-Nicht-Terminal aus dem zu verarbeitenden Regelsatz. Sie wiederholen den Vorgang, bis keine Regel mehr mit allen RHS-Symbolen vorhanden istProd. Die restlichen Nicht-Terminals, nicht inProd am Ende dieses Prozesses sind sie nicht produktiv: Sie können nicht in eine Terminalzeichenfolge abgeleitet und somit aus der Grammatik entfernt werden.

  • Lemma 4.2 von H & U, Seite 89: Entfernen aller nicht erreichbaren Symbole . Dies geschieht durch die klassische Erreichbarkeit von Knoten in gerichteten Graphen, indem Nicht-Terminals als Knoten betrachtet werden und ein Bogen vorliegt(U,V) Wenn es eine Regel gibt Uα so dass V tritt auf in α. Sie erstellen ein SetReach von erreichbaren Symbolen, die nur mit dem Anfangssymbol initialisiert werden S. Dann für jedes nicht-terminale SymbolU im Reach oder später hinzugefügt, und für jede Regel Uα, fügst du hinzu Reach alle Symbole in α. Wenn alle Nicht-Terminals inReach Auf diese Weise wurden alle Symbole (Terminal oder Nicht-Terminals) verarbeitet, die nicht in enthalten sind Reachkann nicht in einer vom Anfangssymbol abgeleiteten Zeichenfolge erscheinen und ist daher unbrauchbar. Somit können sie aus der Grammatik entfernt werden.

Diese beiden grundlegenden Algorithmen sind nützlich, um die Rohergebnisse einiger Grammatikkonstruktionstechniken zu vereinfachen, wie sie beispielsweise für die Überschneidung einer kontextfreien Sprache und einer regulären Menge verwendet werden. Dies ist insbesondere nützlich, um die Ergebnisse allgemeiner CF-Parser zu bereinigen .

Das Entfernen von nicht-terminalen Symbolen ist im Zusammenhang mit der Lösung der gestellten Frage erforderlich, da die Regeln, die sie verwenden, von keiner Zeichenfolge der Sprache "aufgerufen" (dh in ihrer Ableitung verwendet) werden können.

Erstellen eines Satzes von Zeichenfolgen, die jede Regel aufrufen

(Wir suchen noch nicht nach minimalen Zeichenfolgen.)

Wenn man nun speziell die Frage beantwortet, muss man tatsächlich alle nutzlosen Symbole entfernen, ob nicht erreichbare Symbole oder unproduktive nicht-terminale Symbole, sowie nutzlose Regeln mit solchen nutzlosen nicht-terminalen Symbolen wie LHS. Sie haben keine Chance, beim Parsen einer Terminalzeichenfolge jemals sinnvoll aufgerufen zu werden (obwohl einige möglicherweise die Verarbeitungszeit eines Parsers verschwenden, wenn sie nicht entfernt werden; welche Zeit verschwenden können, hängt von der Parsertechnologie ab).

Wir betrachten nun für jede (nützliche) Regel die Erzeugung einer Terminalzeichenfolge, die sie aufruft, dh die unter Verwendung dieser Regel erzeugt werden kann. Dies ist im Wesentlichen das, was diese beiden oben genannten Algorithmen tun, obwohl sie die Informationen nicht speichern, da sie damit zufrieden sind, die Existenz dieser Zeichenfolgen zu beweisen, um sicherzustellen, dass Nicht-Terminals sowohl erreichbar als auch produktiv sind.

Wir modifizieren den ersten Algorithmus (Lemma 4.1), indem wir uns an jedes Nicht-Terminal halten U im Set Prod eine Terminalzeichenfolge σ(U) es leitet sich ab auf: Uσ(U). Für jedes Terminal definieren wir dieσals Identitätszuordnung. WannU wird dem Set hinzugefügt Prod weil eine Regel Uγ hat alle seine RHS-Symbole in Prod, dann definieren wir σ(U)=σ(γ), verlängern σ als Homomorphismus auf Strings, und wir entfernen alle U-Regeln, das sind alles Regeln mit U als LHS.

Wir modifizieren den zweiten Algorithmus (Lemma 4.2), indem wir jedes nicht-terminale Symbol beibehalten U hinzugefügt zu Reach der Pfad, über den es vom Anfangssymbol aus erreicht wurde S, was die aufeinanderfolgenden Regeln gibt, um eine Ableitung zu erhalten SαUβ.

Dann für jede Regel UγIn der Grammatik erzeugen wir eine Terminalzeichenfolge, die diese Regel wie folgt "aufruft". Wir nehmen aus dem Ergebnis des zweiten Algorithmus die Ableitung SαUβ. Dann wenden wir die Regel an, um den String zu erhaltenαγβ. Eine Terminalzeichenfolge, die die Regel "aufruft"Uγ ist σ(αγβ)

Erstellen einer Reihe minimaler Zeichenfolgen, die jede Regel "aufrufen"

Wir ignorieren das Problem der Beseitigung nutzloser Symbole, die ein Nebenprodukt dieser modifizierten Algorithmen sein können.

Das Erstellen eines Satzes minimaler Zeichenfolgen setzt voraus, dass zuerst eine minimale abgeleitete Zeichenfolge für jedes Nicht-Terminal abgerufen wird. Dies erfolgt durch weitere Modifikation des ersten Algorithmus (Lemma 4.1). Zuerst entfernen wir alle rekursiven Regeln aus dem zu verarbeitenden Regelsatz (dh mit einem LHS-Symbol in der RHS-Zeichenfolge). Es ist offensichtlich, dass keine dieser Regeln zu einer kürzeren Terminalzeichenfolge führen kann als die nicht rekursiven Regeln mit derselben LHS. Und es muss mindestens eine nicht rekursive Regel geben, wenn die LHS kein nutzloses Nicht-Terminal ist (weil nicht produktiv).

Dann gehen wir wie zuvor vor, um das Set zu erstellen Prod von produktiven Symbolen, die mit jedem Synbol assoziiert sind U eine Terminalzeichenfolge, die wir notieren σ(U). Die Saiteσ(U) wird wie bisher durch Anwendung der Regel hergestellt Uγ, Ersetzen jedes Nicht-Terminals V auftreten in γ mit σ(V). Bisher war es notwendig, dies nur auf eine Regel mit einem bestimmten Nicht-Terminal anzuwendenU als seine LHS, die erste, die alle seine RHS-Nicht-Terminals in hätte Prodund ignorieren Sie dann die anderen, da eine solche abgeleitete Zeichenfolge ausreichen würde. Aber wir suchen jetzt nach einer minimalen abgeleiteten Zeichenfolge. Daher für ein Nicht-TerminalUDies muss für alle Regeln mit gemacht werden Uals LHS. Wir behalten jedoch nur eine Terminalzeichenfolgeσ(U)Ersetzen des aktuellen durch den neu gefundenen, wenn der neue kleiner ist.

Außerdem, wann immer die Zeichenfolge σ(U) wird durch eine kleinere ersetzt, alle Regeln mit einem Vorkommen von Uin der RHS, die bereits verarbeitet wurde, müssen in den zu verarbeitenden Regelsatz zurückgesetzt werden, da durch Änderung die RHS auf einer kürzeren Zeichenfolge abgeleitet werden kann. Dies erfordert mehr Iterationen, endet jedoch irgendwann, da keine dieser Zeichenfolgen jemals viel kürzer als die leere Zeichenfolge wird.

Am Ende dieses ersten Algorithmus die Zeichenfolge σ(U) ist eine der kleinsten Zeichenfolgen, aus denen abgeleitet werden kann U. Es kann andere geben.

Jetzt müssen wir auch den zweiten Algorithmus modifizieren, um für jedes Nicht-Terminal zu erhalten U, (einer von) der kürzesten Zeichenfolge, die U als einziges Nicht-Terminal enthält. Zu diesem Zweck behalten wir den gleichen gerichteten Graphen mit Nicht-Terminals als Knoten bei und haben einen Bogen(U,V) Wenn es eine Regel gibt UαVβ. Jetzt legen wir Gewichte auf die Bögen, um die Mindestlänge des Terminalkontexts zu berechnen, die mit erreichbaren Nicht-Terminals verknüpft werden muss. Das mit dem Lichtbogen verbundene Gewicht(U,V) oben ist die Länge |σ(αβ)|, wo die Zuordnung σwird als Identität auf Terminals erweitert und dann erneut als String-Homomorphismus erweitert. Dies ist die Länge (einer) der kürzesten Terminal-Strings, die aus dem String abgeleitet werden können αβ. Beachten Sie, dassVwird in dieser Berechnung entfernt. JEDOCH, wenn es mehrere Vorkommen von gibtVIn der RHS muss nur eine entfernt werden. Es können mehrere möglich sein(U,V) Bögen mit unterschiedlichen Gewichten, wenn es mehrere Regeln mit gibt U als LHS und Vin der RHS. In einem solchen Fall wird nur einer der leichteren Lichtbögen beibehalten.

In diesem Diagramm suchen wir nicht mehr nur nach der Erreichbarkeit von Knoten von S, aber für den kürzesten gewichteten Pfad, der jeden Knoten vom Anfangssymbol aus erreicht S. Dies kann mit dem Dijkstra-Algorithmus erfolgen .

Gegeben der kürzeste Weg für ein Nicht-Terminal UWir lesen es wie zuvor als eine Folge von Regeln, aus denen wir eine Dérivation erhalten SαUβ. Dann zu jeder Regel des FormularsUγ In der Grammatik erzeugen wir eine minimale Terminalzeichenfolge, die diese Regel als "aufruft" σ(αγβ)

Anmerkung : Dieselbe minimale Zeichenfolge kann wahrscheinlich für mehrere Regeln verwendet werden. Aber die Tatsache, dass eine der Zeichenfolgen eine Regel verwendetρ in seiner Ableitung bedeutet dies nicht unbedingt, dass es sich um eine minimale Zeichenfolge für diese Regel handelt ρ, wie es für eine andere Regel gefunden worden sein kann, während eine kürzere für gefunden werden kann ρ. Es kann möglich sein, die Wahrscheinlichkeit zu erhöhen, dass dieselbe minimale Zeichenfolge für mehrere Regeln gefunden wird, indem eine Prioritätsrichtlinie verwendet wird, wenn Flexibilität besteht. Aber ist es die Mühe wert?

Ein schnellerer Algorithmus für minimale Terminalzeichenfolgen, die von einem Nicht-Terminal abgeleitet werden

Aufbau der Funktion σ so dass σ(U) ist eine minimale Terminalzeichenfolge, die von abgeleitet ist Uwird oben mit einer eher naiven Technik durchgeführt, die eine iterative Überprüfung der bereits geleisteten Arbeit erfordert, wenn für einige Nicht-Terminals eine neue kleinere abgeleitete Zeichenfolge gefunden wird. Dies ist verschwenderisch, auch wenn der Prozess eindeutig beendet wird.

Wir schlagen hier einen effizienteren Algorithmus vor, dh im Wesentlichen eine Anpassung an den CF-Grammatikgraphen einer Erweiterung des Dijkstra-Algorithmus für kürzeste Pfade zu und / oder Graphen mit einer korrekten Definition des Pfadkonzepts für einen und / oder Graphen . Diese Variante des Algorithmus existiert wahrscheinlich in der Literatur (vorausgesetzt, er ist korrekt), aber ich konnte ihn in den Ressourcen, auf die ich zugreifen kann, nicht finden. Daher beschreibe ich es zusammen mit einem Beweis ausführlicher.

Wie zuvor entfernen wir zuerst alle rekursiven Regeln (dh Regeln mit einem LHS-Symbol in der RHS-Zeichenfolge) aus dem zu verarbeitenden Regelsatz. Es ist offensichtlich, dass keine dieser rekursiven Regeln zu einer kürzeren Terminalzeichenfolge führen kann als die nicht rekursiven Regeln mit derselben LHS. Und für eine LHSU Es muss mindestens eine nicht rekursive vorhanden sein U-Regel wenn das Symbol Uist kein nutzloses Nicht-Terminal (weil nicht produktiv). Dies ist nicht unbedingt erforderlich, verringert jedoch die Anzahl der Regeln, die später berücksichtigt werden müssen.

Dann gehen wir wie zuvor vor, um das Set zu erstellen Prod von produktiven Symbolen, die mit jedem Synbol assoziiert sind X eine Terminalzeichenfolge, die wir notieren σ(X)Dies ist eine Terminal-Zeichenfolge mit minimaler Größe, von der abgeleitet werden kann X (Im vorherigen Algorithmus war dies erst nach Beendigung der Fall). Die Menge Prod wird mit allen Terminalsymbolen und für jedes Terminalsymbol initialisiert a, wir definieren σ(a)=a.

Dann betrachten wir jede Regel Uγ so dass alle RHS-Symbole in sind Prodund wir wählen eine so, dass σ(γ)ist minimal minimal. Dann fügen wir hinzuU zu Prodmit σ(U)=σ(γ)und entfernen Sie alle U-Regeln. Wir iterieren, bis alle Produktterminals eingegeben wurdenProd. Jedes Nicht-TerminalU, einmal eingegeben Prodmuss nie wieder in Betracht gezogen werden, um sich zu ändern σ(U) für eine kleinere Saite.

Beweis :

Die vorherigen Algorithmen waren mehr oder weniger intuitiv offensichtlich. Dieser ist aufgrund des und / oder Charakters des Diagramms etwas kniffliger, und ein Beweis scheint notwendiger zu sein. Alles, was wir brauchen, ist das folgende Lemma, das die Richtigkeit des Algorithmus bei Anwendung auf die letzte Iteration feststellt.

Lemma : Nach jeder Iteration des Algorithmusσ(X) ist eine Terminal-Zeichenfolge mit minimaler Größe, von der abgeleitet werden kann X, für alle X im Prod.

Der Basisschritt ist offensichtlich, da dies per Definition für alle Terminals in gilt Prod wenn es initialisiert ist.

Angenommen, dies ist der Fall, nachdem einige Nicht-Terminals hinzugefügt wurden Prod, Lassen Uγ als Regel ausgewählt werden, um ein neues Nicht-Terminal hinzuzufügen Prod. Wir wissen, dass diese Regel gewählt wird, weil γProd und σ(γ) ist über alle RHS aller Regeln mit einem RHS in der Größe minimal Prod. DannU wird hinzugefügt Prodund das müssen wir nur beweisen σ(γ) ist eine Terminal-Zeichenfolge mit minimaler Größe, von der abgeleitet werden kann U.

Dies ist offensichtlich für alle Ableitungen der Fall, die mit der Regel beginnen Uγ, da durch Induktionshypothese, Anwendung der Kartierung σ ist so, dass alle Nicht-Terminals in σwerden durch von ihnen abgeleitete Terminal-Strings mit minimaler Größe ersetzt. Daher kann keine andere Ableitung eine kürzere Terminalzeichenfolge erzeugen.

Wir betrachten daher nur Ableitungen, die mit einer anderen beginnen U-Regel Uβ, so dass βwΣ, wo Σ ist der Satz von Terminalsymbolen.

Wenn βProd, dann ist eine minimale Zeichenfolge, aus der es abgeleitet werden kann σ(β). Aber da haben wir die Regel gewähltUγmuss es das sein |σ(β)||σ(γ)|. Also die Regel Uβ leitet sich nicht von einem kleineren terminalen Teilstring ab.

Der letzte zu berücksichtigende Fall ist, wann βProdund wir betrachten dann eine Ableitung βwΣ. Wenn diese Ableitung nur Nicht-Trminals in betrifftProd, dann βProdDies ist ein Fall, den wir bereits gesehen haben. Daher betrachten wir nur Ableitungen, die Schritte haben, die eine Regel verwenden, deren LHS nicht in istProd. LassenVα sei eine solche Regel, so dass αProdEs muss mindestens eine solche Regel geben, da sie teilweise nach Ableitungsreihenfolge geordnet sind, und wProd.

So haben wir UβμVν. Wir wissen dasμ und ν leiten Sie auf eine Zeichenfolge mit einer Größe von mindestens 0 ab, und da nein V-Regel mit einer RHS in Prod gewählt wurde, leiten sie sich an terminalen Strings ab, deren Länge mindestens gleich ist |σ(γ)|. Daher mit der RegelUβ, U leitet sich von einer Terminalzeichenfolge ab, deren Länge mindestens gleich ist |σ(γ)|.

babou
quelle
sieht plausibel aus, schlägt aber vor, dass es zumindest in Pseudocode konvertiert werden muss. es scheint auch plausibel, dass das Problem oder nahe Varianten irgendwo in der Literatur untersucht wurde ...
vzn
@vzn Ich bezweifle, dass dieses Problem veröffentlicht wurde, da ich keine Anwendung sehe, die diese Minimalität erfordert. Der Algorithmus zur Eliminierung nutzloser Symbole ist eine klassische Technik, die wahrscheinlich in allen Lehrbüchern zu CF-Sprachen zu finden ist. Und wie gesagt, es hat einige Anwendungen im allgemeinen CF-Parsing. Ich habe noch nie davon gehört, es weiter zu forcieren, um Testsätze zu produzieren. Ich habe versucht, die Ableitung zu erklären, anstatt Pseudocode anzugeben. Da ist die Antwort schon sehr lang. Vielleicht in einer zweiten Antwort, wenn das auf der Website akzeptabel ist ... da der Text zu lang wird, um verarbeitet zu werden.
Babou
@vzn Was in der Literatur stehen könnte, ist die Erweiterung des Dijkstra-Algorithmus für kürzeste Wege auf und / oder Graphen. Leider habe ich keinen Zugriff auf Ressourcen, um dies herauszufinden. Ich könnte es in vereinfachter Form schreiben, ohne die CF-Grammatik. Aber ich denke nicht, dass es eine große Sache ist. Es gibt jedoch die richtigen Hinweise für eine Implementierung mit geringer Komplexität.
Babou
Minimierung und "Bedeckungen" sind häufige Überlegungen in der theoretischen Literatur. weiter gedacht, vielleicht könnte eine normale Form, entweder Greibach oder Chomsky , der Schlüssel zum besseren Verständnis sein (es gibt dort auch einen Zusammenhang mit dem Entfernen unproduktiver Terminals / nicht erreichbarer Symbole). Ich denke, dies ist eine lohnende Frage für tcs.se, vielleicht stellen Sie sie an einer Stelle dort und zitieren Sie diese (afaik Migrationen neigen dazu, Kommentare zu verlieren.)
vzn
@vzn Ich bezweifle, dass Greibach oder Chomsky normale Form Licht bringen wird. Dies ist mehr grammatikalisch als sprachabhängig. Durch Ändern der Grammatik wird das Problem geändert. Eigentlich dachte ich zuerst, es sei eine Hausaufgaben-Müllkippe. Die Minimalitätsanforderung gibt ihm ein gewisses Interesse, da es weniger offensichtlich war, einen guten Algorithmus zu finden, insbesondere meine letzte Version. Ich habe den Beweis geschrieben, weil ich mich nicht anders davon überzeugen konnte, dass er tatsächlich funktioniert. Es ist irgendwie weniger intuitiv als der gerade Dijkstra-Algorithmus, von dem ich ihn abgeleitet habe.
Babou
2

Eine einfache Lösung

Wenn Sie sich nicht zu sehr für die Anzahl der Zeichenfolgen interessieren, gibt es eine einfache Lösung. Grundsätzlich generieren wir für jede Produktion einen String, der diese Produktion abdeckt.

Wie machen wir das? Es ist ein einfacher Arbeitslistenalgorithmus. Angenommen, wir möchten die Produktion abdeckenA::=BCx. Grundsätzlich führen wir die Breitensuche ab dem Start ohne Terminal durchS um eine Ableitung zu finden, die das Nicht-Terminal enthält A auf der rechten Seite: Anfangs sind alle Nicht-Terminals nicht markiert, und der Satz erreichbarer Nicht-Terminals ist {S};; In jeder Iteration wählen wir beispielsweise ein nicht markiertes erreichbares Nicht-Terminal ausXund für jede Produktion mit X Auf der linken Seite fügen wir alle Nicht-Terminals auf der rechten Seite zu den erreichbaren Terminals hinzu und markieren sie X. Wiederholen, bis dieser Vorgang dies findetAist erreichbar. Wenn dies der Fall ist, erhalten Sie durch Zurückverfolgen eine Ableitung des FormularsSA. Sie können dies auf eine Ableitung des Formulars erweiternSABCx.

Als nächstes müssen wir die Ableitung abschließen, indem wir einen Weg wählen, um sie so zu vervollständigen, dass sie alle Nicht-Terminals sind. Das ist auch einfach. Für jedes Nicht-Terminal finden wir die kürzeste Zeichenfolge in der Sprache, die von diesen Nicht-Terminals generiert wird. Dies kann durch eine Arbeitslisteniteration erreicht werden. Lassen(A) bezeichnen die Länge der kürzesten Zeichenfolge, die wir bisher in der von generierten Sprache gefunden haben A. Zunächst setzen wir(A)= für alle Nicht-Terminals A, es sei denn, es gibt eine Produktion A::=xyzwobei alle Symbole auf der rechten Seite Terminals sind; In diesem Fall setzen wir(A) entsprechend (z. (A)=3 wenn die Produktion uns das sagt xyz ist in L(A)). Jetzt suchen wir iterativ nach einer Möglichkeit, eine kürzere Zeichenfolge zu finden. Wenn Sie eine Produktion wie habenA::=xBCy, Du weißt, dass (A)2+(B)+(C);; So finden Sie jedes Mal eine neue kürzere Zeichenfolge inL(B) oder L(C) (dh jedes Mal, wenn Sie aktualisieren (B) oder (C) um kleiner zu sein) können Sie überprüfen, ob Sie dadurch eine neue kürzere Zeichenfolge erhalten L(A)und wenn ja, reduzieren Sie den Wert von (A)... was wiederum andere kaskadierende Änderungen verursachen kann. Wenden Sie alle kaskadierenden Änderungen so lange an, bis keine weiteren Änderungen mehr ausgelöst werden. Zu diesem Zeitpunkt haben Sie einen festen Punkt erreicht, und durch Zurückverfolgen kennen Sie für jedes Nicht-Terminal die kürzeste Zeichenfolge (von Terminals), die von diesem Nicht-Terminal abgeleitet werden kann.

Nun, angesichts Ihrer Ableitung des Formulars SBCx, finden Sie einen Weg, um die Ableitung abzuschließen: Ersetzen Sie jedes Nicht-Terminal in BCx mit der kürzesten Zeichenfolge (von Terminals), die daraus abgeleitet werden kann.

Dies gibt Ihnen eine Reihe (von Terminals), die die Produktion abdeckt A::=BCx. Tun Sie dies einmal für jede Produktion, bis alle abgedeckt sind.

Die Anzahl der Saiten entspricht der Anzahl der Produktionen. Die Laufzeit ist quadratisch: Der Prozess ist für jede Produktion linear. (Sie könnten es wahrscheinlich insgesamt auf einen linearen Zeitalgorithmus reduzieren, aber ich vermute, dass dies in der Praxis gut genug ist.)

Optimierungen

Wenn Sie die Anzahl der Zeichenfolgen reduzieren möchten, stellen Sie möglicherweise fest, dass jede Zeichenfolge normalerweise viele Produktionen abdeckt, sodass wahrscheinlich eine Teilmenge dieser Zeichenfolgen ausreicht.

Es gibt viele Möglichkeiten, die Anzahl der Zeichenfolgen zu verringern. Eine einfache Möglichkeit besteht darin, den Standardalgorithmus für die gierige Approximation für die Set-Abdeckung zu verwenden. Generieren Sie zunächst alle oben genannten Zeichenfolgen, eine für jede Produktion, und zählen Sie, wie viele Produktionen jede Zeichenfolge abdeckt. Behalten Sie die Saite, die die meisten Produktionen abdeckt. das, was Sie definitiv wollen, also fügen Sie es Ihren Bewahrern hinzu. Jetzt werden einige der Produktionen von diesem Keeper abgedeckt, sodass wir keine neuen Streicher benötigen, die sie erneut abdecken. Daher sollten Sie Ihre Anzahl für jede Zeichenfolge aktualisieren: für ZeichenfolgesZählen Sie die Anzahl der Produktionen, die von abgedeckt werden swerden aber von keinem Torhüter abgedeckt. Wählen Sie die Zeichenfolge mit der größten Anzahl aus, fügen Sie sie Ihrer Gruppe von Keepern hinzu und aktualisieren Sie die Anzahl erneut. Wiederholen, bis alle Produktionen abgedeckt sind. Dadurch erhalten Sie wahrscheinlich einen erheblich kleineren Satz von Zeichenfolgen, die alle Produktionen in Ihrer Grammatik abdecken.

DW
quelle