Wie bewerte ich einen mathematischen Ausdruck in Zeichenfolgenform?


318

Ich versuche, eine Java-Routine zu schreiben, um einfache mathematische Ausdrücke anhand von StringWerten wie:

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

Ich möchte viele Wenn-Dann-Sonst-Aussagen vermeiden. Wie kann ich das machen?


7
Ich habe kürzlich einen Parser für mathematische Ausdrücke namens exp4j geschrieben, der unter der Apache-Lizenz veröffentlicht wurde. Sie können ihn hier nachlesen
fasseg

2
Welche Arten von Ausdrücken erlauben Sie? Nur einzelne Operatorausdrücke? Sind Klammern erlaubt?
Raedwald



3
Wie kann dies als zu weit gefasst angesehen werden? Dijkstras Bewertung ist die offensichtliche Lösung hier en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Antworten:


376

Mit JDK1.6 können Sie die integrierte 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));
    } 
}

52
Es scheint, dass es dort ein großes Problem gibt; Es führt ein Skript aus und wertet keinen Ausdruck aus. Um klar zu sein, gibt engine.eval ("8; 40 + 2") 42 aus! Wenn Sie einen Ausdrucksparser möchten, der auch die Syntax überprüft, habe ich gerade einen fertiggestellt (weil ich nichts gefunden habe, das meinen Anforderungen entspricht): Javaluator .
Jean-Marc Astesana

4
Als Randnotiz: Wenn Sie das Ergebnis dieses Ausdrucks an einer anderen Stelle in Ihrem Code verwenden müssen, können Sie das Ergebnis wie return (Double) engine.eval(foo);
folgt

38
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 entführen.
Boann

3
@ Boann, ich bitte Sie, mir eine Referenz über das zu geben, was Sie gesagt haben. (
Um

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- schreibt eine Datei über JavaScript (standardmäßig) in das aktuelle Verzeichnis des Programms
Boann

236

Ich habe diese evalMethode für arithmetische Ausdrücke geschrieben, um diese Frage zu beantworten. Es führt Addition, Subtraktion, Multiplikation, Division, Exponentiation (unter Verwendung des ^Symbols) und einige grundlegende Funktionen wie sqrt. Es unterstützt die Gruppierung mit (... )und stellt die korrekten Vorrang- und Assoziativitätsregeln für Operatoren sicher .

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 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();
                eat(')');
            } 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);
                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 rekursiver Abstiegsparser , verwendet also intern separate Analysemethoden für jede Ebene der Operatorrangfolge in seiner Grammatik. Ich habe es kurz gehalten, damit es leicht zu ändern ist, aber hier sind einige Ideen, mit denen Sie es möglicherweise erweitern möchten:

  • Variablen:

    Das Bit des Parsers, das die Namen für Funktionen liest, kann leicht geändert werden, um auch benutzerdefinierte Variablen zu verarbeiten, indem Namen in einer an die evalMethode übergebenen Variablentabelle nachgeschlagen werden, z Map<String,Double> variables.

  • Separate Zusammenstellung und Auswertung:

    Was wäre, wenn Sie, nachdem Sie die Unterstützung für Variablen hinzugefügt haben, denselben Ausdruck millionenfach mit geänderten Variablen auswerten möchten, ohne ihn jedes Mal zu analysieren? Es ist möglich. Definieren Sie zunächst eine Schnittstelle, die zum Auswerten des vorkompilierten Ausdrucks verwendet werden soll:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Ändern Sie nun alle Methoden, die doubles zurückgeben, und geben Sie stattdessen eine Instanz dieser Schnittstelle zurück. Die Lambda-Syntax von Java 8 eignet sich hervorragend dafür. Beispiel einer 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;
            }
        }
    }

    Dadurch wird ein rekursiver Baum von ExpressionObjekten erstellt, 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:

    Stattdessen doublekönnen Sie den Evaluator so ändern, dass er eine leistungsfähigere BigDecimalKlasse oder eine Klasse verwendet, die komplexe Zahlen oder rationale Zahlen (Brüche) implementiert. Sie können sogar Objecteine Mischung von Datentypen in Ausdrücken verwenden, genau wie eine echte Programmiersprache. :) :)


Der gesamte Code in dieser Antwort ist gemeinfrei . Habe Spaß!


1
Netter Algorithmus, ausgehend davon habe ich es geschafft, logische Operatoren zu implementieren. Wir haben separate Klassen für Funktionen erstellt, um eine Funktion auszuwerten. Wie bei Ihrer Vorstellung von Variablen erstelle ich eine Karte mit Funktionen und kümmere mich um den Funktionsnamen. Jede Funktion implementiert eine Schnittstelle mit einer Methodenbewertung (T rightOperator, T leftOperator), sodass wir jederzeit Features hinzufügen können, ohne den Algorithmuscode zu ändern. Und es ist eine gute Idee, es mit generischen Typen arbeiten zu lassen. Danke!
Vasile Bors

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

1
Ich versuche, eine Beschreibung dessen zu geben, was ich aus dem von Boann geschriebenen Code verstehe, und Beispiele, die im Wiki beschrieben werden. Die Logik dieses Algorithmus beginnt mit den Regeln der Operationsreihenfolge. 1. Bedienerzeichen | variable Auswertung | Funktionsaufruf | Klammern (Unterausdrücke); 2. Potenzierung; 3. Multiplikation, Division; 4. Addition, Subtraktion;
Vasile Bors

1
Die Algorithmusmethoden sind für jede Ebene der Operationsreihenfolge wie folgt unterteilt: parseFactor = 1. Operatorzeichen | variable Auswertung | Funktionsaufruf | Klammern (Unterausdrücke); 2. Potenzierung; parseTerms = 3. Multiplikation, Division; parseExpression = 4. Addition, Subtraktion. Der Algorithmus ruft Methoden in umgekehrter Reihenfolge auf (parseExpression -> parseTerms -> parseFactor -> parseExpression (für Unterausdrücke)), aber jede Methode in der ersten Zeile ruft die Methode auf die nächste Ebene auf, sodass die gesamte Ausführungsreihenfolge angewendet wird eigentlich normale Reihenfolge der Operationen.
Vasile Bors

1
Zum Beispiel die parseExpression-Methode, die double x = parseTerm(); den linken Operator for (;;) {...}auswertet, danach sukzessive Operationen der tatsächlichen Auftragsebene (Addition, Subtraktion). Die gleiche Logik sind und in parseTerm-Methode. Der parseFactor hat keine nächste Ebene, daher gibt es nur Auswertungen von Methoden / Variablen oder im Falle einer Paranthesis - Auswertung des Unterausdrucks. Die boolean eat(int charToEat)Methode prüft die Gleichheit des aktuellen Cursorzeichens mit dem Zeichen charToEat. Wenn gleich true zurückgibt und der Cursor zum nächsten Zeichen bewegt wird, verwende ich den Namen 'accept' dafür.
Vasile Bors

34

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

Das Erstellen eines rekursiven Abstiegsparsers ist eine wirklich gute Lernübung.


26

Für mein Universitätsprojekt suchte ich einen Parser / Evaluator, der sowohl Grundformeln als auch kompliziertere Gleichungen (insbesondere iterierte Operatoren) unterstützt. Ich habe eine sehr schöne Open Source Bibliothek für JAVA und .NET namens mXparser gefunden. Ich werde einige Beispiele geben, um ein Gefühl für die Syntax zu bekommen. Weitere Anweisungen finden Sie auf der Projektwebsite (insbesondere im 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 möchten), können Sie die Scalar Calculator- App herunterladen , die von mXparser unterstützt wird.

Freundliche Grüße


Bisher ist dies die beste Mathematikbibliothek da draußen; Einfach zu starten, einfach zu bedienen und erweiterbar. Sollte auf jeden Fall die beste Antwort sein.
Trynkiewicz Mariusz

Finden Maven - Version hier .
izogfif

Ich habe festgestellt, dass mXparser keine illegale Formel identifizieren kann. Beispiel: '0/0' wird als '0' angezeigt. Wie kann ich dieses Problem lösen?
lulijun

Habe

20

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

Im Gegensatz zur JavaScript-Engine konzentriert sich diese Bibliothek nur auf die Auswertung mathematischer Ausdrücke. Darüber hinaus ist die Bibliothek erweiterbar und unterstützt die Verwendung von Booleschen Operatoren sowie Klammern.


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

Aber es gibt eine Möglichkeit, dies zu beheben, indem Sie es auf int setzen, dh int output = (int) 65 * 6, es wird jetzt 390
paarth batra

1
Zur Verdeutlichung ist dies kein Problem der Bibliothek, sondern ein Problem bei der Darstellung von Zahlen als Gleitkommawerte.
DavidBittner

Diese Bibliothek ist wirklich gut. @paarth batra Wenn Sie auf int setzen, werden alle Dezimalstellen entfernt. Verwenden Sie stattdessen Folgendes: expression.eval (). ToPlainString ();
einUsername

15

Sie können auch den BeanShell- Interpreter ausprobieren :

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));

1
Können Sie mir bitte sagen, wie man BeanShell in adnroid Studio verwendet?
Hanni

1
Hanni - dieser Beitrag könnte Ihnen helfen, BeanShell zu Ihrem Androidstudio-Projekt hinzuzufügen
marciowerner

14

Sie können Ausdrücke einfach auswerten, wenn Ihre Java-Anwendung bereits auf eine Datenbank zugreift, ohne andere JARs zu verwenden.

Bei einigen Datenbanken müssen Sie eine Dummy-Tabelle verwenden (z. B. die "duale" Tabelle von Oracle), bei anderen können Sie Ausdrücke auswerten, ohne eine Tabelle "auswählen" zu müssen.

Zum Beispiel in SQL Server oder SQLite

select (((12.10 +12.0))/ 233.0) amount

und in Oracle

select (((12.10 +12.0))/ 233.0) amount from dual;

Der Vorteil der Verwendung einer Datenbank besteht darin, dass Sie viele Ausdrücke gleichzeitig auswerten können. Außerdem können Sie in den meisten DBs hochkomplexe Ausdrücke verwenden und verfügen über eine Reihe zusätzlicher Funktionen, die bei Bedarf aufgerufen werden können.

Die Leistung kann jedoch beeinträchtigt werden, wenn viele einzelne Ausdrücke einzeln ausgewertet werden müssen, insbesondere wenn sich die Datenbank auf einem Netzwerkserver befindet.

Im Folgenden wird das Leistungsproblem in gewissem Umfang mithilfe einer speicherinternen Sqlite-Datenbank behoben.

Hier ist ein voll funktionsfähiges Beispiel in Java

Class. forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite::memory:");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount");
rs.next();
System.out.println(rs.getBigDecimal(1));
stat.close();
conn.close();

Natürlich können Sie den obigen Code erweitern, um mehrere Berechnungen gleichzeitig durchzuführen.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");

5
Begrüßen Sie die SQL-Injection!
Cyberz

Dies hängt davon ab, wofür Sie die Datenbank verwenden. Wenn Sie sicher sein möchten, können Sie leicht eine leere SQLite-Datenbank erstellen, speziell für die mathematische Auswertung.
DAB

4
@cyberz Wenn Sie mein Beispiel oben verwenden, erstellt Sqlite eine temporäre Datenbank im Speicher. Siehe stackoverflow.com/questions/849679/…
DAB

11

Dieser Artikel beschreibt verschiedene Ansätze. Hier sind die 2 wichtigsten Ansätze, die im Artikel erwähnt werden:

JEXL von Apache

Ermöglicht Skripte, die Verweise auf Java-Objekte enthalten.

// Create or retrieve a JexlEngine
JexlEngine jexl = new JexlEngine();
// Create an expression object
String jexlExp = "foo.innerFoo.bar()";
Expression e = jexl.createExpression( jexlExp );

// Create a context and add data
JexlContext jctx = new MapContext();
jctx.set("foo", new Foo() );

// Now evaluate the expression, getting the result
Object o = e.evaluate(jctx);

Verwenden Sie die im JDK eingebettete Javascript-Engine:

private static void jsEvalWithVariable()
{
    List<String> namesList = new ArrayList<String>();
    namesList.add("Jill");
    namesList.add("Bob");
    namesList.add("Laureen");
    namesList.add("Ed");

    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

    jsEngine.put("namesListKey", namesList);
    System.out.println("Executing in script environment...");
    try
    {
      jsEngine.eval("var x;" +
                    "var names = namesListKey.toArray();" +
                    "for(x in names) {" +
                    "  println(names[x]);" +
                    "}" +
                    "namesListKey.add(\"Dana\");");
    }
    catch (ScriptException ex)
    {
        ex.printStackTrace();
    }
}

4
Bitte fassen Sie die Informationen aus dem Artikel zusammen, falls der Link dazu defekt ist.
DJClayworth

Ich habe die Antwort aktualisiert, um relevante Teile aus dem Artikel aufzunehmen
Brad Parks

1
In der Praxis ist JEXL langsam (verwendet die Introspektion von Beans) und hat Leistungsprobleme mit Multithreading (globaler Cache)
Nishi

Gut zu wissen, @Nishi! - Mein Anwendungsfall war das Debuggen von Dingen in Live-Umgebungen, aber nicht Teil der normal bereitgestellten App.
Brad Parks

10

Eine andere Möglichkeit ist die Verwendung von Spring Expression Language oder SpEL, die neben der Bewertung mathematischer Ausdrücke noch viel mehr bewirkt und daher möglicherweise etwas übertrieben ist. Sie müssen das Spring-Framework nicht verwenden, um diese Ausdrucksbibliothek zu verwenden, da sie eigenständig ist. Kopieren von Beispielen aus der SpEL-Dokumentation:

ExpressionParser parser = new SpelExpressionParser();
int two = parser.parseExpression("1 + 1").getValue(Integer.class); // 2 
double twentyFour = parser.parseExpression("2.0 * 3e0 * 4").getValue(Double.class); //24.0

Weitere prägnante SpEL-Beispiele finden Sie hier und die vollständigen Dokumente hier


8

Wenn wir es implementieren wollen, können wir den folgenden Algorithmus verwenden: -

  1. Während es noch Token zum Einlesen gibt,

    1.1 Holen Sie sich das nächste Token. 1.2 Wenn das Token lautet:

    1.2.1 Eine Zahl: Schieben Sie sie auf den Wertestapel.

    1.2.2 Eine Variable: Holen Sie sich ihren Wert und drücken Sie auf den Wertestapel.

    1.2.3 Eine linke Klammer: Schieben Sie sie auf den Bedienerstapel.

    1.2.4 Eine rechte Klammer:

     1 While the thing on top of the operator stack is not a 
       left parenthesis,
         1 Pop the operator from the operator stack.
         2 Pop the value stack twice, getting two operands.
         3 Apply the operator to the operands, in the correct order.
         4 Push the result onto the value stack.
     2 Pop the left parenthesis from the operator stack, and discard it.

    1.2.5 Ein Operator (nennen Sie es thisOp):

     1 While the operator stack is not empty, and the top thing on the
       operator stack has the same or greater precedence as thisOp,
       1 Pop the operator from the operator stack.
       2 Pop the value stack twice, getting two operands.
       3 Apply the operator to the operands, in the correct order.
       4 Push the result onto the value stack.
     2 Push thisOp onto the operator stack.
  2. Während der Operatorstapel nicht leer ist, 1 Pop den Operator aus dem Operatorstapel. 2 Pop den Wertestapel zweimal, um zwei Operanden zu erhalten. 3 Wenden Sie den Operator in der richtigen Reihenfolge auf die Operanden an. 4 Schieben Sie das Ergebnis auf den Wertestapel.

  3. Zu diesem Zeitpunkt sollte der Operatorstapel leer sein und der Wertestapel sollte nur einen Wert enthalten, was das Endergebnis ist.


3
Dies ist eine nicht im Abspann veröffentlichte Darstellung des Dijkstra Shunting-Yard-Algorithmus . Kredit, wo Kredit fällig ist.
Marquis von Lorne



4

Ich denke, wie auch immer Sie dies tun, es wird eine Menge bedingter Aussagen beinhalten. Für einzelne Operationen wie in Ihren Beispielen können Sie sie jedoch auf 4 if-Anweisungen mit so etwas wie beschränken

String math = "1+4";

if (math.split("+").length == 2) {
    //do calculation
} else if (math.split("-").length == 2) {
    //do calculation
} ...

Es wird viel komplizierter, wenn Sie mehrere Operationen wie "4 + 5 * 6" ausführen möchten.

Wenn Sie versuchen, einen Taschenrechner zu erstellen, würde ich jeden Abschnitt der Berechnung separat (jede Zahl oder jeden Operator) und nicht als einzelne Zeichenfolge übergeben.


2
Es wird viel komplizierter, sobald Sie sich mit mehreren Operationen, Operatorrang, Klammern usw. befassen müssen. Tatsächlich alles, was einen echten arithmetischen Ausdruck kennzeichnet. Mit dieser Technik können Sie nicht dorthin gelangen.
Marquis von Lorne

4

Es ist zu spät, um zu antworten, aber ich bin auf die gleiche Situation gestoßen, um den Ausdruck in Java zu bewerten. Es könnte jemandem helfen

MVELWenn zur Laufzeit eine Auswertung von Ausdrücken durchgeführt wird, können wir einen Java-Code schreiben String, um ihn darin auszuwerten.

    String expressionStr = "x+y";
    Map<String, Object> vars = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);

Ich habe einige zusätzliche arithmetische Funktionen
durchsucht

Ehrfürchtig! Es hat meinen Tag gerettet. Danke
Sarika.S

4

Vielleicht sehen Sie sich das Symja-Framework an :

ExprEvaluator util = new ExprEvaluator(); 
IExpr result = util.evaluate("10-40");
System.out.println(result.toString()); // -> "-30" 

Beachten Sie, dass definitiv komplexere Ausdrücke ausgewertet werden können:

// D(...) gives the derivative of the function Sin(x)*Cos(x)
IAST function = D(Times(Sin(x), Cos(x)), x);
IExpr result = util.evaluate(function);
// print: Cos(x)^2-Sin(x)^2

4

Probieren Sie den folgenden Beispielcode mit der Javascript-Engine von JDK1.6 mit Code-Injection-Behandlung aus.

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

public class EvalUtil {
private static ScriptEngine engine = new ScriptEngineManager().getEngineByName("JavaScript");
public static void main(String[] args) {
    try {
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || 5 >3 "));
        System.out.println((new EvalUtil()).eval("(((5+5)/2) > 5) || true"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public Object eval(String input) throws Exception{
    try {
        if(input.matches(".*[a-zA-Z;~`#$_{}\\[\\]:\\\\;\"',\\.\\?]+.*")) {
            throw new Exception("Invalid expression : " + input );
        }
        return engine.eval(input);
    } catch (Exception e) {
        e.printStackTrace();
        throw e;
    }
 }
}

4

Dies ergänzt tatsächlich die Antwort von @Boann. Es hat einen kleinen Fehler, der dazu führt, dass "-2 ^ 2" ein fehlerhaftes Ergebnis von -4,0 ergibt. Das Problem dafür ist der Punkt, an dem die Potenzierung in seinem bewertet wird. Verschieben Sie einfach die Potenzierung in den Block von parseTerm (), und alles wird gut. Schauen Sie sich das Folgende an, die Antwort von @ Boann ist leicht modifiziert. Änderung ist in den Kommentaren.

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 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 if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                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();
                eat(')');
            } 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);
                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 -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4ist eigentlich normal und kein Bug. Es wird gruppiert wie -(2^2). Probieren Sie es zum Beispiel auf Desmos aus. Ihr Code führt tatsächlich mehrere Fehler ein. Das erste ist, dass ^nicht mehr von rechts nach links gruppiert wird. Mit anderen Worten, 2^3^2soll gruppieren wie, 2^(3^2)weil ^es rechtsassoziativ ist, aber Ihre Modifikationen machen es gruppieren wie (2^3)^2. Das zweite ist, dass ^es eine höhere Priorität als *und haben soll /, aber Ihre Modifikationen behandeln es gleich. Siehe ideone.com/iN2mMa .
Radiodef

Sie schlagen also vor, dass die Potenzierung besser dort bleibt, wo sie war, nicht wahr?
Romeo Sierra

Ja, das schlage ich vor.
Radiodef

4
package ExpressionCalculator.expressioncalculator;

import java.text.DecimalFormat;
import java.util.Scanner;

public class ExpressionCalculator {

private static String addSpaces(String exp){

    //Add space padding to operands.
    //https://regex101.com/r/sJ9gM7/73
    exp = exp.replaceAll("(?<=[0-9()])[\\/]", " / ");
    exp = exp.replaceAll("(?<=[0-9()])[\\^]", " ^ ");
    exp = exp.replaceAll("(?<=[0-9()])[\\*]", " * ");
    exp = exp.replaceAll("(?<=[0-9()])[+]", " + "); 
    exp = exp.replaceAll("(?<=[0-9()])[-]", " - ");

    //Keep replacing double spaces with single spaces until your string is properly formatted
    /*while(exp.indexOf("  ") != -1){
        exp = exp.replace("  ", " ");
     }*/
    exp = exp.replaceAll(" {2,}", " ");

       return exp;
}

public static Double evaluate(String expr){

    DecimalFormat df = new DecimalFormat("#.####");

    //Format the expression properly before performing operations
    String expression = addSpaces(expr);

    try {
        //We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and
        //subtraction will be processed in following order
        int indexClose = expression.indexOf(")");
        int indexOpen = -1;
        if (indexClose != -1) {
            String substring = expression.substring(0, indexClose);
            indexOpen = substring.lastIndexOf("(");
            substring = substring.substring(indexOpen + 1).trim();
            if(indexOpen != -1 && indexClose != -1) {
                Double result = evaluate(substring);
                expression = expression.substring(0, indexOpen).trim() + " " + result + " " + expression.substring(indexClose + 1).trim();
                return evaluate(expression.trim());
            }
        }

        String operation = "";
        if(expression.indexOf(" / ") != -1){
            operation = "/";
        }else if(expression.indexOf(" ^ ") != -1){
            operation = "^";
        } else if(expression.indexOf(" * ") != -1){
            operation = "*";
        } else if(expression.indexOf(" + ") != -1){
            operation = "+";
        } else if(expression.indexOf(" - ") != -1){ //Avoid negative numbers
            operation = "-";
        } else{
            return Double.parseDouble(expression);
        }

        int index = expression.indexOf(operation);
        if(index != -1){
            indexOpen = expression.lastIndexOf(" ", index - 2);
            indexOpen = (indexOpen == -1)?0:indexOpen;
            indexClose = expression.indexOf(" ", index + 2);
            indexClose = (indexClose == -1)?expression.length():indexClose;
            if(indexOpen != -1 && indexClose != -1) {
                Double lhs = Double.parseDouble(expression.substring(indexOpen, index));
                Double rhs = Double.parseDouble(expression.substring(index + 2, indexClose));
                Double result = null;
                switch (operation){
                    case "/":
                        //Prevent divide by 0 exception.
                        if(rhs == 0){
                            return null;
                        }
                        result = lhs / rhs;
                        break;
                    case "^":
                        result = Math.pow(lhs, rhs);
                        break;
                    case "*":
                        result = lhs * rhs;
                        break;
                    case "-":
                        result = lhs - rhs;
                        break;
                    case "+":
                        result = lhs + rhs;
                        break;
                    default:
                        break;
                }
                if(indexClose == expression.length()){
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose);
                }else{
                    expression = expression.substring(0, indexOpen) + " " + result + " " + expression.substring(indexClose + 1);
                }
                return Double.valueOf(df.format(evaluate(expression.trim())));
            }
        }
    }catch(Exception exp){
        exp.printStackTrace();
    }
    return 0.0;
}

public static void main(String args[]){

    Scanner scanner = new Scanner(System.in);
    System.out.print("Enter an Mathematical Expression to Evaluate: ");
    String input = scanner.nextLine();
    System.out.println(evaluate(input));
}

}}


1
Behandelt nicht die Priorität von Operatoren oder mehrere Operatoren oder Klammern. Verwende nicht.
Marquis von Lorne

2

Wie wäre es mit so etwas:

String st = "10+3";
int result;
for(int i=0;i<st.length();i++)
{
  if(st.charAt(i)=='+')
  {
    result=Integer.parseInt(st.substring(0, i))+Integer.parseInt(st.substring(i+1, st.length()));
    System.out.print(result);
  }         
}

und machen Sie das Gleiche für jeden anderen mathematischen Operator entsprechend.


9
Sie sollten lesen, wie Sie effiziente Parser für mathematische Ausdrücke schreiben. Es gibt eine Informatik-Methodik. Schauen Sie sich zum Beispiel ANTLR an. Wenn Sie gut über das nachdenken, was Sie geschrieben haben, werden Sie feststellen, dass Dinge wie (a + b / -c) * (e / f) nicht mit Ihrer Idee funktionieren oder der Code super schmutzig und ineffizient ist.
Daniel Nuriyev

2
Dies ist schlimmer als eine Switch-Case-Anweisung ...
Programmierer5


2

Noch eine Option: https://github.com/stefanhaustein/expressionparser

Ich habe dies implementiert, um eine einfache, aber flexible Option zu haben, die beides ermöglicht:

Der oben verlinkte TreeBuilder ist Teil eines CAS- Demopakets , das eine symbolische Ableitung durchführt. Es gibt auch ein BASIC-Interpreter- Beispiel, und ich habe damit begonnen, einen TypeScript-Interpreter damit zu erstellen .


2

Eine Java-Klasse, die mathematische Ausdrücke auswerten kann:

package test;

public class Calculator {

    public static Double calculate(String expression){
        if (expression == null || expression.length() == 0) {
            return null;
        }
        return calc(expression.replace(" ", ""));
    }
    public static Double calc(String expression) {

        if (expression.startsWith("(") && expression.endsWith(")")) {
            return calc(expression.substring(1, expression.length() - 1));
        }
        String[] containerArr = new String[]{expression};
        double leftVal = getNextOperand(containerArr);
        expression = containerArr[0];
        if (expression.length() == 0) {
            return leftVal;
        }
        char operator = expression.charAt(0);
        expression = expression.substring(1);

        while (operator == '*' || operator == '/') {
            containerArr[0] = expression;
            double rightVal = getNextOperand(containerArr);
            expression = containerArr[0];
            if (operator == '*') {
                leftVal = leftVal * rightVal;
            } else {
                leftVal = leftVal / rightVal;
            }
            if (expression.length() > 0) {
                operator = expression.charAt(0);
                expression = expression.substring(1);
            } else {
                return leftVal;
            }
        }
        if (operator == '+') {
            return leftVal + calc(expression);
        } else {
            return leftVal - calc(expression);
        }

    }

    private static double getNextOperand(String[] exp){
        double res;
        if (exp[0].startsWith("(")) {
            int open = 1;
            int i = 1;
            while (open != 0) {
                if (exp[0].charAt(i) == '(') {
                    open++;
                } else if (exp[0].charAt(i) == ')') {
                    open--;
                }
                i++;
            }
            res = calc(exp[0].substring(1, i - 1));
            exp[0] = exp[0].substring(i);
        } else {
            int i = 1;
            if (exp[0].charAt(0) == '-') {
                i++;
            }
            while (exp[0].length() > i && isNumber((int) exp[0].charAt(i))) {
                i++;
            }
            res = Double.parseDouble(exp[0].substring(0, i));
            exp[0] = exp[0].substring(i);
        }
        return res;
    }


    private static boolean isNumber(int c) {
        int zero = (int) '0';
        int nine = (int) '9';
        return (c >= zero && c <= nine) || c =='.';
    }

    public static void main(String[] args) {
        System.out.println(calculate("(((( -6 )))) * 9 * -1"));
        System.out.println(calc("(-5.2+-5*-5*((5/4+2)))"));

    }

}

2
Behandelt die Priorität des Operators nicht richtig. Es gibt Standardmethoden, und dies ist keine davon.
Marquis von Lorne

EJP, können Sie bitte darauf hinweisen, wo es ein Problem mit der Priorität des Operators gibt? Ich stimme voll und ganz der Tatsache zu, dass dies nicht die Standardmethode ist. Die Standardmethoden wurden bereits in früheren Beiträgen erwähnt. Die Idee war, einen anderen Weg zu zeigen, dies zu tun.
Efi G

2

Externe Bibliotheken wie RHINO oder NASHORN können zum Ausführen von Javascript verwendet werden. Und Javascript kann einfache Formeln auswerten, ohne die Zeichenfolge zu analysieren. Keine Auswirkungen auf die Leistung, wenn der Code gut geschrieben ist. Unten sehen Sie ein Beispiel mit RHINO -

public class RhinoApp {
    private String simpleAdd = "(12+13+2-2)*2+(12+13+2-2)*2";

public void runJavaScript() {
    Context jsCx = Context.enter();
    Context.getCurrentContext().setOptimizationLevel(-1);
    ScriptableObject scope = jsCx.initStandardObjects();
    Object result = jsCx.evaluateString(scope, simpleAdd , "formula", 0, null);
    Context.exit();
    System.out.println(result);
}

2
import java.util.*;

public class check { 
   int ans;
   String str="7 + 5";
   StringTokenizer st=new StringTokenizer(str);

   int v1=Integer.parseInt(st.nextToken());
   String op=st.nextToken();
   int v2=Integer.parseInt(st.nextToken());

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.