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?
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 .
publicstaticdouble eval(finalString str){returnnewObject(){int pos =-1, ch;void nextChar(){
ch =(++pos < str.length())? str.charAt(pos):-1;}boolean eat(int charToEat){while(ch ==' ') nextChar();if(ch == charToEat){
nextChar();returntrue;}returnfalse;}double parse(){
nextChar();double x = parseExpression();if(pos < str.length())thrownewRuntimeException("Unexpected: "+(char)ch);return x;}// Grammar:// expression = term | expression `+` term | expression `-` term// term = factor | term `*` factor | term `/` factor// factor = `+` factor | `-` factor | `(` expression `)`// | number | functionName factor | factor `^` factordouble parseExpression(){double x = parseTerm();for(;;){if(eat('+')) x += parseTerm();// additionelseif(eat('-')) x -= parseTerm();// subtractionelsereturn x;}}double parseTerm(){double x = parseFactor();for(;;){if(eat('*')) x *= parseFactor();// multiplicationelseif(eat('/')) x /= parseFactor();// divisionelsereturn x;}}double parseFactor(){if(eat('+'))return parseFactor();// unary plusif(eat('-'))return-parseFactor();// unary minusdouble x;int startPos =this.pos;if(eat('(')){// parentheses
x = parseExpression();
eat(')');}elseif((ch >='0'&& ch <='9')|| ch =='.'){// numberswhile((ch >='0'&& ch <='9')|| ch =='.') nextChar();
x =Double.parseDouble(str.substring(startPos,this.pos));}elseif(ch >='a'&& ch <='z'){// functionswhile(ch >='a'&& ch <='z') nextChar();String func = str.substring(startPos,this.pos);
x = parseFactor();if(func.equals("sqrt")) x =Math.sqrt(x);elseif(func.equals("sin")) x =Math.sin(Math.toRadians(x));elseif(func.equals("cos")) x =Math.cos(Math.toRadians(x));elseif(func.equals("tan")) x =Math.tan(Math.toRadians(x));elsethrownewRuntimeException("Unknown function: "+ func);}else{thrownewRuntimeException("Unexpected: "+(char)ch);}if(eat('^')) x =Math.pow(x, parseFactor());// exponentiationreturn x;}}.parse();}
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:
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('+')){// additionExpression a = x, b = parseTerm();
x =(()-> a.eval()+ b.eval());}elseif(eat('-')){// subtractionExpression a = x, b = parseTerm();
x =(()-> a.eval()- b.eval());}else{return x;}}}
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:
publicstaticvoid main(String[] args){Map<String,Double> variables =newHashMap<>();Expression exp = parse("x^2 - x + 2", variables);for(double x =-20; x <=+20; x++){
variables.put("x", x);System.out.println(x +" => "+ exp.eval());}}
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!
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.
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).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Argumenty i stałe zdefiniowane przez użytkownika
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Funkcje zdefiniowane przez użytkownika
Function f =newFunction("f(x, y, z) = sin(x) + cos(y*z)");Expression e =newExpression("f(3,2,5)", f);double v = e.calculate()
4 - Iteracja
Expression e =newExpression("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.
Jak dotąd jest to najlepsza biblioteka matematyczna; prosty do uruchomienia, łatwy w użyciu i rozszerzalny. Zdecydowanie powinna być najlepsza odpowiedź.
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 ();
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.
Pozwala na skrypty zawierające odniesienia do obiektów java.
// Create or retrieve a JexlEngineJexlEngine jexl =newJexlEngine();// Create an expression objectString jexlExp ="foo.innerFoo.bar()";Expression e = jexl.createExpression( jexlExp );// Create a context and add dataJexlContext jctx =newMapContext();
jctx.set("foo",newFoo());// Now evaluate the expression, getting the resultObject o = e.evaluate(jctx);
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:
jeśli zamierzamy go wdrożyć, możemy użyć następującego algorytmu:
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:
1While the thing on top of the operator stack is not a
left parenthesis,1Pop the operator from the operator stack.2Pop the value stack twice, getting two operands.3Apply the operator to the operands, in the correct order.4Push the result onto the value stack.2Pop the left parenthesis from the operator stack, and discard it.
1.2.5 Operator (nazwij to thisOp):
1While the operator stack is not empty, and the top thing on the
operator stack has the same or greater precedence as thisOp,1Pop the operator from the operator stack.2Pop the value stack twice, getting two operands.3Apply the operator to the operands, in the correct order.4Push the result onto the value stack.2Push thisOp onto the operator stack.
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.
W tym momencie stos operatora powinien być pusty, a stos wartości powinien zawierać tylko jedną wartość, co jest wynikiem końcowym.
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}elseif(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.
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 =newHashMap<String,Object>();
vars.put("x",10);
vars.put("y",20);ExecutableStatement statement =(ExecutableStatement) MVEL.compileExpression(expressionStr);Object result = MVEL.executeExpression(statement, vars);
ExprEvaluator util =newExprEvaluator();IExpr result = util.evaluate("10-40");System.out.println(result.toString());// -> "-30"
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
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.
publicstaticdouble eval(finalString str){returnnewObject(){int pos =-1, ch;void nextChar(){
ch =(++pos < str.length())? str.charAt(pos):-1;}boolean eat(int charToEat){while(ch ==' ') nextChar();if(ch == charToEat){
nextChar();returntrue;}returnfalse;}double parse(){
nextChar();double x = parseExpression();if(pos < str.length())thrownewRuntimeException("Unexpected: "+(char)ch);return x;}// Grammar:// expression = term | expression `+` term | expression `-` term// term = factor | term `*` factor | term `/` factor// factor = `+` factor | `-` factor | `(` expression `)`// | number | functionName factor | factor `^` factordouble parseExpression(){double x = parseTerm();for(;;){if(eat('+')) x += parseTerm();// additionelseif(eat('-')) x -= parseTerm();// subtractionelsereturn x;}}double parseTerm(){double x = parseFactor();for(;;){if(eat('*')) x *= parseFactor();// multiplicationelseif(eat('/')) x /= parseFactor();// divisionelseif(eat('^')) x =Math.pow(x, parseFactor());//exponentiation -> Moved in to here. So the problem is fixedelsereturn x;}}double parseFactor(){if(eat('+'))return parseFactor();// unary plusif(eat('-'))return-parseFactor();// unary minusdouble x;int startPos =this.pos;if(eat('(')){// parentheses
x = parseExpression();
eat(')');}elseif((ch >='0'&& ch <='9')|| ch =='.'){// numberswhile((ch >='0'&& ch <='9')|| ch =='.') nextChar();
x =Double.parseDouble(str.substring(startPos,this.pos));}elseif(ch >='a'&& ch <='z'){// functionswhile(ch >='a'&& ch <='z') nextChar();String func = str.substring(startPos,this.pos);
x = parseFactor();if(func.equals("sqrt")) x =Math.sqrt(x);elseif(func.equals("sin")) x =Math.sin(Math.toRadians(x));elseif(func.equals("cos")) x =Math.cos(Math.toRadians(x));elseif(func.equals("tan")) x =Math.tan(Math.toRadians(x));elsethrownewRuntimeException("Unknown function: "+ func);}else{thrownewRuntimeException("Unexpected: "+(char)ch);}//if (eat('^')) x = Math.pow(x, parseFactor()); // exponentiation -> This is causing a bit of problemreturn x;}}.parse();}
-2^2 = -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
packageExpressionCalculator.expressioncalculator;import java.text.DecimalFormat;import java.util.Scanner;publicclassExpressionCalculator{privatestaticString addSpaces(String exp){//Add space padding to operands.//https://regex101.com/r/sJ9gM7/73
exp = exp.replaceAll("(?<=[0-9()])[\\/]"," / ");
exp = exp.replaceAll("(?<=[0-9()])[\\^]"," ^ ");
exp = exp.replaceAll("(?<=[0-9()])[\\*]"," * ");
exp = exp.replaceAll("(?<=[0-9()])[+]"," + ");
exp = exp.replaceAll("(?<=[0-9()])[-]"," - ");//Keep replacing double spaces with single spaces until your string is properly formatted/*while(exp.indexOf(" ") != -1){
exp = exp.replace(" ", " ");
}*/
exp = exp.replaceAll(" {2,}"," ");return exp;}publicstaticDouble evaluate(String expr){DecimalFormat df =newDecimalFormat("#.####");//Format the expression properly before performing operationsString expression = addSpaces(expr);try{//We will evaluate using rule BDMAS, i.e. brackets, division, power, multiplication, addition and//subtraction will be processed in following orderint indexClose = expression.indexOf(")");int indexOpen =-1;if(indexClose !=-1){String substring = expression.substring(0, indexClose);
indexOpen = substring.lastIndexOf("(");
substring = substring.substring(indexOpen +1).trim();if(indexOpen !=-1&& indexClose !=-1){Double result = evaluate(substring);
expression = expression.substring(0, indexOpen).trim()+" "+ result +" "+ expression.substring(indexClose +1).trim();return evaluate(expression.trim());}}String operation ="";if(expression.indexOf(" / ")!=-1){
operation ="/";}elseif(expression.indexOf(" ^ ")!=-1){
operation ="^";}elseif(expression.indexOf(" * ")!=-1){
operation ="*";}elseif(expression.indexOf(" + ")!=-1){
operation ="+";}elseif(expression.indexOf(" - ")!=-1){//Avoid negative numbers
operation ="-";}else{returnDouble.parseDouble(expression);}int index = expression.indexOf(operation);if(index !=-1){
indexOpen = expression.lastIndexOf(" ", index -2);
indexOpen =(indexOpen ==-1)?0:indexOpen;
indexClose = expression.indexOf(" ", index +2);
indexClose =(indexClose ==-1)?expression.length():indexClose;if(indexOpen !=-1&& indexClose !=-1){Double lhs =Double.parseDouble(expression.substring(indexOpen, index));Double rhs =Double.parseDouble(expression.substring(index +2, indexClose));Double result =null;switch(operation){case"/"://Prevent divide by 0 exception.if(rhs ==0){returnnull;}
result = lhs / rhs;break;case"^":
result =Math.pow(lhs, rhs);break;case"*":
result = lhs * rhs;break;case"-":
result = lhs - rhs;break;case"+":
result = lhs + rhs;break;default:break;}if(indexClose == expression.length()){
expression = expression.substring(0, indexOpen)+" "+ result +" "+ expression.substring(indexClose);}else{
expression = expression.substring(0, indexOpen)+" "+ result +" "+ expression.substring(indexClose +1);}returnDouble.valueOf(df.format(evaluate(expression.trim())));}}}catch(Exception exp){
exp.printStackTrace();}return0.0;}publicstaticvoid main(String args[]){Scanner scanner =newScanner(System.in);System.out.print("Enter an Mathematical Expression to Evaluate: ");String input = scanner.nextLine();System.out.println(evaluate(input));}
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
Możliwe jest przekonwertowanie dowolnego ciągu wyrażeniowego w notacji infiksowej na notację postfiksową za pomocą algorytmu Djikstry . Wynik algorytmu może następnie służyć jako dane wejściowe do algorytmu Postfiks z zwraca wynik wyrażenia.
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 -
Odpowiedzi:
Dzięki JDK1.6 możesz korzystać z wbudowanego silnika JavaScript.
źródło
return (Double) engine.eval(foo);
new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");
- zapisze plik przez JavaScript w (domyślnie) bieżącym katalogu programuNapisałem tę
eval
metodę wyrażeń arytmetycznych, aby odpowiedzieć na to pytanie. Wykonuje dodawanie, odejmowanie, mnożenie, dzielenie, potęgowanie (za pomocą^
symbolu) i kilka podstawowych funkcji, takich jaksqrt
. Obsługuje grupowanie za pomocą(
...)
i poprawia pierwszeństwo operatora oraz zasady asocjatywności .Przykład:
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
eval
metody, takiej jak aMap<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:
Teraz zmień wszystkie metody, które zwracają
double
s, więc zamiast tego zwracają instancję tego interfejsu. Świetnie nadaje się do tego składnia lambda Java 8. Przykład jednej ze zmienionych metod:To buduje rekurencyjne drzewo
Expression
obiektó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:Różne typy danych:
Zamiast tego
double
możesz zmienić ewaluator, aby użył czegoś mocniejszegoBigDecimal
, 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!
źródło
double x = parseTerm();
ocenia lewego operatora, po czymfor (;;) {...}
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.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.
źródło
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
2 - Argumenty i stałe zdefiniowane przez użytkownika
3 - Funkcje zdefiniowane przez użytkownika
4 - Iteracja
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
źródło
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.
źródło
Możesz także wypróbować interpreter BeanShell :
źródło
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
i w Oracle
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
Oczywiście można rozszerzyć powyższy kod, aby obsługiwał wiele obliczeń jednocześnie.
źródło
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.
Użyj silnika javascript osadzonego w JDK:
źródło
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:
Przeczytaj więcej zwięzłych przykładów SPEL tutaj i pełne dokumenty tutaj
źródło
jeśli zamierzamy go wdrożyć, możemy użyć następującego algorytmu:
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.2.5 Operator (nazwij to thisOp):
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.
W tym momencie stos operatora powinien być pusty, a stos wartości powinien zawierać tylko jedną wartość, co jest wynikiem końcowym.
źródło
To kolejna interesująca alternatywa https://github.com/Shy-Ta/expression-evaluator-demo
Użycie jest bardzo proste i wykonuje zadanie, na przykład:
źródło
Wygląda na to, że JEP powinien wykonać to zadanie
źródło
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
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.
źródło
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
MVEL
wykonuje ocenę wyrażeń w środowisku wykonawczym, możemy napisać kod Java,String
aby uzyskać w tym miejscu ocenę.źródło
Możesz rzucić okiem na środowisko Symja :
Zwróć uwagę, że można ocenić zdecydowanie bardziej złożone wyrażenia:
źródło
Wypróbuj następujący przykładowy kod, używając silnika JavaScript JDK1.6 z obsługą wstrzykiwania kodu.
źródło
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.
źródło
-2^2 = -4
jest 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^2
należ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 .}
źródło
Co powiesz na coś takiego:
i zrobić to samo dla każdego innego operatora matematycznego odpowiednio.
źródło
Możliwe jest przekonwertowanie dowolnego ciągu wyrażeniowego w notacji infiksowej na notację postfiksową za pomocą algorytmu Djikstry . Wynik algorytmu może następnie służyć jako dane wejściowe do algorytmu Postfiks z zwraca wynik wyrażenia.
Napisałem tutaj o tym artykuł z implementacją w Javie
źródło
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 .
źródło
Klasa Java, która może oceniać wyrażenia matematyczne:
źródło
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 -
źródło
źródło