Come valutare un'espressione matematica fornita in forma di stringa?


318

Sto cercando di scrivere una routine Java per valutare semplici espressioni matematiche da Stringvalori come:

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

Voglio evitare molte dichiarazioni if-then-else. Come posso fare questo?


7
Di recente ho scritto un parser di espressioni matematiche chiamato exp4j che è stato rilasciato con la licenza apache che puoi consultare qui: objecthunter.net/exp4j
fasseg il

2
Che tipo di espressioni permetti? Espressioni di un solo operatore? Le parentesi sono consentite?
Raedwald,



3
Come può questo essere considerato troppo ampio? La valutazione di Dijkstra è la soluzione ovvia qui en.wikipedia.org/wiki/Shunting-yard_algorithm
Martin Spamer,

Risposte:


376

Con JDK1.6, è possibile utilizzare il motore Javascript incorporato.

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

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

52
Sembra che ci sia un grosso problema lì; Esegue uno script, non valuta un'espressione. Per essere chiari, engine.eval ("8; 40 + 2"), genera 42! Se vuoi un parser di espressioni che controlli anche la sintassi, ne ho appena terminato uno (perché non ho trovato nulla adatto alle mie esigenze): Javaluator .
Jean-Marc Astesana,

4
Come nota a margine, se è necessario utilizzare il risultato di questa espressione altrove nel codice, è possibile digitare il risultato in modo doppio in questo modo: return (Double) engine.eval(foo);
Ben Visness

38
Nota di sicurezza: non si dovrebbe mai usare questo in un contesto di server con l'input dell'utente. Il JavaScript eseguito può accedere a tutte le classi Java e quindi dirottare la tua applicazione senza limiti.
Boann,

3
@Boann, ti chiedo di darmi un riferimento su ciò che hai detto. (Per essere sicuro al 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();");- scriverà un file via JavaScript nella (per impostazione predefinita) nella directory corrente del programma
Boann,

236

Ho scritto questo evalmetodo per le espressioni aritmetiche per rispondere a questa domanda. Fa addizione, sottrazione, moltiplicazione, divisione, esponenziazione (usando il ^simbolo) e alcune funzioni di base come sqrt. Supporta il raggruppamento utilizzando (... )e ottiene la precedenza dell'operatore e le regole di associatività corrette.

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

Esempio:

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

Uscita: 7.5 (che è corretta)


Il parser è un parser di discesa ricorsivo , quindi utilizza internamente metodi di analisi separati per ciascun livello di precedenza dell'operatore nella sua grammatica. L'ho tenuto breve, quindi è facile da modificare, ma ecco alcune idee con cui potresti volerlo espandere:

  • variabili:

    Il bit del parser che legge i nomi per le funzioni può essere facilmente modificato per gestire anche le variabili personalizzate, cercando i nomi in una tabella delle variabili passata al evalmetodo, ad esempio a Map<String,Double> variables.

  • Raccolta e valutazione separate:

    E se, avendo aggiunto il supporto per le variabili, volessi valutare la stessa espressione milioni di volte con variabili modificate, senza analizzarle ogni volta? È possibile. Definire innanzitutto un'interfaccia da utilizzare per valutare l'espressione precompilata:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Ora cambia tutti i metodi che restituiscono doubles, quindi invece restituiscono un'istanza di tale interfaccia. La sintassi lambda di Java 8 funziona benissimo per questo. Esempio di uno dei metodi modificati:

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

    Ciò crea un albero ricorsivo di Expressionoggetti che rappresentano l'espressione compilata (un albero di sintassi astratto ). Quindi puoi compilarlo una volta e valutarlo ripetutamente con valori diversi:

    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());
        }
    }
  • Diversi tipi di dati:

    Invece double, potresti cambiare il valutatore per usare qualcosa di più potente BigDecimal, o una classe che implementa numeri complessi o numeri razionali (frazioni). Potresti persino usare Object, permettendo un mix di tipi di dati nelle espressioni, proprio come un vero linguaggio di programmazione. :)


Tutto il codice in questa risposta è stato rilasciato al pubblico dominio . Divertiti!


1
Simpatico algoritmo, partendo da esso sono riuscito a impiantare e operatori logici. Abbiamo creato classi separate per le funzioni per valutare una funzione, quindi come la tua idea di variabili, creo una mappa con funzioni e mi occupo del nome della funzione. Ogni funzione implementa un'interfaccia con un metodo eval (T rightOperator, T leftOperator), quindi in qualsiasi momento possiamo aggiungere funzionalità senza modificare il codice dell'algoritmo. Ed è una buona idea farlo funzionare con tipi generici. Grazie!
Vasile Bors,

1
Puoi spiegare la logica dietro questo algoritmo?
iYonatan,

1
Provo a dare una descrizione di ciò che ho capito dal codice scritto da Boann e alcuni esempi di wiki. La logica di questo algoritmo a partire dalle regole degli ordini operativi. 1. segno dell'operatore | valutazione variabile | chiamata di funzione | parentesi (sottoespressioni); 2. esponenziazione; 3. moltiplicazione, divisione; 4. addizione, sottrazione;
Vasile Bors,

1
I metodi dell'algoritmo sono divisi per ciascun livello di ordine delle operazioni come segue: parseFactor = 1. segno operatore | valutazione variabile | chiamata di funzione | parentesi (sottoespressioni); 2. esponenziazione; parseTerms = 3. moltiplicazione, divisione; parseExpression = 4. addizione, sottrazione. L'algoritmo, chiama i metodi in ordine inverso (parseExpression -> parseTerms -> parseFactor -> parseExpression (per sottoespressioni)), ma tutti i metodi della prima riga chiamano il metodo al livello successivo, quindi l'intero metodo dell'ordine di esecuzione sarà ordine delle operazioni effettivamente normale.
Vasile Bors,

1
Ad esempio il metodo parseExpression double x = parseTerm(); valuta l'operatore sinistro, dopo questo for (;;) {...}valuta le operazioni successive del livello di ordine effettivo (addizione, sottrazione). La stessa logica è e nel metodo parseTerm. Il parseFactor non ha un livello successivo, quindi ci sono solo valutazioni di metodi / variabili o in caso di parantesi - valuta la sottoespressione. Il boolean eat(int charToEat)metodo controlla l'uguaglianza dell'attuale carattere del cursore con il carattere charToEat, se uguale restituisce vero e sposta il cursore sul carattere successivo, uso il nome 'accetta' per questo.
Vasile Bors,

34

Il modo corretto di risolverlo è con un lexer e un parser . È possibile scrivere versioni semplici di questi stessi, o quelle pagine hanno anche collegamenti a lexer e parser Java.

La creazione di un parser di discesa ricorsiva è un ottimo esercizio di apprendimento.


26

Per il mio progetto universitario, stavo cercando un parser / valutatore che supportasse sia le formule di base che le equazioni più complicate (in particolare gli operatori iterati). Ho trovato una libreria open source molto bella per JAVA e .NET chiamata mXparser. Fornirò alcuni esempi per dare un'idea della sintassi, per ulteriori istruzioni visitare il sito Web del progetto (in particolare la sezione tutorial).

https://mathparser.org/

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

https://mathparser.org/api/

E alcuni esempi

1 - Furmula semplice

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

2 - Argomenti e costanti definiti dall'utente

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 - Funzioni definite dall'utente

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

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

Trovato di recente: nel caso in cui si desideri provare la sintassi (e vedere il caso d'uso avanzato) è possibile scaricare l' app Calcolatrice scalare basata su mXparser.

I migliori saluti


Finora questa è la migliore biblioteca di matematica là fuori; semplice da avviare, facile da usare ed estensibile. Sicuramente dovrebbe essere la risposta migliore.
Trynkiewicz Mariusz,

Trova la versione di Maven qui .
izogfif,

Ho scoperto che mXparser non è in grado di identificare la formula illegale, ad esempio '0/0' otterrà un risultato come '0'. Come posso risolvere questo problema?
lulijun

Ho appena trovato la soluzione, expression.setSlientMode ()
lulijun

20

QUI è un'altra libreria open source su GitHub chiamata EvalEx.

A differenza del motore JavaScript, questa libreria è focalizzata nella valutazione delle sole espressioni matematiche. Inoltre, la libreria è estensibile e supporta l'uso di operatori booleani e parentesi.


Questo va bene, ma fallisce quando proviamo a moltiplicare i valori di multipli di 5 o 10, ad esempio 65 * 6 risulta in 3,9 E + 2 ...
paarth batra,

Ma c'è un modo per risolvere questo problema lanciandolo su int ovvero int output = (int) 65 * 6 risulterà ora 390
paarth batra

1
Per chiarire, questo non è un problema della libreria ma piuttosto un problema con la rappresentazione dei numeri come valori in virgola mobile.
DavidBittner,

Questa libreria è davvero buona. @paarth batra Il cast su int rimuoverà tutti i punti decimali. Utilizzare questo invece: expression.eval (). ToPlainString ();
einUsername


14

È possibile valutare facilmente le espressioni se l'applicazione Java accede già a un database, senza utilizzare altri JAR.

Alcuni database richiedono l'uso di una tabella fittizia (ad esempio, la tabella "doppia" di Oracle) e altri ti permetteranno di valutare le espressioni senza "selezionare" da nessuna tabella.

Ad esempio, in Sql Server o Sqlite

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

e in Oracle

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

Il vantaggio dell'utilizzo di un DB è che è possibile valutare più espressioni contemporaneamente. Inoltre, la maggior parte dei DB ti permetterà di usare espressioni molto complesse e avrà anche un numero di funzioni extra che possono essere chiamate come necessario.

Tuttavia, le prestazioni potrebbero risentire della necessità di valutare singolarmente molte singole espressioni, in particolare quando il DB si trova su un server di rete.

Quanto segue risolve il problema delle prestazioni in una certa misura, utilizzando un database in memoria Sqlite.

Ecco un esempio funzionante completo in Java

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

Naturalmente è possibile estendere il codice sopra per gestire più calcoli contemporaneamente.

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

5
Saluta l'iniezione SQL!
cyberz,

Dipende da cosa usi il DB per. Se vuoi essere sicuro, potresti facilmente creare un DB sqlite vuoto, in particolare per la valutazione matematica.
DAB,

4
@cyberz Se usi il mio esempio sopra, Sqlite creerà un DB temporaneo in memoria. Vedi stackoverflow.com/questions/849679/…
DAB

11

Questo articolo discute vari approcci. Ecco i 2 approcci chiave menzionati nell'articolo:

JEXL di Apache

Consente script che includono riferimenti a oggetti 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);

Usa il motore javascript incorporato nel 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();
    }
}

4
Si prega di riassumere le informazioni dall'articolo, nel caso in cui il collegamento ad esso sia interrotto.
DJClayworth,

Ho aggiornato la risposta per includere parti pertinenti dell'articolo
Brad Parks,

1
in pratica, JEXL è lento (usa l'introspezione dei bean), ha problemi di prestazioni con il multithreading (cache globale)
Nishi

Buono a sapersi @Nishi! - Il mio caso d'uso era per il debug di cose in ambienti live, ma non faceva parte della normale app distribuita.
Brad Parks

10

Un altro modo è quello di usare Spring Expression Language o SpEL che fa molto di più insieme alla valutazione delle espressioni matematiche, quindi forse leggermente eccessivo. Non è necessario utilizzare il framework Spring per utilizzare questa libreria di espressioni poiché è autonoma. Copia di esempi dalla documentazione di 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

Leggi altri esempi concisi di SpEL qui e i documenti completi qui


8

se lo implementeremo, allora possiamo usare l'algoritmo seguente: -

  1. Mentre ci sono ancora token da leggere,

    1.1 Ottieni il prossimo token. 1.2 Se il token è:

    1.2.1 Un numero: spingilo nello stack di valori.

    1.2.2 Una variabile: ottieni il suo valore e spingi nello stack di valori.

    1.2.3 Una parentesi sinistra: spingerla sulla pila dell'operatore.

    1.2.4 Una parentesi corretta:

     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 operatore (chiamalo 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. Mentre lo stack dell'operatore non è vuoto, 1 Estrarre l'operatore dallo stack dell'operatore. 2 Pop lo stack di valori due volte, ottenendo due operandi. 3 Applicare l'operatore agli operandi, nell'ordine corretto. 4 Spingere il risultato sulla pila di valori.

  3. A questo punto lo stack dell'operatore dovrebbe essere vuoto e lo stack dei valori dovrebbe contenere un solo valore, che è il risultato finale.


3
Questa è un'esposizione non accreditata dell'algoritmo Dijkstra Shunting-yard . Credito dove è dovuto il credito.
Marchese di Lorne,



4

Penso che in qualunque modo tu faccia questo implicherà molte affermazioni condizionali. Ma per singole operazioni come nei tuoi esempi potresti limitarlo a 4 se istruzioni con qualcosa di simile

String math = "1+4";

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

Diventa molto più complicato quando vuoi occuparti di più operazioni come "4 + 5 * 6".

Se stai cercando di costruire una calcolatrice, farei più rapidamente il passaggio di ciascuna sezione del calcolo separatamente (ogni numero o operatore) anziché come singola stringa.


2
Diventa molto più complicato non appena devi affrontare operazioni multiple, precedenza dell'operatore, parentesi, ... in realtà tutto ciò che caratterizza una vera espressione aritmetica. Non puoi arrivarci a partire da questa tecnica.
Marchese di Lorne,

4

È troppo tardi per rispondere, ma mi sono imbattuto nella stessa situazione per valutare l'espressione in java, potrebbe aiutare qualcuno

MVELfa la valutazione runtime delle espressioni, possiamo scrivere un codice Java Stringper farlo valutare in questo.

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

Ho cercato e trovato alcune funzioni aritmetiche aggiuntive gestite anche qui github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Stupendo! Mi ha salvato la giornata. Grazie
Sarika.S

4

Potresti dare un'occhiata al framework Symja :

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

Si noti che è possibile valutare in modo definitivo espressioni più complesse:

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

4

Prova il seguente codice di esempio utilizzando il motore Javascript di JDK1.6 con la gestione dell'iniezione di codice.

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

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

4

Questo in realtà completa la risposta data da @Boann. Ha un leggero bug che fa sì che "-2 ^ 2" dia un risultato errato di -4.0. Il problema per questo è il punto in cui viene valutata l'espiazione nella sua. Basta spostare l'esponente nel blocco di parseTerm () e andrà tutto bene. Dai un'occhiata al seguito, che è la risposta di @ Boann leggermente modificata. La modifica è nei commenti.

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

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

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

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

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

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

        double parseTerm() {
            double x = parseFactor();
            for (;;) {
                if      (eat('*')) x *= parseFactor(); // multiplication
                else if (eat('/')) x /= parseFactor(); // division
                else if (eat('^')) x = Math.pow(x, parseFactor()); //exponentiation -> Moved in to here. So the problem is fixed
                else return x;
            }
        }

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

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

            //if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problem

            return x;
        }
    }.parse();
}

-2^2 = -4è in realtà normale e non un bug. Viene raggruppato come -(2^2). Provalo su Desmos, per esempio. Il codice in realtà introduce diversi bug. Il primo è che ^non raggruppa più da destra a sinistra. In altre parole, 2^3^2dovrebbe raggrupparsi come 2^(3^2)perché ^è associativo giusto, ma le tue modifiche lo rendono simile a un gruppo (2^3)^2. Il secondo è che ^dovrebbe avere una precedenza più alta di *e /, ma le tue modifiche lo trattano allo stesso modo. Vedi ideone.com/iN2mMa .
Radiodef,

Quindi, quello che stai suggerendo è che l'espiazione è meglio conservata dove era, no?
Romeo Sierra,

Sì, è quello che sto suggerendo.
Radiodef,

4
package ExpressionCalculator.expressioncalculator;

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

public class ExpressionCalculator {

private static String addSpaces(String exp){

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

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

       return exp;
}

public static Double evaluate(String expr){

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

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

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

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

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

public static void main(String args[]){

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

}


1
Non gestisce la precedenza dell'operatore, o più operatori o parentesi. Non usare.
Marchese di Lorne,

2

Che ne dici di qualcosa del genere:

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

e fare la stessa cosa per ogni altro operatore matematico di conseguenza ...


9
Dovresti leggere informazioni sulla scrittura di parser di espressioni matematiche efficienti. Esiste una metodologia informatica. Dai un'occhiata ad ANTLR, per esempio. Se pensi bene a ciò che hai scritto vedrai che cose come (a + b / -c) * (e / f) non funzioneranno con la tua idea o il codice sarà super duper sporco e inefficiente.
Daniel Nuriyev,


2

Ancora un'altra opzione: https://github.com/stefanhaustein/expressionparser

Ho implementato questo per avere un'opzione semplice ma flessibile per consentire entrambi:

TreeBuilder collegato sopra fa parte di un pacchetto demo CAS che esegue derivazioni simboliche. C'è anche un esempio di interprete BASIC e ho iniziato a costruire un interprete TypeScript usando esso.


2

Una classe Java in grado di valutare espressioni matematiche:

package test;

public class Calculator {

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

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

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

    }

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


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

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

    }

}

2
Non gestisce correttamente la precedenza dell'operatore. Ci sono modi standard per farlo, e questo non è uno di questi.
Marchese di Lorne,

EJP, puoi indicare dove c'è un problema con la precedenza dell'operatore? concordo pienamente sul fatto che non è il modo standard di farlo. i modi standard erano già stati menzionati nei post precedenti, l'idea era di mostrare un altro modo per farlo.
Efi G

2

Una libreria esterna come RHINO o NASHORN può essere utilizzata per eseguire javascript. E javascript può valutare una formula semplice senza parare la stringa. Nessun impatto sulle prestazioni se il codice è scritto bene. Di seguito è riportato un esempio con RHINO -

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

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

2
import java.util.*;

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

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

   if(op.equals("+")) { ans= v1 + v2; }
   if(op.equals("-")) { ans= v1 - v2; }
   //.........
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.