Comment évaluer une expression mathématique donnée sous forme de chaîne?

318

J'essaie d'écrire une routine Java pour évaluer des expressions mathématiques simples à partir de Stringvaleurs comme:

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

Je veux éviter beaucoup de déclarations if-then-else. Comment puis-je faire ceci?

Shah
la source
7
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?
Raedwald
3
Jetez également un œil à l'algorithme à deux piles de Dijkstra
Ritesh
1
Duplication possible de Y a
Andrew Li
3
Comment cela peut-il être considéré comme trop large? L'évaluation de Dijkstra est la solution évidente ici en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer

Réponses:

376

Avec JDK1.6, vous pouvez utiliser le moteur Javascript intégré.

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));
    } 
}
RealHowTo
la source
52
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.

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();
}

Exemple:

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

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 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:

    @FunctionalInterface
    interface Expression {
        double eval();
    }
    

    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('+')) { // 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;
            }
        }
    }
    

    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:

    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());
        }
    }
    
  • 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. :)


Tout le code de cette réponse est tombé dans le domaine public . S'amuser!

Boann
la source
1
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.

Greg Hewgill
la source
26

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

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

2 - Arguments et constantes définis par l'utilisateur

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 - Fonctions définies par l'utilisateur

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 - Itération

Expression e = new Expression("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.

Meilleures salutations

Leroy Kegan
la source
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.
Trynkiewicz Mariusz
Trouvez la version Maven ici .
izogfif
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.

Tanvir
la source
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 :

Interpreter interpreter = new Interpreter();
interpreter.eval("result = (7+21*6)/(32-27)");
System.out.println(interpreter.get("result"));
marciowerner
la source
1
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.

Voici un exemple de travail complet en 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();

Bien sûr, vous pouvez étendre le code ci-dessus pour gérer plusieurs calculs en même temps.

ResultSet rs = stat.executeQuery( "select (1+10)/20.0 amount, (1+100)/20.0 amount2");
touche
la source
5
Dites bonjour à l'injection SQL!
cyberz
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:

JEXL d'Apache

Permet les scripts qui incluent des références aux objets java.

// 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);

Utilisez le moteur javascript intégré au JDK:

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();
    }
}
Brad Parks
la source
4
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:

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

Lisez des exemples SpEL plus concis ici et la documentation complète ici

Faheem Sohail
la source
8

si nous allons l'implémenter, nous pouvons utiliser l'algorithme ci-dessous: -

  1. 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 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 Un opérateur (appelez-le 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. 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.

  3. À 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.

Prashant Gautam
la source
3
Ceci est une exposition non créditée de l' algorithme de Dijkstra Shunting-yard . Crédit lorsque le crédit est dû.
Marquis de Lorne
7

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:

  ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");  
  assertEquals(BigDecimal.valueOf(11), evalExpr.eval()); 
Scorpion
la source
6

Il semble que JEP devrait faire le travail

Bozho
la source
4

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
} else if (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.

Force brute
la source
2
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 = new HashMap<String, Object>();
    vars.put("x", 10);
    vars.put("y", 20);
    ExecutableStatement statement = (ExecutableStatement) MVEL.compileExpression(expressionStr);
    Object result = MVEL.executeExpression(statement, vars);
Saravana
la source
J'ai parcouru et trouvé quelques fonctions arithmétiques supplémentaires également gérées ici github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
theecodefather
Impressionnant! Cela m'a sauvé la journée. Merci
Sarika.S
4

Vous pourriez jeter un œil au framework Symja :

ExprEvaluator util = new ExprEvaluator(); 
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
Laurent Magnin
la source
4

Essayez l'exemple de code suivant en utilisant le moteur Javascript de JDK1.6 avec la gestion de l'injection de code.

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;
    }
 }
}
Bruce
la source
4

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.

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();
}
Romeo Sierra
la source
-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
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));
}

}

chejaras
la source
1
Ne gère pas la priorité des opérateurs, ni plusieurs opérateurs, ni parenthèses. Ne pas utiliser.
Marquis de Lorne
2

Que diriez-vous quelque chose comme ça:

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);
  }         
}

et faire la même chose pour tous les autres opérateurs mathématiques en conséquence ..

konxie
la source
9
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

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.

Stefan Haustein
la source
2

Une classe Java qui peut évaluer des expressions mathématiques:

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)))"));

    }

}
Efi G
la source
2
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 -

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);
}
Manish
la source
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; }
   //.........
}
calcul
la source