390 Stimmen

Wie kann man einen mathematischen Ausdruck in Form einer Zeichenkette auswerten?

Ich versuche, eine Java-Routine zu schreiben, die mathematische Ausdrücke aus String Werte wie:

  1. "5+3"
  2. "10-40"
  3. "(1+10)*3"

Ich möchte eine Vielzahl von if-then-else-Anweisungen vermeiden. Wie kann ich das tun?

416voto

RealHowTo Punkte 33858

Mit JDK1.6 können Sie die eingebaute Javascript-Engine verwenden.

import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;

public class Test {
  public static void main(String[] args) throws ScriptException {
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    String foo = "40+2";
    System.out.println(engine.eval(foo));
    } 
}

61 Stimmen

Hier scheint es ein großes Problem zu geben: Es wird ein Skript ausgeführt, nicht ein Ausdruck ausgewertet. Um das klarzustellen, engine.eval("8;40+2"), gibt 42 ! Wenn Sie einen Ausdrucks-Parser wollen, der auch die Syntax überprüft, habe ich gerade einen fertiggestellt (weil ich nichts gefunden habe, was meinen Bedürfnissen entspricht): Javaluator .

4 Stimmen

Nebenbei bemerkt, wenn Sie das Ergebnis dieses Ausdrucks an anderer Stelle in Ihrem Code verwenden müssen, können Sie das Ergebnis wie folgt in einen Double-Wert umwandeln: return (Double) engine.eval(foo);

57 Stimmen

Sicherheitshinweis: Sie sollten dies niemals in einem Serverkontext mit Benutzereingaben verwenden. Das ausgeführte JavaScript kann auf alle Java-Klassen zugreifen und so Ihre Anwendung unbegrenzt kapern.

322voto

Boann Punkte 46908

Ich habe dies geschrieben eval Methode für arithmetische Ausdrücke, um diese Frage zu beantworten. Es kann Addition, Subtraktion, Multiplikation, Division, Potenzierung (unter Verwendung der ^ Symbol) und ein paar grundlegende Funktionen wie sqrt . Es unterstützt die Gruppierung mit ( ... ) und es erhält den Operator Vorrang y Assoziativität Regeln korrekt.

public static double eval(final String str) {
    return new Object() {
        int pos = -1, ch;

        void nextChar() {
            ch = (++pos < str.length()) ? str.charAt(pos) : -1;
        }

        boolean eat(int charToEat) {
            while (ch == ' ') nextChar();
            if (ch == charToEat) {
                nextChar();
                return true;
            }
            return false;
        }

        double parse() {
            nextChar();
            double x = parseExpression();
            if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char)ch);
            return x;
        }

        // Grammar:
        // expression = term | expression `+` term | expression `-` term
        // term = factor | term `*` factor | term `/` factor
        // factor = `+` factor | `-` factor | `(` expression `)` | number
        //        | functionName `(` expression `)` | functionName factor
        //        | factor `^` factor

        double parseExpression() {
            double x = parseTerm();
            for (;;) {
                if      (eat('+')) x += parseTerm(); // addition
                else if (eat('-')) x -= parseTerm(); // subtraction
                else return x;
            }
        }

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else return x;
            }
        }

        double parseFactor() {
            if (eat('+')) return +parseFactor(); // unary plus
            if (eat('-')) return -parseFactor(); // unary minus

            double x;
            int startPos = this.pos;
            if (eat('(')) { // parentheses
                x = parseExpression();
                if (!eat(')')) throw new RuntimeException("Missing ')'");
            } else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers
                while ((ch >= '0' && ch <= '9') || ch == '.') nextChar();
                x = Double.parseDouble(str.substring(startPos, this.pos));
            } else if (ch >= 'a' && ch <= 'z') { // functions
                while (ch >= 'a' && ch <= 'z') nextChar();
                String func = str.substring(startPos, this.pos);
                if (eat('(')) {
                    x = parseExpression();
                    if (!eat(')')) throw new RuntimeException("Missing ')' after argument to " + func);
                } else {
                    x = parseFactor();
                }
                if (func.equals("sqrt")) x = Math.sqrt(x);
                else if (func.equals("sin")) x = Math.sin(Math.toRadians(x));
                else if (func.equals("cos")) x = Math.cos(Math.toRadians(x));
                else if (func.equals("tan")) x = Math.tan(Math.toRadians(x));
                else throw new RuntimeException("Unknown function: " + func);
            } else {
                throw new RuntimeException("Unexpected: " + (char)ch);
            }

            if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation

            return x;
        }
    }.parse();
}

Beispiel:

System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));

Ausgabe: 7.5 (was richtig ist)


Der Parser ist ein Parser mit rekursivem Abstieg und verwendet daher intern separate Parse-Methoden für jede Stufe der Operatorpriorität in seiner Grammatik. Ich habe es absichtlich kurz aber hier sind einige Ideen, die Sie vielleicht erweitern möchten:

  • Variablen:

    Der Teil des Parsers, der die Namen für Funktionen liest, kann leicht geändert werden, um auch benutzerdefinierte Variablen zu behandeln, indem man die Namen in einer Variablentabelle nachschlägt, die an die eval Methode, wie zum Beispiel eine Map<String,Double> variables .

  • Getrennte Zusammenstellung und Auswertung:

    Was wäre, wenn Sie, nachdem Sie die Unterstützung für Variablen hinzugefügt haben, denselben Ausdruck Millionen von Malen mit geänderten Variablen auswerten wollten, ohne ihn jedes Mal zu parsen? Das ist möglich. Definieren Sie zunächst eine Schnittstelle für die Auswertung des vorkompilierten Ausdrucks:

      @FunctionalInterface
      interface Expression {
          double eval();
      }

    Um nun die ursprüngliche "eval"-Funktion in eine "parse"-Funktion umzuwandeln, ändern Sie alle Methoden, die Folgendes zurückgeben double s, so dass sie stattdessen eine Instanz dieser Schnittstelle zurückgeben. Die Lambda-Syntax von Java 8 eignet sich gut für diese Aufgabe. Beispiel für eine der geänderten Methoden:

      Expression parseExpression() {
          Expression x = parseTerm();
          for (;;) {
              if (eat('+')) { // addition
                  Expression a = x, b = parseTerm();
                  x = (() -> a.eval() + b.eval());
              } else if (eat('-')) { // subtraction
                  Expression a = x, b = parseTerm();
                  x = (() -> a.eval() - b.eval());
              } else {
                  return x;
              }
          }
      }

    Das erzeugt einen rekursiven Baum von Expression Objekte, die den kompilierten Ausdruck darstellen (ein abstrakter Syntaxbaum ). Dann können Sie es einmal kompilieren und wiederholt mit verschiedenen Werten auswerten:

      public static void main(String[] args) {
          Map<String,Double> variables = new HashMap<>();
          Expression exp = parse("x^2 - x + 2", variables);
          for (double x = -20; x <= +20; x++) {
              variables.put("x", x);
              System.out.println(x + " => " + exp.eval());
          }
      }
  • Verschiedene Datentypen:

    Anstelle von double könnten Sie den Evaluator ändern, um etwas Leistungsfähigeres zu verwenden wie BigDecimal oder eine Klasse, die komplexe Zahlen oder rationale Zahlen (Brüche) implementiert. Sie könnten sogar verwenden Object und erlaubt eine Mischung von Datentypen in Ausdrücken, genau wie eine echte Programmiersprache :)


Alle Codes in dieser Antwort freigegeben <a href="https://creativecommons.org/publicdomain/zero/1.0/" rel="noreferrer">zum öffentlichen Bereich </a>. Viel Spaß!

4 Stimmen

Netter Algorithmus, von dem aus ich es geschafft habe, logische Operatoren zu implementieren. Wir haben separate Klassen für Funktionen erstellt, um eine Funktion zu evaluieren, also wie deine Idee von Variablen, erstelle ich eine Karte mit Funktionen und suche nach dem Funktionsnamen. Jede Funktion implementiert eine Schnittstelle mit einer Methode eval (T rightOperator , T leftOperator), so dass wir jederzeit Funktionen hinzufügen können, ohne den Code des Algorithmus zu ändern. Und es ist eine gute Idee, dass es mit generischen Typen funktioniert. Vielen Dank an Sie!

1 Stimmen

Können Sie die Logik hinter diesem Algorithmus erklären?

1 Stimmen

Ich versuche, eine Beschreibung dessen zu geben, was ich aus dem von Boann geschriebenen Code und den im Wiki beschriebenen Beispielen verstehe.Die Logik dieses Algorithmus beginnt mit den Regeln der Operationsreihenfolge. 1. Operatorzeichen | Variablenbewertung | Funktionsaufruf | Klammern (Unterausdrücke); 2. Potenzierung; 3;

47voto

Leroy Kegan Punkte 1001

Für mein Universitätsprojekt war ich auf der Suche nach einem Parser / Evaluator, der sowohl einfache Formeln als auch kompliziertere Gleichungen (insbesondere iterierte Operatoren) unterstützt. Ich fand eine sehr schöne Open-Source-Bibliothek für JAVA und .NET namens mXparser. Ich werde ein paar Beispiele geben, um ein Gefühl für die Syntax zu bekommen. Für weitere Anweisungen besuchen Sie bitte die Projekt-Website (insbesondere den Tutorial-Bereich).

https://mathparser.org/

https://mathparser.org/mxparser-tutorial/

https://mathparser.org/api/

Und einige Beispiele

1 - Einfache Furmula

Expression e = new Expression("( 2 + 3/4 + sin(pi) )/2");
double v = e.calculate()

2 - Benutzerdefinierte Argumente und Konstanten

Argument x = new Argument("x = 10");
Constant a = new Constant("a = pi^2");
Expression e = new Expression("cos(a*x)", x, a);
double v = e.calculate()

3 - Benutzerdefinierte Funktionen

Function f = new Function("f(x, y, z) = sin(x) + cos(y*z)");
Expression e = new Expression("f(3,2,5)", f);
double v = e.calculate()

4 - Iteration

Expression e = new Expression("sum( i, 1, 100, sin(i) )");
double v = e.calculate()

Kürzlich gefunden - falls Sie die Syntax ausprobieren möchten (und den erweiterten Anwendungsfall sehen wollen), können Sie die Skalar Taschenrechner app die von mXparser unterstützt wird.

1 Stimmen

Bisher ist dies die beste mathematische Bibliothek, die es gibt; einfach zu starten, einfach zu benutzen und erweiterbar. Sollte auf jeden Fall die beste Antwort sein.

2 Stimmen

Maven-Version finden aquí .

1 Stimmen

Ich habe festgestellt, dass mXparser illegale Formeln nicht erkennen kann, z. B. wird '0/0' als '0' ausgegeben. Wie kann ich dieses Problem lösen?

36voto

Greg Hewgill Punkte 882617

Der richtige Weg, dies zu lösen, ist mit einer lexer und eine Parser . Sie können einfache Versionen davon selbst schreiben, oder diese Seiten haben auch Links zu Java-Lexern und -Parsern.

Die Erstellung eines rekursiven Descent-Parsers ist eine wirklich gute Lernübung.

22voto

Tanvir Punkte 522

HIER ist eine weitere Open-Source-Bibliothek auf GitHub namens EvalEx.

Im Gegensatz zur JavaScript-Engine ist diese Bibliothek ausschließlich auf die Auswertung mathematischer Ausdrücke ausgerichtet. Außerdem ist die Bibliothek erweiterbar und unterstützt die Verwendung von booleschen Operatoren und Klammern.

0 Stimmen

Dies ist in Ordnung, schlägt aber fehl, wenn wir versuchen, Werte von Vielfachen von 5 oder 10 zu multiplizieren, z.B. 65 * 6 ergibt 3.9E+2 ...

0 Stimmen

Aber es gibt eine Möglichkeit, dies zu beheben, indem man es in int umwandelt, d.h. int output = (int) 65*6 ergibt jetzt 390

1 Stimmen

Zur Klarstellung: Das ist kein Problem der Bibliothek, sondern ein Problem mit der Darstellung von Zahlen als Fließkommazahlen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X