it-swarm.com.de

Analysieren eines arithmetischen Ausdrucks und Erstellen eines Baums daraus in Java

Ich brauchte Hilfe beim Erstellen von benutzerdefinierten Bäumen mit einem arithmetischen Ausdruck. Nehmen wir zum Beispiel an, Sie geben diesen arithmetischen Ausdruck ein:

(5+2)*7

Der Ergebnisbaum sollte wie folgt aussehen:

    *
   / \
  +   7
 / \
5   2

Ich habe einige benutzerdefinierte Klassen, um die verschiedenen Knotentypen darzustellen, d. H. PlusOp, LeafInt usw. Ich muss den Ausdruck nicht auswerten, sondern erstelle nur den Baum, damit ich später andere Funktionen ausführen kann. Darüber hinaus kann der negative Operator "-" nur ein untergeordnetes Element haben. Um "5-2" darzustellen, müssen Sie es als 5 + (-2) eingeben.

Eine gewisse Validierung des Ausdrucks wäre erforderlich, um sicherzustellen, dass jeder Operatortyp die richtige Nr. Hat. von Argumenten/Kindern wird jede öffnende Klammer von einer schließenden Klammer begleitet.

Außerdem sollte ich wahrscheinlich erwähnen, dass mein Freund bereits Code geschrieben hat, der die Eingabezeichenfolge in einen Stapel von Tokens umwandelt, wenn dies hilfreich sein wird.

Ich würde mich über jede Hilfe freuen. Vielen Dank :)

(Ich habe gelesen, dass Sie eine Grammatik schreiben und antlr/JavaCC usw. verwenden können, um den Analysebaum zu erstellen, aber ich kenne diese Tools oder das Schreiben von Grammatiken nicht. Wenn dies also Ihre Lösung ist, wäre ich Ihnen dankbar könnte einige hilfreiche Tutorials/Links für sie bereitstellen.)

38
ChocolateBear

Das "Fünf Minuten Einführung in ANTLR" enthält ein arithmetisches Grammatikbeispiel. Es lohnt sich, sich das anzuschauen, zumal antlr Open Source (BSD-Lizenz) ist.

9
Cameron Skinner

Angenommen, dies ist eine Art Hausaufgabe und Sie möchten es selbst machen.

Ich habe das einmal gemacht, du brauchst einen Stapel

Also, was Sie für das Beispiel tun, ist:

 analysieren, was zu tun ist? Stapel sieht aus wie 
 (Schieben Sie ihn auf den Stapel (
 5 Drücken Sie 5 (, 5 
 + Drücken Sie + (, 5, + 
 2 Drücken Sie 2 (, 5 , +, 2 
) Auswerten bis (7 
 * Taste * 7, * 
 7 Taste 7 +7, *, 7 
 Eof auswerten bis Top 49 

Die Symbole wie "5" oder "+" können nur als Zeichenfolgen oder einfache Objekte gespeichert werden, oder Sie können das + als + () - Objekt speichern, ohne die Werte festzulegen, und Sie können sie festlegen, wenn Sie auswerten.

Ich gehe davon aus, dass dies auch eine Rangfolge erfordert, daher beschreibe ich, wie das funktioniert.

im Fall von: 5 + 2 * 7

sie müssen drücken 5 drücken + drücken 2 nächste Operation hat höhere Priorität, so dass Sie auch drücken, dann drücken Sie die drei. Wenn Sie entweder auf a) oder auf das Dateiende oder auf einen Operator mit niedrigerer oder gleicher Priorität stoßen, beginnen Sie, den Stapel auf den vorherigen (oder den Anfang der Datei) zu berechnen.

Da Ihr Stapel jetzt 5 + 2 * 7 enthält, platzieren Sie beim Auswerten zuerst den 2 * 7-Knoten und schieben den resultierenden * (2,7) -Knoten auf den Stapel. Anschließend bewerten Sie erneut die drei obersten Punkte auf dem Stapel ( 5 + * Knoten), damit der Baum richtig herauskommt.

Wenn es andersherum bestellt wurde: 5 * 2 + 7, würden Sie pushen, bis Sie zu einem Stapel mit "5 * 2" gelangen, dann würden Sie die niedrigere Priorität + treffen, was bedeutet, dass Sie auswerten, was Sie jetzt haben. Sie würden die 5 * 2 in einen * Knoten auswerten und ihn pushen, dann würden Sie fortfahren, indem Sie + und 3 drücken, sodass Sie * Knoten + 7 hatten, an welchem ​​Punkt Sie das auswerten würden.

Dies bedeutet, dass Sie eine Variable mit der höchsten aktuellen Priorität haben, die eine 1 speichert, wenn Sie +/- drücken, eine 2, wenn Sie * oder/und 3 für "^" drücken. Auf diese Weise können Sie die Variable einfach testen, um festzustellen, ob die Priorität Ihres nächsten Operators <= Ihre aktuelle Priorität ist.

wenn ")" als Priorität 4 betrachtet wird, können Sie es wie andere Operatoren behandeln, mit der Ausnahme, dass das übereinstimmende "(" entfernt wird, eine niedrigere Priorität würde dies nicht tun.

49
Bill K

Ich wollte auf die Antwort von Bill K. antworten, aber mir fehlt der Ruf, dort einen Kommentar hinzuzufügen (genau da gehört diese Antwort hin). Sie können sich dies als einen Nachtrag zu Bill Ks Antwort vorstellen, da seine etwas unvollständig war. Die fehlende Überlegung ist Operatorassoziativität ; nämlich, wie man Ausdrücke wie folgt parst:

49 / 7 / 7

Abhängig davon, ob die Unterteilung links oder rechts assoziativ ist, lautet die Antwort:

49 / (7 / 7) => 49 / 1 => 49

oder

(49 / 7) / 7 => 7 / 7 => 1

Typischerweise wird angenommen, dass Division und Subtraktion linksassoziativ sind (d. H. Obiger Fall zwei), während Exponentiation rechtsassoziativ ist. Wenn Sie also auf eine Reihe von Operatoren mit gleicher Priorität stoßen, möchten Sie diese in der Reihenfolge analysieren, in der sie links assoziativ sind, oder in der umgekehrten Reihenfolge, in der sie rechts assoziativ sind. Dies bestimmt nur, ob Sie auf den Stapel pushen oder poppen, damit der angegebene Algorithmus nicht überkompliziert wird. Es werden nur Fälle hinzugefügt, in denen aufeinanderfolgende Operatoren die gleiche Priorität haben (dh Stapel auswerten, wenn links assoziativ, auf Stapel pushen, wenn rechts assoziativ). .

13
Ray Weidner

Mehrere Optionen für Sie:

  1. Verwenden Sie einen vorhandenen Ausdrucksparser erneut. Das würde funktionieren, wenn Sie in Bezug auf Syntax und Semantik flexibel sind. Eine gute, die ich empfehle, ist die in Java (ursprünglich für die Verwendung in JSP- und JSF-Dateien) integrierte einheitliche Ausdruckssprache.

  2. Schreiben Sie Ihren eigenen Parser von Grund auf neu. Es gibt eine genau definierte Möglichkeit, einen Parser zu schreiben, der die Priorität von Operatoren usw. berücksichtigt. Eine genaue Beschreibung der Vorgehensweise ist nicht Gegenstand dieser Antwort. Wenn Sie diesen Weg gehen, finden Sie ein gutes Buch über Compiler-Design. Die Theorie der Sprachanalyse wird in den ersten Kapiteln behandelt. Typischerweise ist das Parsing von Ausdrücken eines der Beispiele.

  3. Verwenden Sie JavaCC oder ANTLR, um Lexer und Parser zu generieren. Ich bevorzuge JavaCC, aber jedem seinen. Google einfach "javacc samples" oder "antlr samples". Sie werden viel finden.

Zwischen 2 und 3 kann ich 3 nur empfehlen, selbst wenn Sie neue Technologien erlernen müssen. Es gibt einen Grund, warum Parser-Generatoren erstellt wurden.

Beachten Sie auch, dass das Erstellen eines Parsers, der fehlerhafte Eingaben verarbeiten kann (nicht nur mit Ausnahme der Syntaxanalyse), erheblich komplizierter ist als das Schreiben eines Parsers, der nur gültige Eingaben akzeptiert. Sie müssen im Grunde eine Grammatik schreiben, die die verschiedenen gängigen Syntaxfehler beschreibt.

Update: Hier ist ein Beispiel für einen Ausdruckssprachen-Parser, den ich mit JavaCC geschrieben habe. Die Syntax basiert lose auf der einheitlichen Ausdruckssprache. Es sollte Ihnen eine ziemlich gute Vorstellung davon geben, gegen was Sie antreten.

Inhalt von org.Eclipse.sapphire/plugins/org.Eclipse.sapphire.modeling/src/org/Eclipse/sapphire/modeling/el/parser/internal/ExpressionLanguageParser.jj

den angegebenen Ausdruck (5 + 2) * 7 können wir als Infix nehmen

Infix  :     (5+2)*7
Prefix :     *+527

aus dem obigen wissen wir die Vorbestellung und Inorder-Taversal des Baumes ... und wir können daraus leicht einen Baum konstruieren. Vielen Dank,

1
knils