Vorrang der Funktion im Shunting-Yard-Algorithmus

9

Ich arbeite an dem von Wikipedia beschriebenen Shunting-Yard-Algorithmus .

Die Beschreibung des Algorithmus im Umgang mit Operatoren lautet wie folgt:

Wenn das Token ein Operator o1 ist, dann:

während sich oben auf dem Operatorstapel ein Operator-Token o2 befindet, und beides

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

Stellen Sie dann o2 vom Operator-Stack in die Ausgabewarteschlange.

Schieben Sie o1 auf den Bedienerstapel.

Sie geben jedoch das folgende Beispiel:

Eingang: sin max 2 3 / 3 * 3.1415

Wenn der Algorithmus das /Token trifft , lautet die Beschreibung dessen, was passieren soll, wie folgt:

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

Sie knallen den Funktionstoken maxvom stackund setzen ihn in den queue. Nach ihrem Algorithmus scheint dies zu bedeuten, dass das Funktionstoken sowohl ein Operator ist als auch eine geringere Priorität als der /Operator hat.

Es gibt keine Erklärung, ob dies der Fall ist oder nicht. Shunting-yardWelche Priorität hat eine Funktion für den Algorithmus? Sind Funktionen rechts oder links assoziativ? Oder ist Wikipedia nur unvollständig / ungenau?

MirroredFate
quelle

Antworten:

5

Ich glaube, die direkte Antwort ist einfach, dass Funktionen keine Operatoren sind. Von der Seite, die Sie verlinkt haben:

Wenn das Token ein Funktionstoken ist, schieben Sie es auf den Stapel.

Dies ist alles, was es zu sagen hat, da der Funktionsfall (Präfix zu Postfix) viel einfacher ist als der Operatorfall (Infix zu Postfix).

Für die Folgefragen: Die Begriffe Vorrang und Assoziativität werden nur benötigt, weil in einem Ausdruck mit mehreren Infix-Operatoren Mehrdeutigkeiten geerbt werden. Funktionstoken verwenden bereits die Präfixnotation, sodass sie dieses Problem einfach nicht haben. Sie müssen nicht wissen, ob sinoder maxmit "höherer Priorität", um herauszufinden, dass maxdies zuerst bewertet werden muss. es ist bereits aus der Reihenfolge der Token klar. Aus diesem Grund bevorzugen Computer zunächst die Prä- / Postfix-Notation und haben diesen Algorithmus zum Konvertieren von Infix in Pre / Postfix.

Sie benötigen eine Regel, nach der die Argumente einer Funktion beginnen und enden, wenn keine Klammern vorhanden sind. Sie können also sagen, dass Funktionen Vorrang vor Operatoren haben oder umgekehrt. Im Gegensatz zu Infix-Operatoren reicht jedoch eine einzige konsistente Regel für alle Funktionen aus, um ihre Kompositionen vollständig eindeutig zu machen.

Ixrec
quelle
Ihr Algorithmus ist also korrekt; es ist ihr Beispiel, das falsch ist. Die Infixnotation sollte Klammern enthalten, die die Funktionen umschließen:sin( max( 2 3) / 3 * 3.1415)
MirroredFate
Ich bin mir nicht sicher, ob ich es falsch nennen würde, aber dies ist ein starkes Argument für Sprachen, die Klammern und Kommas für alle Funktionsaufrufe erfordern.
Ixrec
Ich denke, es ist falsch, da es unmöglich ist, das Infix mit dem Algorithmus zu analysieren, wie sie es beschreiben.
MirroredFate
@Ixrec Ich sehe die Zeile "Wenn das Token ein Funktionstoken ist, schiebe es auf den Stapel." auf der Wikipedia-Seite. Kann jetzt bearbeitet werden. Aber meinst du, ich kann eine Funktion wie eine Zahl im Algorithmus behandeln?
Abhinav
3

Abhängig von Ihrer Sprachsyntax sind zwei verschiedene Fälle zu berücksichtigen. Wenn Ihre Sprache Klammern verwendet, um die Funktionsanwendung anzuzeigen (z. B. f(2+1)), ist der Vorrang irrelevant. Die Funktion sollte auf den Stapel geschoben und danach herausgesprungen werden (für das obige Beispiel ist das Ergebnis 2 1 + f). Alternativ können Sie die Funktion als Wert behandeln und sofort ausgeben und eine Funktionsaufrufoperation nach der geschlossenen Klammer ausgeben (die ansonsten wie jede andere Klammer behandelt werden sollte), z. B. f 2 1 + $wo $sich die Funktionsaufrufoperation befindet.

Wenn Ihre Sprache jedoch keine Klammern verwendet, um den Funktionsaufruf anzuzeigen, sondern das Argument direkt nach der Funktion ohne spezielle Interpunktion (z. B. f 2 + 1) platziert, wie dies anscheinend im Beispiel von Wikipedia der Fall ist, sind die Dinge etwas komplizierter. Beachten Sie, dass der Ausdruck, den ich gerade als Beispiel gegeben habe, nicht eindeutig ist: Wird f auf 2 und 1 angewendet, die zum Ergebnis hinzugefügt werden, oder addieren wir 2 und 1 zusammen und rufen dann f mit dem Ergebnis auf?

Auch hier gibt es zwei Ansätze. Sie können die Funktion einfach auf den Operator-Stack übertragen, wenn Sie darauf stoßen, und ihr die gewünschte Priorität zuweisen. Dies ist der einfachste Ansatz und anscheinend das, was das zitierte Beispiel getan hat. Es gibt jedoch praktische Probleme. Wie identifizieren Sie zunächst eine Funktion? Wenn Sie eine endliche Menge haben, ist es einfach, aber wenn Sie benutzerdefinierte Funktionen haben, bedeutet dies, dass Ihr Parser auch Rückmeldungen in Ihre Umgebung benötigt, was schnell chaotisch werden kann. Und wie gehen Sie mit Funktionen mit mehreren Argumenten um?

Meiner Meinung nach ist es für diesen Syntaxstil viel sinnvoller, Funktionen als Werte zu verwenden, die von einem Funktionsanwendungsoperator handlicher sind. Dann können Sie den Anwendungsoperator einfach einfügen, wenn Sie einen Wert lesen, und das letzte, was Sie gelesen haben, war auch ein Wert, sodass Sie keine spezielle Methode benötigen, um festzustellen, welche Bezeichner Funktionen sind. Sie können auch mit Ausdrücken arbeiten, die Funktionen zurückgeben (was mit dem Funktionsstil schwierig oder unmöglich ist). Dies bedeutet, dass Sie Currying verwenden können, um mehrere Argumentfunktionen zu verarbeiten. Dies ist eine massive Vereinfachung gegenüber dem Versuch, sie direkt zu verarbeiten.

Das einzige, was Sie dann entscheiden müssen, ist, welche Priorität die Funktionsanwendung hat. Die Wahl liegt bei Ihnen, aber in jeder Sprache, die ich verwendet habe und die so funktioniert, war es der am stärksten bindende Operator in der Sprache und war richtig assoziativ. (Die einzige interessante Variante ist Haskell, das neben der beschriebenen stark bindenden Version auch ein Synonym für das Symbol hat, $das der am schwächsten bindende Operator in der Sprache ist und Ausdrücke wie f 2 + 1f bis 2 f $ 2 + 1anwenden und anwenden lässt es auf den gesamten Rest des Ausdrucks)

Jules
quelle
3

Ich implementierte die nach "Funktionen im Rangierbahnhof" angeforderten Funktionen, nachdem ich Dijkstras ursprüngliche Überlegungen gelesen hatte (Seiten 7-11 im Algol 60-Compilerpapier, https://ir.cwi.nl/pub/9251 ) und eine robuste Lösung benötigte hat folgendes getan:

Parsing:

  • Drücken Sie den Funktionsdeskriptor
  • Drücken Sie eine linke Klammer "[" am Anfang der Argumente, genau wie bei Beginn der Klammer für den Ausdruck.
  • Lesen Sie eine Sequenz mit ausgeglichenen Argumenten "(" bis ")" aus der Eingabe
  • Schieben Sie dies in den Ausgabe-Token-Stream
  • Schieben Sie eine rechte Klammer am Ende des Arguments "]" genau wie seine "kompensierende schließende Klammer".

Infix-to-Postfix (Rangierbahnhof):

  • Fügen Sie einen weiteren Stapel hinzu, den Funktionsstapel, genau wie den Operatorstapel
  • Verschieben Sie beim Scannen eines Funktionsnamens die Funktionsinformationen in den Funktionsstapel
  • Wenn eine rechte Klammer am Ende des Arguments angezeigt wird, platzieren Sie den Funktionsstapel zur Ausgabe

Funktioniert perfekt in robusten Tests und komplizierten Szenarien. In meiner Anwendung (ein Expander für Befehlszeilenargumente, die Ausdrücke enthalten) unterstütze ich Funktionen mit mehreren Argumenten und ein Komma-Token, um sie zu trennen, und diese fließen durch den gesamten Prozess.

Beispiele sehen aus wie "sqrt (3 ^ 2 + 4 ^ 2)", was zu "3 2 ^ 4 2 ^ + sqrt" und letztendlich zu "5" wird, was das Programm für das Argument hält. Es ist bignum, also ist Binomial (64, 32) / gcd (Binomial (64, 32), Binomial (63, 31)) ==> große Dinge ==> "2" hilfreich. 123456 ^ 789 ist 40.173 Stellen und das Timing zeigt "Auswertung = 0,000390 Sek." auf meinem MacBookPro, so schnell.

Ich benutze dies auch, um Daten in Tabellen zu erweitern und finde das praktisch. Wie auch immer, dies ist mein Tipp auf dem Weg, Funktionsaufrufe, mehrere Argumente und tiefes Verschachteln in einem Dijkstra-Rangierbahn-Kontext sorgfältig zu behandeln. Habe es heute gerade aus unabhängigem Denken gemacht. Ich weiß nicht, ob es bessere Wege gibt.

Michael
quelle