Jak ocenić wyrażenie matematyczne podane w postaci ciągu?


318

Próbuję napisać procedurę Java, aby ocenić proste wyrażenia matematyczne na podstawie Stringwartości takich jak:

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

Chcę uniknąć wielu stwierdzeń „jeśli to wtedy”. W jaki sposób mogę to zrobić?


7
Niedawno napisałem parser wyrażeń matematycznych o nazwie exp4j, który został wydany na licencji Apache, możesz to sprawdzić tutaj: objecthunter.net/exp4j
fasseg

2
Jakie rodzaje wyrażeń są dozwolone? Tylko wyrażenia jednego operatora? Czy nawiasy są dozwolone?
Raedwald,



3
Jak można to uznać za zbyt szerokie? Ocena Dijkstry jest oczywistym rozwiązaniem tutaj en.wikipedia.org/wiki/Shunting-yard_alameterm
Martin Spamer

Odpowiedzi:


376

Dzięki JDK1.6 możesz korzystać z wbudowanego silnika JavaScript.

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
Wydaje się, że jest tam poważny problem; Wykonuje skrypt, nie ocenia wyrażenia. Żeby było jasne, engine.eval („8; 40 + 2”), wyjścia 42! Jeśli chcesz parsera wyrażeń, który również sprawdza składnię, właśnie go ukończyłem (ponieważ nie znalazłem nic, co odpowiada moim potrzebom): Javaluator .
Jean-Marc Astesana

4
Na marginesie, jeśli chcesz użyć wyniku tego wyrażenia w innym miejscu w kodzie, możesz rzutować wynik do wyniku podwójnego w następujący sposób: return (Double) engine.eval(foo);
Ben Visness

38
Uwaga dotycząca bezpieczeństwa: nigdy nie należy tego używać w kontekście serwera z danymi wejściowymi użytkownika. Wykonywany JavaScript może uzyskać dostęp do wszystkich klas Java i tym samym przejąć twoją aplikację bez ograniczeń.
Boann,

3
@ Booann, proszę o przekazanie mi referencji na temat tego, co powiedziałeś (na pewno 100%)
część

17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- zapisze plik przez JavaScript w (domyślnie) bieżącym katalogu programu
Boann

236

Napisałem tę evalmetodę wyrażeń arytmetycznych, aby odpowiedzieć na to pytanie. Wykonuje dodawanie, odejmowanie, mnożenie, dzielenie, potęgowanie (za pomocą ^symbolu) i kilka podstawowych funkcji, takich jak sqrt. Obsługuje grupowanie za pomocą (... )i poprawia pierwszeństwo operatora oraz zasady asocjatywności .

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

Przykład:

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

Wyjście: 7,5 (co jest poprawne)


Analizator składni jest rekurencyjnym analizatorem pochodzenia , więc wewnętrznie używa osobnych metod analizy dla każdego poziomu pierwszeństwa operatora w gramatyce. I trzymał go krótko więc łatwo zmodyfikować, ale oto kilka pomysłów, które warto go rozwinąć z:

  • Zmienne:

    Bit parsera, który odczytuje nazwy funkcji, można łatwo zmienić w celu obsługi również zmiennych niestandardowych, wyszukując nazwy w tabeli zmiennych przekazywanej do evalmetody, takiej jak a Map<String,Double> variables.

  • Oddzielna kompilacja i ocena:

    Co jeśli, po dodaniu obsługi zmiennych, chciałbyś oceniać to samo wyrażenie miliony razy ze zmienionymi zmiennymi, bez analizowania go za każdym razem? To jest możliwe. Najpierw zdefiniuj interfejs do oceny wstępnie skompilowanego wyrażenia:

    @FunctionalInterface
    interface Expression {
        double eval();
    }

    Teraz zmień wszystkie metody, które zwracają doubles, więc zamiast tego zwracają instancję tego interfejsu. Świetnie nadaje się do tego składnia lambda Java 8. Przykład jednej ze zmienionych metod:

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

    To buduje rekurencyjne drzewo Expressionobiektów reprezentujących skompilowane wyrażenie ( abstrakcyjne drzewo składniowe ). Następnie możesz go raz skompilować i wielokrotnie oceniać przy użyciu różnych wartości:

    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());
        }
    }
  • Różne typy danych:

    Zamiast tego doublemożesz zmienić ewaluator, aby użył czegoś mocniejszego BigDecimal, lub klasy, która implementuje liczby zespolone lub liczby wymierne (ułamki). Możesz nawet użyć Object, pozwalając na mieszanie typów danych w wyrażeniach, tak jak prawdziwy język programowania. :)


Cały kod w tej odpowiedzi został udostępniony publicznie . Baw się dobrze!


1
Niezły algorytm, począwszy od niego udało mi się zaimplementować i operatory logiczne. Stworzyliśmy osobne klasy dla funkcji do oceny funkcji, więc podobnie jak twój pomysł na zmienne, tworzę mapę z funkcjami i dbam o nazwę funkcji. Każda funkcja implementuje interfejs z metodą eval (T rightOperator, T leftOperator), więc w każdej chwili możemy dodawać funkcje bez zmiany kodu algorytmu. Dobrym pomysłem jest, aby działał z typami rodzajowymi. Dzięki Ci!
Vasile Bors,

1
Czy potrafisz wyjaśnić logikę tego algorytmu?
iYonatan

1
Staram się opisać to, co rozumiem z kodu napisanego przez Boanna, oraz przykłady opisujące wiki. Logika tego algorytmu, zaczynając od reguł rozkazów operacji. 1. znak operatora | ocena zmiennych | wywołanie funkcji | nawiasy (podwyrażenia); 2. potęgowanie; 3. mnożenie, dzielenie; 4. dodawanie, odejmowanie;
Vasile Bors

1
Metody algorytmów są podzielone dla każdego poziomu kolejności operacji w następujący sposób: parseFactor = 1. znak operatora | ocena zmiennych | wywołanie funkcji | nawiasy (podwyrażenia); 2. potęgowanie; parseTerms = 3. mnożenie, dzielenie; parseExpression = 4. dodawanie, odejmowanie. Algorytm wywołuje metody w odwrotnej kolejności (parseExpression -> parseTerms -> parseFactor -> parseExpression (dla wyrażeń podrzędnych)), ale każda metoda w pierwszym wierszu wywołuje metodę na następny poziom, więc wszystkie metody kolejności wykonywania będą faktycznie normalna kolejność operacji.
Vasile Bors

1
Na przykład metoda parseExpression double x = parseTerm(); ocenia lewego operatora, po czym for (;;) {...}ocenia kolejne operacje na rzeczywistym poziomie zamówienia (dodawanie, odejmowanie). Ta sama logika jest w metodzie parseTerm. ParseFactor nie ma następnego poziomu, więc są tylko oceny metod / zmiennych lub w przypadku parantez - ocena podwyrażenia. boolean eat(int charToEat)Równość check metoda bieżącego znaku kursora z charakterem charToEat, jeśli równa powrót prawdziwe i przesunięcie kursora do następnego znaku, używam nazwy „akceptować” dla niego.
Vasile Bors

34

Poprawnym sposobem rozwiązania tego jest użycie leksera i parsera . Możesz samodzielnie napisać ich proste wersje, lub na tych stronach znajdują się również linki do leksykonów i analizatorów składni Java.

Tworzenie parsera rekurencyjnego zejścia jest naprawdę dobrym ćwiczeniem edukacyjnym.


26

W moim projekcie uniwersyteckim szukałem parsera / ewaluatora obsługującego zarówno podstawowe formuły, jak i bardziej skomplikowane równania (zwłaszcza iterowane operatory). Znalazłem bardzo ładną bibliotekę open source dla JAVA i .NET o nazwie mXparser. Podam kilka przykładów, aby poczuć składnię. Dalsze instrukcje można znaleźć na stronie projektu (szczególnie w sekcji samouczka).

https://mathparser.org/

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

https://mathparser.org/api/

I kilka przykładów

1 - Prosta furmula

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

2 - Argumenty i stałe zdefiniowane przez użytkownika

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 - Funkcje zdefiniowane przez użytkownika

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

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

Znalezione niedawno - na wypadek gdybyś chciał wypróbować składnię (i zapoznaj się z zaawansowanym przypadkiem użycia), możesz pobrać aplikację Kalkulator skalarny obsługiwany przez mXparser.

Z poważaniem


Jak dotąd jest to najlepsza biblioteka matematyczna; prosty do uruchomienia, łatwy w użyciu i rozszerzalny. Zdecydowanie powinna być najlepsza odpowiedź.
Trynkiewicz Mariusz

Znajdź wersję Maven tutaj .
izogfif

Odkryłem, że mXparser nie może zidentyfikować nielegalnej formuły, na przykład „0/0” otrzyma wynik jako „0”. Jak rozwiązać ten problem?
lulijun

Właśnie znalazłem rozwiązanie, expression.setSlientMode ()
lulijun

20

TUTAJ to kolejna biblioteka open source na GitHub o nazwie EvalEx.

W przeciwieństwie do silnika JavaScript biblioteka ta koncentruje się wyłącznie na ocenie wyrażeń matematycznych. Ponadto biblioteka jest rozszerzalna i obsługuje użycie operatorów logicznych oraz nawiasów.


Jest to w porządku, ale kończy się niepowodzeniem, gdy próbujemy pomnożyć wartości wielokrotności 5 lub 10, na przykład 65 * 6 wyników w 3,9E + 2 ...
paarth batra

.Ale istnieje sposób, aby to naprawić poprzez rzutowanie na int, tj. Int output = (int) 65 * 6, spowoduje to teraz 390
paarth batra

1
Aby wyjaśnić, nie jest to problem biblioteki, ale raczej problem z reprezentacją liczb jako wartości zmiennoprzecinkowych.
DavidBittner,

Ta biblioteka jest naprawdę dobra. @paarth batra Przesyłanie do int usunie wszystkie miejsca po przecinku. Zamiast tego użyj tego: expression.eval (). ToPlainString ();
einUsername

15

Możesz także wypróbować interpreter BeanShell :

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

1
Czy możesz mi powiedzieć, jak używać BeanShell w adnroid Studio.
Hanni

1
Hanni - ten post może pomóc w dodaniu BeanShell do projektu
androidstudio

14

Możesz łatwo oceniać wyrażenia, jeśli aplikacja Java już uzyskuje dostęp do bazy danych, bez używania innych plików JAR.

Niektóre bazy danych wymagają użycia tabeli zastępczej (np. „Podwójnej” tabeli Oracle), a inne pozwalają na ocenę wyrażeń bez „wybierania” z dowolnej tabeli.

Na przykład w Sql Server lub Sqlite

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

i w Oracle

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

Zaletą korzystania z bazy danych jest to, że można oceniać wiele wyrażeń jednocześnie. Również większość baz danych pozwoli ci na użycie bardzo złożonych wyrażeń i będzie mieć także szereg dodatkowych funkcji, które można wywołać w razie potrzeby.

Wydajność może jednak ulec pogorszeniu, jeśli wiele pojedynczych wyrażeń należy oceniać indywidualnie, szczególnie gdy baza danych znajduje się na serwerze sieciowym.

Poniżej w pewnym stopniu rozwiązano problem z wydajnością, korzystając z bazy danych Sqlite w pamięci.

Oto pełny działający przykład w Javie

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

Oczywiście można rozszerzyć powyższy kod, aby obsługiwał wiele obliczeń jednocześnie.

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

5
Przywitaj się z iniekcją SQL!
cyberz

To zależy od tego, do czego używasz DB. Jeśli chcesz mieć pewność, możesz łatwo utworzyć pustą bazę danych sqlite, specjalnie do oceny matematyki.
DAB

4
@cyberz Jeśli użyjesz mojego przykładu powyżej, Sqlite utworzy tymczasową bazę danych w pamięci. Zobacz stackoverflow.com/questions/849679/...
DAB

11

Ten artykuł omówiono różne podejścia. Oto 2 kluczowe podejścia wymienione w artykule:

JEXL z Apache

Pozwala na skrypty zawierające odniesienia do obiektów 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);

Użyj silnika javascript osadzonego w 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
Proszę streścić informacje z artykułu, na wypadek, gdyby link do niego został zerwany.
DJClayworth,

Uaktualniłem odpowiedź, tak aby zawierała odpowiednie fragmenty z artykułu
Brad Parks

1
w praktyce JEXL działa powoli (wykorzystuje introspekcję ziaren), ma problemy z wydajnością podczas wielowątkowości (globalna pamięć podręczna)
Nishi

Dobrze wiedzieć @Nishi! - Mój przypadek użycia dotyczył debugowania rzeczy w środowiskach na żywo, ale nie był częścią normalnie wdrażanej aplikacji.
Brad Parks

10

Innym sposobem jest użycie Spring Expression Language lub SpEL, który robi znacznie więcej wraz z oceną wyrażeń matematycznych, dlatego może nieco przesadzić. Nie musisz używać środowiska Spring, aby korzystać z tej biblioteki wyrażeń, ponieważ jest ona autonomiczna. Kopiowanie przykładów z dokumentacji 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

Przeczytaj więcej zwięzłych przykładów SPEL tutaj i pełne dokumenty tutaj


8

jeśli zamierzamy go wdrożyć, możemy użyć następującego algorytmu:

  1. Chociaż nadal są tokeny do odczytania,

    1.1 Zdobądź następny token. 1.2 Jeśli token to:

    1.2.1 Liczba: wepchnij ją na stos wartości.

    1.2.2 Zmienna: pobierz jej wartość i wciśnij na stos wartości.

    1.2.3 Lewy nawias: wciśnij go na stos operatora.

    1.2.4 Właściwy nawias:

     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 Operator (nazwij to 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. Podczas gdy stos operatora nie jest pusty, 1 Usuń operatora ze stosu operatora. 2 Pop dwukrotnie stos wartości, otrzymując dwa operandy. 3 Zastosuj operatora do operandów we właściwej kolejności. 4 Wciśnij wynik na stos wartości.

  3. W tym momencie stos operatora powinien być pusty, a stos wartości powinien zawierać tylko jedną wartość, co jest wynikiem końcowym.


3
Jest to niewymieniona prezentacja algorytmu stoczni Dijkstra . Kredyt, w którym kredyt jest należny.
Markiz Lorne



4

Myślę, że niezależnie od tego, jak to zrobisz, będzie wymagało wielu stwierdzeń warunkowych. Ale w przypadku pojedynczych operacji, takich jak w przykładach, można ograniczyć go do 4, jeśli instrukcje zawierają coś podobnego

String math = "1+4";

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

To staje się o wiele bardziej skomplikowane, gdy chcesz poradzić sobie z wieloma operacjami, takimi jak „4 + 5 * 6”.

Jeśli próbujesz zbudować kalkulator, z zaskoczeniem przekazałbym każdą sekcję obliczenia osobno (każdą liczbę lub operator), a nie jako pojedynczy ciąg.


2
To staje się o wiele bardziej skomplikowane, gdy tylko masz do czynienia z wieloma operacjami, pierwszeństwem operatora, nawiasami ... w rzeczywistości wszystko, co charakteryzuje prawdziwe wyrażenie arytmetyczne. Nie możesz się tam dostać, zaczynając od tej techniki.
Markiz Lorne

4

Jest za późno, by odpowiedzieć, ale natknąłem się na tę samą sytuację, aby ocenić wyrażenie w java, może to komuś pomóc

MVELwykonuje ocenę wyrażeń w środowisku wykonawczym, możemy napisać kod Java, Stringaby uzyskać w tym miejscu ocenę.

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

Przeszukałem i znalazłem kilka dodatkowych funkcji arytmetycznych, które również zostały tutaj obsługiwane, github.com/mvel/mvel/blob/master/src/test/java/org/mvel2/tests/…
thecodefather

Awsome! Uratowało mi to dzień. Dzięki
Sarika.Sec

4

Możesz rzucić okiem na środowisko Symja :

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

Zwróć uwagę, że można ocenić zdecydowanie bardziej złożone wyrażenia:

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

Wypróbuj następujący przykładowy kod, używając silnika JavaScript JDK1.6 z obsługą wstrzykiwania kodu.

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

To faktycznie uzupełnia odpowiedź udzieloną przez @Boann. Ma niewielki błąd, który powoduje, że „-2 ^ 2” daje błędny wynik -4,0. Problemem jest tutaj punkt, w którym potęguje się jego. Po prostu przenieś potęgowanie do bloku parseTerm (), a wszystko będzie dobrze. Spójrz na poniższe, nieco zmodyfikowana odpowiedź @ Boanna . Modyfikacja znajduje się w komentarzach.

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 = -4jest właściwie normalny i nie jest błędem. Grupuje się jak -(2^2). Wypróbuj na przykład na Desmos. Twój kod faktycznie wprowadza kilka błędów. Po pierwsze, ^grupy nie są grupowane od prawej do lewej. Innymi słowy, 2^3^2należy grupować jak, 2^(3^2)ponieważ ^jest asocjacyjnie prawe, ale twoje modyfikacje sprawiają, że grupuje się podobnie (2^3)^2. Po drugie, ^ma to mieć wyższy priorytet niż *i /, ale twoje modyfikacje traktują to tak samo. Zobacz ideone.com/iN2mMa .
Radiodef

Sugerujesz więc, że potęgowanie jest lepiej utrzymywane tam, gdzie to było, prawda?
Romeo Sierra

Tak, właśnie to sugeruję.
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
Nie obsługuje pierwszeństwa operatorów, kilku operatorów ani nawiasów. Nie używaj.
Markiz Lorne

2

Co powiesz na coś takiego:

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

i zrobić to samo dla każdego innego operatora matematycznego odpowiednio.


9
Powinieneś przeczytać o pisaniu wydajnych parserów wyrażeń matematycznych. Jest w tym metodologia informatyczna. Spójrz na przykład na ANTLR. Jeśli dobrze się zastanowisz nad tym, co napisałeś, zobaczysz, że rzeczy takie jak (a + b / -c) * (e / f) nie będą działać z twoim pomysłem lub kod będzie super duper brudny i nieefektywny.
Daniel Nuriyev


2

Jeszcze inna opcja: https://github.com/stefanhaustein/expressionparser

Zaimplementowałem to, aby mieć prostą, ale elastyczną opcję pozwalającą zarówno na:

TreeBuilder, do którego prowadzi link powyżej, jest częścią pakietu demonstracyjnego CAS, który wykonuje derywację symboliczną. Istnieje również przykład interpretera BASIC i zacząłem budować z niego interpreter TypeScript .


2

Klasa Java, która może oceniać wyrażenia matematyczne:

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
Nie obsługuje poprawnie priorytetu operatora. Istnieją standardowe sposoby na zrobienie tego i nie jest to jeden z nich.
Markiz Lorne

EJP, czy możesz wskazać, gdzie występuje problem z pierwszeństwem operatora? w pełni zgadzam się z tym, że nie jest to standardowy sposób na zrobienie tego. standardowe sposoby były już wspomniane w poprzednich postach, pomysł polegał na wskazaniu innego sposobu na zrobienie tego.
Efi G

2

Zewnętrzna biblioteka, taka jak RHINO lub NASHORN, może być używana do uruchamiania javascript. A javascript może oceniać prostą formułę bez parowania łańcucha. Nie wpływa również na wydajność, jeśli kod jest dobrze napisany. Poniżej znajduje się przykład z 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; }
   //.........
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.