J'ai récemment écrit un analyseur d'expression mathématique appelé exp4j qui a été publié sous la licence apache, vous pouvez le vérifier ici: objecthunter.net/exp4j
effectueg
2
Quels types d'expressions autorisez-vous? Uniquement des expressions d'opérateur unique? Les parenthèses sont-elles autorisées?
Il semble qu'il y ait là un problème majeur; Il exécute un script, n'évalue pas une expression. Pour être clair, engine.eval ("8; 40 + 2"), 42 sorties! Si vous voulez un analyseur d'expression qui vérifie également la syntaxe, je viens d'en terminer un (car je n'ai rien trouvé qui réponde à mes besoins): Javaluator .
Jean-Marc Astesana
4
En guise de remarque, si vous devez utiliser le résultat de cette expression ailleurs dans votre code, vous pouvez transtyper le résultat en double comme ceci: return (Double) engine.eval(foo);
Ben Visness
38
Note de sécurité: vous ne devez jamais l'utiliser dans un contexte de serveur avec une entrée utilisateur. Le JavaScript exécuté peut accéder à toutes les classes Java et ainsi pirater votre application sans limite.
Boann
3
@Boann, je vous demande de me donner une référence sur ce que vous avez dit. (Pour être sûr à 100%)
partho
17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- va écrire un fichier via JavaScript dans (par défaut) le répertoire courant du programme
Boann
236
J'ai écrit cette evalméthode pour les expressions arithmétiques pour répondre à cette question. Il fait l'addition, la soustraction, la multiplication, la division, l'exponentiation (en utilisant le ^symbole), et quelques fonctions de base comme sqrt. Il prend en charge le regroupement à l'aide de (... )et obtient la priorité de l'opérateur et les règles d' associativité correctes.
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();}
L'analyseur est un analyseur de descente récursif , donc utilise en interne des méthodes d'analyse distinctes pour chaque niveau de priorité de l'opérateur dans sa grammaire. Je l'ai gardé court donc c'est facile à modifier, mais voici quelques idées que vous voudrez peut-être développer avec:
Variables:
Le bit de l'analyseur qui lit les noms des fonctions peut facilement être modifié pour gérer également les variables personnalisées, en recherchant les noms dans une table de variables passée à la evalméthode, comme a Map<String,Double> variables.
Compilation et évaluation séparées:
Et si, après avoir ajouté la prise en charge des variables, vous vouliez évaluer la même expression des millions de fois avec des variables modifiées, sans l'analyser à chaque fois? C'est possible. Définissez d'abord une interface à utiliser pour évaluer l'expression précompilée:
Maintenant, changez toutes les méthodes qui retournent doubles, donc à la place, elles retournent une instance de cette interface. La syntaxe lambda de Java 8 fonctionne très bien pour cela. Exemple d'une des méthodes modifiées:
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;}}}
Cela construit un arbre récursif d' Expressionobjets représentant l'expression compilée (un arbre de syntaxe abstraite ). Ensuite, vous pouvez le compiler une fois et l'évaluer à plusieurs reprises avec différentes valeurs:
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());}}
Différents types de données:
Au lieu de double, vous pouvez changer l'évaluateur pour utiliser quelque chose de plus puissant BigDecimal, ou une classe qui implémente des nombres complexes, ou des nombres rationnels (fractions). Vous pouvez même utiliser Object, permettant un mélange de types de données dans les expressions, tout comme un véritable langage de programmation. :)
Bel algorithme, à partir de celui-ci j'ai réussi à implémenter des opérateurs logiques. Nous avons créé des classes distinctes pour les fonctions afin d'évaluer une fonction, donc comme votre idée de variables, je crée une carte avec des fonctions et je m'occupe du nom de la fonction. Chaque fonction implémente une interface avec une méthode eval (T rightOperator, T leftOperator), donc à tout moment nous pouvons ajouter des fonctionnalités sans changer le code de l'algorithme. Et c'est une bonne idée de le faire fonctionner avec des types génériques. Merci!
Vasile Bors
1
Pouvez-vous expliquer la logique derrière cet algorithme?
iYonatan
1
J'essaie de donner une description de ce que je comprends à partir du code écrit par Boann, et des exemples décrits wiki.La logique de cet algorithme à partir des règles de fonctionnement des ordres. 1. panneau opérateur | évaluation des variables | appel de fonction | parenthèses (sous-expressions); 2. l'exponentiation; 3. multiplication, division; 4. addition, soustraction;
Vasile Bors
1
Les méthodes d'algorithme sont divisées pour chaque niveau de l'ordre des opérations comme suit: parseFactor = 1. signe opérateur | évaluation des variables | appel de fonction | parenthèses (sous-expressions); 2. l'exponentiation; parseTerms = 3. multiplication, division; parseExpression = 4. addition, soustraction. L'algorithme appelle les méthodes dans l'ordre inverse (parseExpression -> parseTerms -> parseFactor -> parseExpression (pour les sous-expressions)), mais chaque méthode de la première ligne appelle la méthode au niveau suivant, donc toutes les méthodes de l'ordre d'exécution seront ordre de fonctionnement normal.
Vasile Bors
1
Par exemple, la méthode parseExpression double x = parseTerm(); évalue l'opérateur de gauche, puis for (;;) {...}évalue les opérations successives du niveau d'ordre réel (addition, soustraction). La même logique est et dans la méthode parseTerm. Le parseFactor n'a pas de niveau suivant, il n'y a donc que des évaluations de méthodes / variables ou en cas de paranthèse - évaluer la sous-expression. La boolean eat(int charToEat)méthode vérifie l'égalité du caractère du curseur actuel avec le caractère charToEat, si égal retourne vrai et déplace le curseur au caractère suivant, j'utilise le nom 'accept' pour cela.
Vasile Bors
34
La bonne façon de résoudre ce problème est d' utiliser un lexer et un analyseur . Vous pouvez en écrire vous-même des versions simples, ou ces pages contiennent également des liens vers des lexers et des analyseurs Java.
La création d'un analyseur de descente récursive est un très bon exercice d'apprentissage.
Pour mon projet universitaire, je cherchais un analyseur / évaluateur supportant à la fois les formules de base et les équations plus compliquées (notamment les opérateurs itérés). J'ai trouvé une très belle bibliothèque open source pour JAVA et .NET appelée mXparser. Je vais donner quelques exemples pour vous faire une idée de la syntaxe, pour plus d'instructions, veuillez visiter le site Web du projet (en particulier la section tutoriel).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Arguments et constantes définis par l'utilisateur
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Fonctions définies par l'utilisateur
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 - Itération
Expression e =newExpression("sum( i, 1, 100, sin(i) )");double v = e.calculate()
Trouvé récemment - au cas où vous voudriez essayer la syntaxe (et voir le cas d'utilisation avancée), vous pouvez télécharger l' application Scalar Calculator qui est propulsée par mXparser.
Jusqu'à présent, c'est la meilleure bibliothèque de mathématiques là-bas; simple à démarrer, facile à utiliser et extensible. Certainement devrait être la meilleure réponse.
J'ai trouvé que mXparser ne pouvait pas identifier la formule illégale, par exemple, '0/0' obtiendrait un résultat comme '0'. Comment puis-je résoudre ce problème?
lulijun
Je viens de trouver la solution, expression.setSlientMode ()
lulijun
20
ICI est une autre bibliothèque open source sur GitHub nommée EvalEx.
Contrairement au moteur JavaScript, cette bibliothèque se concentre uniquement sur l'évaluation des expressions mathématiques. De plus, la bibliothèque est extensible et prend en charge l'utilisation d'opérateurs booléens ainsi que les parenthèses.
C'est correct, mais échoue lorsque nous essayons de multiplier les valeurs de multiples de 5 ou 10, par exemple 65 * 6 donne 3,9E + 2 ...
paarth batra
.Mais il existe un moyen de résoudre ce problème en le convertissant en int ie int output = (int) 65 * 6, il en résultera maintenant 390
paarth batra
1
Pour clarifier, ce n'est pas un problème de bibliothèque mais plutôt un problème de représentation des nombres sous forme de valeurs à virgule flottante.
DavidBittner
Cette bibliothèque est vraiment bonne. @paarth batra Casting to int supprimera toutes les décimales. Utilisez plutôt ceci: expression.eval (). ToPlainString ();
einUsername
15
Vous pouvez également essayer l' interpréteur BeanShell :
Pouvez-vous me dire comment utiliser BeanShell dans adnroid Studio.
Hanni
1
Hanni - ce message pourrait vous aider à ajouter BeanShell à votre projet
androidstudio
14
Vous pouvez évaluer facilement des expressions si votre application Java accède déjà à une base de données, sans utiliser d'autres JAR.
Certaines bases de données nécessitent que vous utilisiez une table fictive (par exemple, la table "double" d'Oracle) et d'autres vous permettront d'évaluer des expressions sans "sélectionner" dans une table.
Par exemple, dans Sql Server ou Sqlite
select (((12.10+12.0))/233.0) amount
et dans Oracle
select (((12.10+12.0))/233.0) amount from dual;
L'avantage d'utiliser une base de données est que vous pouvez évaluer plusieurs expressions en même temps. De plus, la plupart des bases de données vous permettront d'utiliser des expressions très complexes et auront également un certain nombre de fonctions supplémentaires qui peuvent être appelées si nécessaire.
Cependant, les performances peuvent souffrir si de nombreuses expressions uniques doivent être évaluées individuellement, en particulier lorsque la base de données se trouve sur un serveur réseau.
Ce qui suit résout le problème de performances dans une certaine mesure, en utilisant une base de données en mémoire Sqlite.
Cela dépend de l'utilisation que vous faites de la base de données. Si vous voulez en être sûr, vous pouvez facilement créer une base de données sqlite vide, spécialement pour l'évaluation mathématique.
DAB
4
@cyberz Si vous utilisez mon exemple ci-dessus, Sqlite créera une base de données temporaire en mémoire. Voir stackoverflow.com/questions/849679/…
DAB
11
Cet article présente différentes approches. Voici les 2 approches clés mentionnées dans l'article:
Permet les scripts qui incluent des références aux objets java.
// 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);
Veuillez résumer les informations de l'article, au cas où le lien vers celui-ci serait rompu.
DJClayworth
J'ai mis à jour la réponse pour inclure des extraits pertinents de l'article
Brad Parks
1
en pratique, JEXL est lent (utilise l'introspection des beans), a des problèmes de performances avec le multithreading (cache global)
Nishi
Bon à savoir @Nishi! - Mon cas d'utilisation était pour le débogage de choses dans des environnements vivants, mais ne faisait pas partie de l'application déployée normale.
Brad Parks
10
Une autre façon consiste à utiliser Spring Expression Language ou SpEL qui fait beaucoup plus avec l'évaluation des expressions mathématiques, donc peut-être un peu exagéré. Il n'est pas nécessaire d'utiliser Spring Framework pour utiliser cette bibliothèque d'expressions car elle est autonome. Copie d'exemples de la documentation de SpEL:
si nous allons l'implémenter, nous pouvons utiliser l'algorithme ci-dessous: -
Bien qu'il reste des jetons à lire,
1.1 Obtenez le prochain jeton. 1.2 Si le jeton est:
1.2.1 Un nombre: poussez-le sur la pile de valeurs.
1.2.2 Une variable: obtenez sa valeur et poussez sur la pile de valeurs.
1.2.3 Une parenthèse gauche: poussez-la sur la pile d'opérateurs.
1.2.4 Une parenthèse droite:
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 Un opérateur (appelez-le 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.
Tant que la pile d'opérateurs n'est pas vide, 1 Pop l'opérateur de la pile d'opérateurs. 2 Pop la pile de valeurs deux fois, obtenant deux opérandes. 3 Appliquez l'opérateur aux opérandes, dans le bon ordre. 4 Poussez le résultat sur la pile de valeurs.
À ce stade, la pile d'opérateurs doit être vide et la pile de valeurs ne doit contenir qu'une seule valeur, ce qui est le résultat final.
Je pense que quoi que vous fassiez, cela impliquera beaucoup de déclarations conditionnelles. Mais pour des opérations uniques comme dans vos exemples, vous pouvez le limiter à 4 si des instructions avec quelque chose comme
String math ="1+4";if(math.split("+").length ==2){//do calculation}elseif(math.split("-").length ==2){//do calculation}...
Cela devient beaucoup plus compliqué lorsque vous souhaitez gérer plusieurs opérations comme "4 + 5 * 6".
Si vous essayez de construire une calculatrice, je ferais mieux de passer chaque section du calcul séparément (chaque nombre ou opérateur) plutôt que comme une seule chaîne.
Cela devient beaucoup plus compliqué dès que vous devez gérer plusieurs opérations, la priorité des opérateurs, les parenthèses, ... en fait tout ce qui caractérise une véritable expression arithmétique. Vous ne pouvez pas y arriver à partir de cette technique.
Marquis de Lorne
4
Il est trop tard pour répondre mais je suis tombé sur la même situation pour évaluer l'expression en java, cela pourrait aider quelqu'un
MVELfait l'évaluation des expressions au moment de l'exécution, nous pouvons écrire un code java Stringpour le faire évaluer.
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"
Notez que des expressions définitivement plus complexes peuvent être évaluées:
// 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
Cela complète en fait la réponse donnée par @Boann. Il a un léger bug qui fait que "-2 ^ 2" donne un résultat erroné de -4.0. Le problème pour cela est le point auquel l'exponentiation est évaluée dans le sien. Déplacez simplement l'exponentiation vers le bloc de parseTerm (), et tout ira bien. Jetez un œil à ce qui suit, qui est la réponse de @ Boann légèrement modifiée. La modification est dans les commentaires.
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 = -4est en fait normal, et pas un bug. Il est groupé comme -(2^2). Essayez-le sur Desmos, par exemple. Votre code introduit en fait plusieurs bogues. La première est que ^ne regroupe plus de droite à gauche. En d'autres termes, 2^3^2est supposé grouper comme 2^(3^2)parce qu'il ^est associatif à droite, mais vos modifications le rendent groupé (2^3)^2. La seconde est celle qui ^est censée avoir une priorité plus élevée que *et /, mais vos modifications la traitent de la même manière. Voir ideone.com/iN2mMa .
Radiodef
Donc, ce que vous proposez, c'est que l'exponentiation est mieux conservée là où elle était, n'est-ce pas?
Romeo Sierra
Oui, c'est ce que je propose.
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));}
Vous devriez lire comment écrire des analyseurs d'expression mathématiques efficaces. Il y a une méthodologie informatique. Jetez un œil à ANTLR, par exemple. Si vous pensez bien à ce que vous avez écrit, vous verrez que des choses comme (a + b / -c) * (e / f) ne fonctionneront pas avec votre idée ou le code sera super duper sale et inefficace.
Daniel Nuriyev
2
Il est possible de convertir n'importe quelle chaîne d'expression en notation infixe en une notation postfixe en utilisant l' algorithme de shunting-yard de Djikstra . Le résultat de l'algorithme peut alors servir d'entrée à l' algorithme de postfixe avec retourne le résultat de l'expression.
Ne gère pas correctement la priorité des opérateurs. Il existe des moyens standard de le faire, et ce n'est pas l'un d'entre eux.
Marquis de Lorne
EJP, pouvez-vous s'il vous plaît indiquer où il y a un problème avec la priorité des opérateurs? je suis entièrement d'accord sur le fait que ce n'est pas la manière standard de le faire. les méthodes standard étaient déjà mentionnées dans les articles précédents, l'idée était de montrer une autre façon de le faire.
Efi G
2
Une bibliothèque externe comme RHINO ou NASHORN peut être utilisée pour exécuter javascript. Et javascript peut évaluer une formule simple sans parceller la chaîne. Aucun impact sur les performances également si le code est bien écrit. Voici un exemple avec RHINO -
Réponses:
Avec JDK1.6, vous pouvez utiliser le moteur Javascript intégré.
la source
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();");
- va écrire un fichier via JavaScript dans (par défaut) le répertoire courant du programmeJ'ai écrit cette
eval
méthode pour les expressions arithmétiques pour répondre à cette question. Il fait l'addition, la soustraction, la multiplication, la division, l'exponentiation (en utilisant le^
symbole), et quelques fonctions de base commesqrt
. Il prend en charge le regroupement à l'aide de(
...)
et obtient la priorité de l'opérateur et les règles d' associativité correctes.Exemple:
Sortie: 7,5 (ce qui est correct)
L'analyseur est un analyseur de descente récursif , donc utilise en interne des méthodes d'analyse distinctes pour chaque niveau de priorité de l'opérateur dans sa grammaire. Je l'ai gardé court donc c'est facile à modifier, mais voici quelques idées que vous voudrez peut-être développer avec:
Variables:
Le bit de l'analyseur qui lit les noms des fonctions peut facilement être modifié pour gérer également les variables personnalisées, en recherchant les noms dans une table de variables passée à la
eval
méthode, comme aMap<String,Double> variables
.Compilation et évaluation séparées:
Et si, après avoir ajouté la prise en charge des variables, vous vouliez évaluer la même expression des millions de fois avec des variables modifiées, sans l'analyser à chaque fois? C'est possible. Définissez d'abord une interface à utiliser pour évaluer l'expression précompilée:
Maintenant, changez toutes les méthodes qui retournent
double
s, donc à la place, elles retournent une instance de cette interface. La syntaxe lambda de Java 8 fonctionne très bien pour cela. Exemple d'une des méthodes modifiées:Cela construit un arbre récursif d'
Expression
objets représentant l'expression compilée (un arbre de syntaxe abstraite ). Ensuite, vous pouvez le compiler une fois et l'évaluer à plusieurs reprises avec différentes valeurs:Différents types de données:
Au lieu de
double
, vous pouvez changer l'évaluateur pour utiliser quelque chose de plus puissantBigDecimal
, ou une classe qui implémente des nombres complexes, ou des nombres rationnels (fractions). Vous pouvez même utiliserObject
, permettant un mélange de types de données dans les expressions, tout comme un véritable langage de programmation. :)Tout le code de cette réponse est tombé dans le domaine public . S'amuser!
la source
double x = parseTerm();
évalue l'opérateur de gauche, puisfor (;;) {...}
évalue les opérations successives du niveau d'ordre réel (addition, soustraction). La même logique est et dans la méthode parseTerm. Le parseFactor n'a pas de niveau suivant, il n'y a donc que des évaluations de méthodes / variables ou en cas de paranthèse - évaluer la sous-expression. Laboolean eat(int charToEat)
méthode vérifie l'égalité du caractère du curseur actuel avec le caractère charToEat, si égal retourne vrai et déplace le curseur au caractère suivant, j'utilise le nom 'accept' pour cela.La bonne façon de résoudre ce problème est d' utiliser un lexer et un analyseur . Vous pouvez en écrire vous-même des versions simples, ou ces pages contiennent également des liens vers des lexers et des analyseurs Java.
La création d'un analyseur de descente récursive est un très bon exercice d'apprentissage.
la source
Pour mon projet universitaire, je cherchais un analyseur / évaluateur supportant à la fois les formules de base et les équations plus compliquées (notamment les opérateurs itérés). J'ai trouvé une très belle bibliothèque open source pour JAVA et .NET appelée mXparser. Je vais donner quelques exemples pour vous faire une idée de la syntaxe, pour plus d'instructions, veuillez visiter le site Web du projet (en particulier la section tutoriel).
https://mathparser.org/
https://mathparser.org/mxparser-tutorial/
https://mathparser.org/api/
Et quelques exemples
1 - Furmula simple
2 - Arguments et constantes définis par l'utilisateur
3 - Fonctions définies par l'utilisateur
4 - Itération
Trouvé récemment - au cas où vous voudriez essayer la syntaxe (et voir le cas d'utilisation avancée), vous pouvez télécharger l' application Scalar Calculator qui est propulsée par mXparser.
Meilleures salutations
la source
ICI est une autre bibliothèque open source sur GitHub nommée EvalEx.
Contrairement au moteur JavaScript, cette bibliothèque se concentre uniquement sur l'évaluation des expressions mathématiques. De plus, la bibliothèque est extensible et prend en charge l'utilisation d'opérateurs booléens ainsi que les parenthèses.
la source
Vous pouvez également essayer l' interpréteur BeanShell :
la source
Vous pouvez évaluer facilement des expressions si votre application Java accède déjà à une base de données, sans utiliser d'autres JAR.
Certaines bases de données nécessitent que vous utilisiez une table fictive (par exemple, la table "double" d'Oracle) et d'autres vous permettront d'évaluer des expressions sans "sélectionner" dans une table.
Par exemple, dans Sql Server ou Sqlite
et dans Oracle
L'avantage d'utiliser une base de données est que vous pouvez évaluer plusieurs expressions en même temps. De plus, la plupart des bases de données vous permettront d'utiliser des expressions très complexes et auront également un certain nombre de fonctions supplémentaires qui peuvent être appelées si nécessaire.
Cependant, les performances peuvent souffrir si de nombreuses expressions uniques doivent être évaluées individuellement, en particulier lorsque la base de données se trouve sur un serveur réseau.
Ce qui suit résout le problème de performances dans une certaine mesure, en utilisant une base de données en mémoire Sqlite.
Voici un exemple de travail complet en Java
Bien sûr, vous pouvez étendre le code ci-dessus pour gérer plusieurs calculs en même temps.
la source
Cet article présente différentes approches. Voici les 2 approches clés mentionnées dans l'article:
JEXL d'Apache
Permet les scripts qui incluent des références aux objets java.
Utilisez le moteur javascript intégré au JDK:
la source
Une autre façon consiste à utiliser Spring Expression Language ou SpEL qui fait beaucoup plus avec l'évaluation des expressions mathématiques, donc peut-être un peu exagéré. Il n'est pas nécessaire d'utiliser Spring Framework pour utiliser cette bibliothèque d'expressions car elle est autonome. Copie d'exemples de la documentation de SpEL:
Lisez des exemples SpEL plus concis ici et la documentation complète ici
la source
si nous allons l'implémenter, nous pouvons utiliser l'algorithme ci-dessous: -
Bien qu'il reste des jetons à lire,
1.1 Obtenez le prochain jeton. 1.2 Si le jeton est:
1.2.1 Un nombre: poussez-le sur la pile de valeurs.
1.2.2 Une variable: obtenez sa valeur et poussez sur la pile de valeurs.
1.2.3 Une parenthèse gauche: poussez-la sur la pile d'opérateurs.
1.2.4 Une parenthèse droite:
1.2.5 Un opérateur (appelez-le thisOp):
Tant que la pile d'opérateurs n'est pas vide, 1 Pop l'opérateur de la pile d'opérateurs. 2 Pop la pile de valeurs deux fois, obtenant deux opérandes. 3 Appliquez l'opérateur aux opérandes, dans le bon ordre. 4 Poussez le résultat sur la pile de valeurs.
À ce stade, la pile d'opérateurs doit être vide et la pile de valeurs ne doit contenir qu'une seule valeur, ce qui est le résultat final.
la source
Ceci est une autre alternative intéressante https://github.com/Shy-Ta/expression-evaluator-demo
L'utilisation est très simple et fait le travail, par exemple:
la source
Il semble que JEP devrait faire le travail
la source
Je pense que quoi que vous fassiez, cela impliquera beaucoup de déclarations conditionnelles. Mais pour des opérations uniques comme dans vos exemples, vous pouvez le limiter à 4 si des instructions avec quelque chose comme
Cela devient beaucoup plus compliqué lorsque vous souhaitez gérer plusieurs opérations comme "4 + 5 * 6".
Si vous essayez de construire une calculatrice, je ferais mieux de passer chaque section du calcul séparément (chaque nombre ou opérateur) plutôt que comme une seule chaîne.
la source
Il est trop tard pour répondre mais je suis tombé sur la même situation pour évaluer l'expression en java, cela pourrait aider quelqu'un
MVEL
fait l'évaluation des expressions au moment de l'exécution, nous pouvons écrire un code javaString
pour le faire évaluer.la source
Vous pourriez jeter un œil au framework Symja :
Notez que des expressions définitivement plus complexes peuvent être évaluées:
la source
Essayez l'exemple de code suivant en utilisant le moteur Javascript de JDK1.6 avec la gestion de l'injection de code.
la source
Cela complète en fait la réponse donnée par @Boann. Il a un léger bug qui fait que "-2 ^ 2" donne un résultat erroné de -4.0. Le problème pour cela est le point auquel l'exponentiation est évaluée dans le sien. Déplacez simplement l'exponentiation vers le bloc de parseTerm (), et tout ira bien. Jetez un œil à ce qui suit, qui est la réponse de @ Boann légèrement modifiée. La modification est dans les commentaires.
la source
-2^2 = -4
est en fait normal, et pas un bug. Il est groupé comme-(2^2)
. Essayez-le sur Desmos, par exemple. Votre code introduit en fait plusieurs bogues. La première est que^
ne regroupe plus de droite à gauche. En d'autres termes,2^3^2
est supposé grouper comme2^(3^2)
parce qu'il^
est associatif à droite, mais vos modifications le rendent groupé(2^3)^2
. La seconde est celle qui^
est censée avoir une priorité plus élevée que*
et/
, mais vos modifications la traitent de la même manière. Voir ideone.com/iN2mMa .}
la source
Que diriez-vous quelque chose comme ça:
et faire la même chose pour tous les autres opérateurs mathématiques en conséquence ..
la source
Il est possible de convertir n'importe quelle chaîne d'expression en notation infixe en une notation postfixe en utilisant l' algorithme de shunting-yard de Djikstra . Le résultat de l'algorithme peut alors servir d'entrée à l' algorithme de postfixe avec retourne le résultat de l'expression.
J'ai écrit un article à ce sujet ici, avec une implémentation en java
la source
Encore une autre option: https://github.com/stefanhaustein/expressionparser
J'ai implémenté cela pour avoir une option simple mais flexible pour permettre à la fois:
Le TreeBuilder lié ci-dessus fait partie d'un package de démonstration CAS qui fait une dérivation symbolique. Il existe également un exemple d' interpréteur BASIC et j'ai commencé à construire un interpréteur TypeScript en l' utilisant.
la source
Une classe Java qui peut évaluer des expressions mathématiques:
la source
Une bibliothèque externe comme RHINO ou NASHORN peut être utilisée pour exécuter javascript. Et javascript peut évaluer une formule simple sans parceller la chaîne. Aucun impact sur les performances également si le code est bien écrit. Voici un exemple avec RHINO -
la source
la source