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?
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 .
publicstaticdouble eval(finalString str){returnnewObject(){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();returntrue;}returnfalse;}double parse(){
nextChar();double x = parseExpression();if(pos < str.length())thrownewRuntimeException("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 `^` factordouble parseExpression(){double x = parseTerm();for(;;){if(eat('+')) x += parseTerm();// additionelseif(eat('-')) x -= parseTerm();// subtractionelsereturn x;}}double parseTerm(){double x = parseFactor();for(;;){if(eat('*')) x *= parseFactor();// multiplicationelseif(eat('/')) x /= parseFactor();// divisionelsereturn x;}}double parseFactor(){if(eat('+'))return parseFactor();// unary plusif(eat('-'))return-parseFactor();// unary minusdouble x;int startPos =this.pos;if(eat('(')){// parentheses
x = parseExpression();
eat(')');}elseif((ch >='0'&& ch <='9')|| ch =='.'){// numberswhile((ch >='0'&& ch <='9')|| ch =='.') nextChar();
x =Double.parseDouble(str.substring(startPos,this.pos));}elseif(ch >='a'&& ch <='z'){// functionswhile(ch >='a'&& ch <='z') nextChar();String func = str.substring(startPos,this.pos);
x = parseFactor();if(func.equals("sqrt")) x =Math.sqrt(x);elseif(func.equals("sin")) x =Math.sin(Math.toRadians(x));elseif(func.equals("cos")) x =Math.cos(Math.toRadians(x));elseif(func.equals("tan")) x =Math.tan(Math.toRadians(x));elsethrownewRuntimeException("Unknown function: "+ func);}else{thrownewRuntimeException("Unexpected: "+(char)ch);}if(eat('^')) x =Math.pow(x, parseFactor());// exponentiationreturn x;}}.parse();}
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:
Ä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('+')){// additionExpression a = x, b = parseTerm();
x =(()-> a.eval()+ b.eval());}elseif(eat('-')){// subtractionExpression 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:
publicstaticvoid main(String[] args){Map<String,Double> variables =newHashMap<>();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ß!
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.
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).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Benutzerdefinierte Argumente und Konstanten
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Benutzerdefinierte Funktionen
Function f =newFunction("f(x, y, z) = sin(x) + cos(y*z)");Expression e =newExpression("f(3,2,5)", f);double v = e.calculate()
4 - Iteration
Expression e =newExpression("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.
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.
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 :
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
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.
Ermöglicht Skripte, die Verweise auf Java-Objekte enthalten.
// Create or retrieve a JexlEngineJexlEngine jexl =newJexlEngine();// Create an expression objectString jexlExp ="foo.innerFoo.bar()";Expression e = jexl.createExpression( jexlExp );// Create a context and add dataJexlContext jctx =newMapContext();
jctx.set("foo",newFoo());// Now evaluate the expression, getting the resultObject o = e.evaluate(jctx);
Verwenden Sie die im JDK eingebettete Javascript-Engine:
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:
Wenn wir es implementieren wollen, können wir den folgenden Algorithmus verwenden: -
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:
1While the thing on top of the operator stack is not a
left parenthesis,1Pop the operator from the operator stack.2Pop the value stack twice, getting two operands.3Apply the operator to the operands, in the correct order.4Push the result onto the value stack.2Pop the left parenthesis from the operator stack, and discard it.
1.2.5 Ein Operator (nennen Sie es thisOp):
1While the operator stack is not empty, and the top thing on the
operator stack has the same or greater precedence as thisOp,1Pop the operator from the operator stack.2Pop the value stack twice, getting two operands.3Apply the operator to the operands, in the correct order.4Push the result onto the value stack.2Push thisOp onto the operator stack.
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.
Zu diesem Zeitpunkt sollte der Operatorstapel leer sein und der Wertestapel sollte nur einen Wert enthalten, was das Endergebnis ist.
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}elseif(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.
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 =newHashMap<String,Object>();
vars.put("x",10);
vars.put("y",20);ExecutableStatement statement =(ExecutableStatement) MVEL.compileExpression(expressionStr);Object result = MVEL.executeExpression(statement, vars);
ExprEvaluator util =newExprEvaluator();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
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.
publicstaticdouble eval(finalString str){returnnewObject(){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();returntrue;}returnfalse;}double parse(){
nextChar();double x = parseExpression();if(pos < str.length())thrownewRuntimeException("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 `^` factordouble parseExpression(){double x = parseTerm();for(;;){if(eat('+')) x += parseTerm();// additionelseif(eat('-')) x -= parseTerm();// subtractionelsereturn x;}}double parseTerm(){double x = parseFactor();for(;;){if(eat('*')) x *= parseFactor();// multiplicationelseif(eat('/')) x /= parseFactor();// divisionelseif(eat('^')) x =Math.pow(x, parseFactor());//exponentiation -> Moved in to here. So the problem is fixedelsereturn x;}}double parseFactor(){if(eat('+'))return parseFactor();// unary plusif(eat('-'))return-parseFactor();// unary minusdouble x;int startPos =this.pos;if(eat('(')){// parentheses
x = parseExpression();
eat(')');}elseif((ch >='0'&& ch <='9')|| ch =='.'){// numberswhile((ch >='0'&& ch <='9')|| ch =='.') nextChar();
x =Double.parseDouble(str.substring(startPos,this.pos));}elseif(ch >='a'&& ch <='z'){// functionswhile(ch >='a'&& ch <='z') nextChar();String func = str.substring(startPos,this.pos);
x = parseFactor();if(func.equals("sqrt")) x =Math.sqrt(x);elseif(func.equals("sin")) x =Math.sin(Math.toRadians(x));elseif(func.equals("cos")) x =Math.cos(Math.toRadians(x));elseif(func.equals("tan")) x =Math.tan(Math.toRadians(x));elsethrownewRuntimeException("Unknown function: "+ func);}else{thrownewRuntimeException("Unexpected: "+(char)ch);}//if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problemreturn 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
packageExpressionCalculator.expressioncalculator;import java.text.DecimalFormat;import java.util.Scanner;publicclassExpressionCalculator{privatestaticString 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;}publicstaticDouble evaluate(String expr){DecimalFormat df =newDecimalFormat("#.####");//Format the expression properly before performing operationsString expression = addSpaces(expr);try{//We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and//subtraction will be processed in following orderint 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 ="/";}elseif(expression.indexOf(" ^ ")!=-1){
operation ="^";}elseif(expression.indexOf(" * ")!=-1){
operation ="*";}elseif(expression.indexOf(" + ")!=-1){
operation ="+";}elseif(expression.indexOf(" - ")!=-1){//Avoid negative numbers
operation ="-";}else{returnDouble.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){returnnull;}
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);}returnDouble.valueOf(df.format(evaluate(expression.trim())));}}}catch(Exception exp){
exp.printStackTrace();}return0.0;}publicstaticvoid main(String args[]){Scanner scanner =newScanner(System.in);System.out.print("Enter an Mathematical Expression to Evaluate: ");String input = scanner.nextLine();System.out.println(evaluate(input));}
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
Mit dem Shunt-Yard-Algorithmus von Djikstra ist es möglich, eine beliebige Ausdruckszeichenfolge in Infix-Notation in eine Postfix-Notation umzuwandeln . Das Ergebnis des Algorithmus kann dann als Eingabe für den Postfix-Algorithmus dienen, wobei das Ergebnis des Ausdrucks zurückgegeben wird.
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 .
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 -
Antworten:
Mit JDK1.6 können Sie die integrierte Javascript-Engine verwenden.
quelle
return (Double) engine.eval(foo);
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 ProgrammsIch habe diese
eval
Methode 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 wiesqrt
. Es unterstützt die Gruppierung mit(
...)
und stellt die korrekten Vorrang- und Assoziativitätsregeln für Operatoren sicher .Beispiel:
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
eval
Methode übergebenen Variablentabelle nachgeschlagen werden, zMap<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:
Ändern Sie nun alle Methoden, die
double
s 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:Dadurch wird ein rekursiver Baum von
Expression
Objekten erstellt, die den kompilierten Ausdruck darstellen (ein abstrakter Syntaxbaum ). Dann können Sie es einmal kompilieren und wiederholt mit verschiedenen Werten auswerten:Verschiedene Datentypen:
Stattdessen
double
können Sie den Evaluator so ändern, dass er eine leistungsfähigereBigDecimal
Klasse oder eine Klasse verwendet, die komplexe Zahlen oder rationale Zahlen (Brüche) implementiert. Sie können sogarObject
eine Mischung von Datentypen in Ausdrücken verwenden, genau wie eine echte Programmiersprache. :) :)Der gesamte Code in dieser Antwort ist gemeinfrei . Habe Spaß!
quelle
double x = parseTerm();
den linken Operatorfor (;;) {...}
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. Dieboolean 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.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.
quelle
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
2 - Benutzerdefinierte Argumente und Konstanten
3 - Benutzerdefinierte Funktionen
4 - Iteration
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
quelle
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.
quelle
Sie können auch den BeanShell- Interpreter ausprobieren :
quelle
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
und in Oracle
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
Natürlich können Sie den obigen Code erweitern, um mehrere Berechnungen gleichzeitig durchzuführen.
quelle
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.
Verwenden Sie die im JDK eingebettete Javascript-Engine:
quelle
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:
Weitere prägnante SpEL-Beispiele finden Sie hier und die vollständigen Dokumente hier
quelle
Wenn wir es implementieren wollen, können wir den folgenden Algorithmus verwenden: -
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.2.5 Ein Operator (nennen Sie es thisOp):
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.
Zu diesem Zeitpunkt sollte der Operatorstapel leer sein und der Wertestapel sollte nur einen Wert enthalten, was das Endergebnis ist.
quelle
Dies ist eine weitere interessante Alternative https://github.com/Shy-Ta/expression-evaluator-demo
Die Verwendung ist sehr einfach und erledigt die Arbeit zum Beispiel:
quelle
Es scheint, als sollte JEP den Job machen
quelle
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
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.
quelle
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
MVEL
Wenn zur Laufzeit eine Auswertung von Ausdrücken durchgeführt wird, können wir einen Java-Code schreibenString
, um ihn darin auszuwerten.quelle
Vielleicht sehen Sie sich das Symja-Framework an :
Beachten Sie, dass definitiv komplexere Ausdrücke ausgewertet werden können:
quelle
Probieren Sie den folgenden Beispielcode mit der Javascript-Engine von JDK1.6 mit Code-Injection-Behandlung aus.
quelle
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.
quelle
-2^2 = -4
ist 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^2
soll 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 .}}
quelle
Wie wäre es mit so etwas:
und machen Sie das Gleiche für jeden anderen mathematischen Operator entsprechend.
quelle
Mit dem Shunt-Yard-Algorithmus von Djikstra ist es möglich, eine beliebige Ausdruckszeichenfolge in Infix-Notation in eine Postfix-Notation umzuwandeln . Das Ergebnis des Algorithmus kann dann als Eingabe für den Postfix-Algorithmus dienen, wobei das Ergebnis des Ausdrucks zurückgegeben wird.
Ich habe hier einen Artikel darüber geschrieben , mit einer Implementierung in Java
quelle
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 .
quelle
Eine Java-Klasse, die mathematische Ausdrücke auswerten kann:
quelle
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 -
quelle
quelle