Кажется, там есть большая проблема; Он выполняет скрипт, а не оценивает выражение. Чтобы было ясно, engine.eval ("8; 40 + 2"), выводит 42! Если вам нужен синтаксический анализатор выражений, который также проверяет синтаксис, я только что закончил (потому что не нашел ничего, что соответствовало бы моим потребностям): Javaluator .
Жан-Марк Астесана
4
В качестве примечания: если вам нужно использовать результат этого выражения в другом месте вашего кода, вы можете типизировать результат в Double, как return (Double) engine.eval(foo);
показано ниже
38
Примечание по безопасности: Никогда не используйте это в контексте сервера с пользовательским вводом. Выполненный JavaScript может получить доступ ко всем классам Java и, таким образом, захватить ваше приложение без ограничений.
Boann
3
@Boann, я прошу вас дать мне ссылку на то, что вы сказали. (Чтобы быть уверенным на 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();");- записывает файл через JavaScript в (по умолчанию) текущую директорию программы
Boann
236
Я написал этот evalметод для арифметических выражений, чтобы ответить на этот вопрос. Это делает сложение, вычитание, умножение, деление, возведение в степень (используя ^символ), и несколько основных функций, как sqrt. Он поддерживает группировку с использованием (... ), и он корректно определяет правила приоритета операторов и ассоциативности .
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();}
Анализатор является анализатором рекурсивного спуска , поэтому внутренне использует отдельные методы синтаксического анализа для каждого уровня приоритета оператора в своей грамматике. Я держал его коротким , так что легко изменить, но вот некоторые идеи , которые вы могли бы хотеть , чтобы расширить его:
Переменные:
Бит синтаксического анализатора, который читает имена для функций, можно легко изменить для обработки пользовательских переменных, просматривая имена в таблице переменных, передаваемой evalметоду, например a Map<String,Double> variables.
Отдельная компиляция и оценка:
Что если, добавив поддержку переменных, вы захотите вычислять одно и то же выражение миллионы раз с измененными переменными, не анализируя его каждый раз? Это возможно. Сначала определите интерфейс, который будет использоваться для оценки предварительно скомпилированного выражения:
Теперь измените все методы, которые возвращают doubles, поэтому вместо этого они возвращают экземпляр этого интерфейса. Лямбда-синтаксис Java 8 отлично подходит для этого. Пример одного из измененных методов:
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;}}}
Это создает рекурсивное дерево Expressionобъектов, представляющих скомпилированное выражение ( абстрактное синтаксическое дерево ). Затем вы можете скомпилировать его один раз и повторно оценить с различными значениями:
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());}}
Различные типы данных:
Вместо этого doubleвы можете изменить оценщик, чтобы использовать что-то более мощное, например BigDecimalкласс, реализующий комплексные числа или рациональные числа (дроби). Вы даже можете использовать Object, позволяя смешивать типы данных в выражениях, как настоящий язык программирования. :)
Весь код в этом ответе опубликован в открытом доступе . Радоваться, веселиться!
Хороший алгоритм, исходя из которого мне удалось реализовать и логические операторы. Мы создали отдельные классы для функций, чтобы оценить функцию, поэтому, как и ваша идея переменных, я создаю карту с функциями и присматриваюсь к имени функции. Каждая функция реализует интерфейс с методом eval (T rightOperator, T leftOperator), поэтому в любое время мы можем добавлять функции без изменения кода алгоритма. И это хорошая идея, чтобы заставить его работать с универсальными типами. Спасибо вам!
Василе Борс
1
Можете ли вы объяснить логику этого алгоритма?
iYonatan
1
Я пытаюсь дать описание того, что я понимаю, из кода, написанного Boann, и примеров, описанных вики. Логика этого алгоритма начинается с правил порядка работы. 1. знак оператора | оценка переменных | вызов функции | скобки (подвыражения); 2. возведение в степень; 3. умножение, деление; 4. сложение, вычитание;
Василе Борс
1
Методы алгоритма делятся для каждого уровня порядка операций следующим образом: parseFactor = 1. знак оператора | оценка переменных | вызов функции | скобки (подвыражения); 2. возведение в степень; parseTerms = 3. умножение, деление; parseExpression = 4. сложение, вычитание. Алгоритм, вызов методов в обратном порядке (parseExpression -> parseTerms -> parseFactor -> parseExpression (для подвыражений)), но каждый метод в первой строке вызывает метод на следующий уровень, поэтому все методы порядка выполнения будут на самом деле нормальный порядок операций.
Василе Борс
1
Например, метод parseExpression double x = parseTerm(); оценивает левый оператор, после этого for (;;) {...}оценивает последовательные операции фактического уровня заказа (сложение, вычитание). Та же логика есть и в методе parseTerm. ParseFactor не имеет следующего уровня, поэтому существуют только оценки методов / переменных или, в случае парантеза, - оценка подвыражения. boolean eat(int charToEat)Проверка метода равенство текущего символа курсора с характером charToEat, если равно возвращение истинный и перемещает курсор к следующему символу, я использую имя «принять» для него.
Василий Борс
34
Правильный способ решить это с помощью лексера и парсера . Вы можете написать их простые версии, или на этих страницах также есть ссылки на лексеры и парсеры Java.
Создание парсера с рекурсивным спуском - действительно хорошее упражнение в обучении.
Для моего университетского проекта я искал парсер / оценщик, поддерживающий как базовые формулы, так и более сложные уравнения (особенно итерированные операторы). Я нашел очень хорошую библиотеку с открытым исходным кодом для JAVA и .NET, которая называется mXparser. Я приведу несколько примеров, чтобы немного разобраться в синтаксисе, для дальнейших инструкций, пожалуйста, посетите веб-сайт проекта (особенно раздел учебника).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Определяемые пользователем аргументы и константы
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Пользовательские функции
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 - Итерация
Expression e =newExpression("sum( i, 1, 100, sin(i) )");double v = e.calculate()
Найден недавно - если вы хотите попробовать синтаксис (и посмотреть расширенный вариант использования), вы можете скачать приложение Scalar Calculator , которое работает на mXparser.
Пока что это лучшая математическая библиотека из всех; простой в использовании, простой в использовании и расширяемый. Определенно должен быть топ ответ.
Я обнаружил, что mXparser не может определить недопустимую формулу, например, «0/0» получит результат как «0». Как я могу решить эту проблему?
lulijun
Только что нашел решение, expression.setSlientMode ()
lulijun
20
ЗДЕСЬ есть еще одна библиотека с открытым исходным кодом на GitHub под названием EvalEx.
В отличие от движка JavaScript эта библиотека ориентирована только на оценку математических выражений. Кроме того, библиотека расширяема и поддерживает использование логических операторов, а также скобок.
Это нормально, но терпит неудачу, когда мы пытаемся умножить значения, кратные 5 или 10, например, 65 * 6 приводит к 3.9E + 2 ...
paarth batra
.Но есть способ исправить это, приведя его к int, то есть к int output = (int) 65 * 6, теперь это приведет к 390
paarth batra
1
Для пояснения, это не проблема библиотеки, а проблема с представлением чисел в виде значений с плавающей запятой.
DavidBittner
Эта библиотека действительно хороша. @paarth batra Приведение к int удалит все десятичные точки. Используйте это вместо: expression.eval (). ToPlainString ();
einUsername
15
Вы также можете попробовать интерпретатор BeanShell :
Вы можете легко оценить выражения, если ваше Java-приложение уже обращается к базе данных, без использования других JAR-файлов.
Некоторые базы данных требуют, чтобы вы использовали фиктивную таблицу (например, «двойную» таблицу Oracle), а другие позволят вам оценивать выражения, не «выбирая» из какой-либо таблицы.
Например, в Sql Server или Sqlite
select (((12.10+12.0))/233.0) amount
и в Oracle
select (((12.10+12.0))/233.0) amount from dual;
Преимущество использования БД в том, что вы можете вычислять много выражений одновременно. Кроме того, большинство БД позволит вам использовать очень сложные выражения, а также будет иметь ряд дополнительных функций, которые можно вызывать по мере необходимости.
Однако производительность может снизиться, если многие отдельные выражения необходимо оценивать по отдельности, особенно если БД находится на сетевом сервере.
Следующее в некоторой степени решает проблему производительности, используя базу данных Sqlite в памяти.
Это зависит от того, для чего вы используете БД. Если вы хотите быть уверены, вы можете легко создать пустую базу данных sqlite, специально для оценки математики.
Позволяет для сценариев, которые включают ссылки на объекты 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);
Пожалуйста, суммируйте информацию из статьи, если ссылка на нее не работает.
DJClayworth
Я обновил ответ, включив в него соответствующие фрагменты из статьи
Брэд Паркс
1
на практике JEXL работает медленно (использует интроспекцию bean-компонентов), имеет проблемы с производительностью при многопоточности (глобальный кеш)
Nishi
Полезно знать @Nishi! - Мой вариант использования был для отладки вещей в реальных средах, но не являлся частью обычного развернутого приложения.
Брэд Паркс
10
Другой способ - использовать Spring Expression Language или SpEL, который делает намного больше вместе с оценкой математических выражений, поэтому может быть немного излишним. Вам не нужно использовать Spring Framework, чтобы использовать эту библиотеку выражений, поскольку она автономна. Копирование примеров из документации SpEL:
если мы собираемся реализовать это, то мы можем использовать следующий алгоритм:
Пока еще есть токены для чтения,
1.1 Получить следующий токен 1.2 Если токен:
1.2.1 Число: поместите его в стек значений.
1.2.2 Переменная: получите ее значение и поместите в стек значений.
1.2.3 Левая скобка: вставьте ее в стек оператора.
1.2.4 Правая скобка:
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 Оператор (назовите это 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.
Пока стек операторов не пуст, 1 вытолкните оператор из стека операторов. 2 Дважды вытолкните стек значений, получив два операнда. 3 Примените оператор к операндам в правильном порядке. 4 Вставьте результат в стек значений.
На этом этапе стек операторов должен быть пустым, а в стеке значений должно быть только одно значение, что является конечным результатом.
Я думаю, что когда бы вы ни делали это, это будет включать в себя много условных утверждений. Но для отдельных операций, как в ваших примерах, вы можете ограничить его до 4, если операторы с чем-то вроде
String math ="1+4";if(math.split("+").length ==2){//do calculation}elseif(math.split("-").length ==2){//do calculation}...
Это становится намного сложнее, когда вы хотите иметь дело с несколькими операциями, такими как «4 + 5 * 6».
Если вы пытаетесь построить калькулятор, я бы предпочел передавать каждую часть вычисления отдельно (каждый номер или оператор), а не как одну строку.
Это становится намного сложнее, как только вам приходится иметь дело с несколькими операциями, приоритетами операторов, круглыми скобками, ... фактически всем, что характеризует реальное арифметическое выражение. Вы не можете получить там, начиная с этой техники.
маркиз Лорн
4
Уже слишком поздно, чтобы ответить, но я столкнулся с той же ситуацией, чтобы оценить выражение в Java, это может помочь кому-то
MVELВыполняет оценку выражений во время выполнения, мы можем написать Java-код, Stringчтобы оценить его в этом.
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"
Обратите внимание, что определенно более сложные выражения могут быть оценены:
// 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
Это фактически дополняет ответ, данный @Boann. Имеется небольшая ошибка, которая приводит к тому, что «-2 ^ 2» дает ошибочный результат -4.0. Проблемой для этого является тот момент, когда возведение в степень оценивается по-своему. Просто переместите возведение в блок parseTerm (), и все будет в порядке. Посмотрите на приведенный ниже ответ , который @ Boann слегка изменил. Модификация есть в комментариях.
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 = -4на самом деле нормально, а не ошибка. Это группируется как -(2^2). Попробуйте это на Desmos, например. Ваш код на самом деле вносит несколько ошибок. Во-первых, ^больше не группируются справа налево. Другими словами, 2^3^2предполагается, что сгруппировать как, 2^(3^2)потому что ^это право-ассоциативно, но ваши изменения делают это сгруппировать как (2^3)^2. Второе - это то, что ^должен иметь более высокий приоритет, чем *и /, но ваши модификации относятся к нему одинаково. Смотрите ideone.com/iN2mMa .
Radiodef
Итак, что вы предлагаете, так это то, что возведение в степень лучше хранить там, где оно было, не так ли?
Ромео Сьерра
Да, это то, что я предлагаю.
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));}
Вы должны прочитать о написании эффективных синтаксических анализаторов выражений. В этом есть методология информатики. Взгляните на ANTLR, например. Если вы хорошо подумаете о том, что написали, то увидите, что такие вещи, как (a + b / -c) * (e / f), не будут работать с вашей идеей, или код будет очень грязным и неэффективным.
Даниил Нуриев
2
Можно преобразовать любую строку выражения в инфиксной записи в постфиксную запись с помощью алгоритма шунтирования ярда Джикстры . Результат алгоритма может затем служить входом для постфиксного алгоритма. с возвращением результата выражения.
Не правильно обрабатывает приоритет оператора. Есть стандартные способы сделать это, и это не один из них.
Маркиз Лорн
EJP, можете ли вы указать, где есть проблема с приоритетом оператора? Я полностью согласен с тем, что это не стандартный способ сделать это. стандартные способы уже упоминались в предыдущих постах, идея заключалась в том, чтобы показать другой способ сделать это.
Efi G
2
Внешняя библиотека, такая как RHINO или NASHORN, может быть использована для запуска JavaScript. И javascript может оценить простую формулу без разбора строки. Не влияет на производительность, если код написан хорошо. Ниже приведен пример с RHINO -
Ответы:
С JDK1.6 вы можете использовать встроенный движок Javascript.
источник
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();");
- записывает файл через JavaScript в (по умолчанию) текущую директорию программыЯ написал этот
eval
метод для арифметических выражений, чтобы ответить на этот вопрос. Это делает сложение, вычитание, умножение, деление, возведение в степень (используя^
символ), и несколько основных функций, какsqrt
. Он поддерживает группировку с использованием(
...)
, и он корректно определяет правила приоритета операторов и ассоциативности .Пример:
Выход: 7,5 (что правильно)
Анализатор является анализатором рекурсивного спуска , поэтому внутренне использует отдельные методы синтаксического анализа для каждого уровня приоритета оператора в своей грамматике. Я держал его коротким , так что легко изменить, но вот некоторые идеи , которые вы могли бы хотеть , чтобы расширить его:
Переменные:
Бит синтаксического анализатора, который читает имена для функций, можно легко изменить для обработки пользовательских переменных, просматривая имена в таблице переменных, передаваемой
eval
методу, например aMap<String,Double> variables
.Отдельная компиляция и оценка:
Что если, добавив поддержку переменных, вы захотите вычислять одно и то же выражение миллионы раз с измененными переменными, не анализируя его каждый раз? Это возможно. Сначала определите интерфейс, который будет использоваться для оценки предварительно скомпилированного выражения:
Теперь измените все методы, которые возвращают
double
s, поэтому вместо этого они возвращают экземпляр этого интерфейса. Лямбда-синтаксис Java 8 отлично подходит для этого. Пример одного из измененных методов:Это создает рекурсивное дерево
Expression
объектов, представляющих скомпилированное выражение ( абстрактное синтаксическое дерево ). Затем вы можете скомпилировать его один раз и повторно оценить с различными значениями:Различные типы данных:
Вместо этого
double
вы можете изменить оценщик, чтобы использовать что-то более мощное, напримерBigDecimal
класс, реализующий комплексные числа или рациональные числа (дроби). Вы даже можете использоватьObject
, позволяя смешивать типы данных в выражениях, как настоящий язык программирования. :)Весь код в этом ответе опубликован в открытом доступе . Радоваться, веселиться!
источник
double x = parseTerm();
оценивает левый оператор, после этогоfor (;;) {...}
оценивает последовательные операции фактического уровня заказа (сложение, вычитание). Та же логика есть и в методе parseTerm. ParseFactor не имеет следующего уровня, поэтому существуют только оценки методов / переменных или, в случае парантеза, - оценка подвыражения.boolean eat(int charToEat)
Проверка метода равенство текущего символа курсора с характером charToEat, если равно возвращение истинный и перемещает курсор к следующему символу, я использую имя «принять» для него.Правильный способ решить это с помощью лексера и парсера . Вы можете написать их простые версии, или на этих страницах также есть ссылки на лексеры и парсеры Java.
Создание парсера с рекурсивным спуском - действительно хорошее упражнение в обучении.
источник
Для моего университетского проекта я искал парсер / оценщик, поддерживающий как базовые формулы, так и более сложные уравнения (особенно итерированные операторы). Я нашел очень хорошую библиотеку с открытым исходным кодом для JAVA и .NET, которая называется mXparser. Я приведу несколько примеров, чтобы немного разобраться в синтаксисе, для дальнейших инструкций, пожалуйста, посетите веб-сайт проекта (особенно раздел учебника).
https://mathparser.org/
https://mathparser.org/mxparser-tutorial/
https://mathparser.org/api/
И несколько примеров
1 - Простая фурмула
2 - Определяемые пользователем аргументы и константы
3 - Пользовательские функции
4 - Итерация
Найден недавно - если вы хотите попробовать синтаксис (и посмотреть расширенный вариант использования), вы можете скачать приложение Scalar Calculator , которое работает на mXparser.
С уважением
источник
ЗДЕСЬ есть еще одна библиотека с открытым исходным кодом на GitHub под названием EvalEx.
В отличие от движка JavaScript эта библиотека ориентирована только на оценку математических выражений. Кроме того, библиотека расширяема и поддерживает использование логических операторов, а также скобок.
источник
Вы также можете попробовать интерпретатор BeanShell :
источник
Вы можете легко оценить выражения, если ваше Java-приложение уже обращается к базе данных, без использования других JAR-файлов.
Некоторые базы данных требуют, чтобы вы использовали фиктивную таблицу (например, «двойную» таблицу Oracle), а другие позволят вам оценивать выражения, не «выбирая» из какой-либо таблицы.
Например, в Sql Server или Sqlite
и в Oracle
Преимущество использования БД в том, что вы можете вычислять много выражений одновременно. Кроме того, большинство БД позволит вам использовать очень сложные выражения, а также будет иметь ряд дополнительных функций, которые можно вызывать по мере необходимости.
Однако производительность может снизиться, если многие отдельные выражения необходимо оценивать по отдельности, особенно если БД находится на сетевом сервере.
Следующее в некоторой степени решает проблему производительности, используя базу данных Sqlite в памяти.
Вот полный рабочий пример на Java
Конечно, вы можете расширить приведенный выше код для одновременной обработки нескольких вычислений.
источник
В этой статье рассматриваются различные подходы. Вот 2 ключевых подхода, упомянутых в статье:
JEXL от Apache
Позволяет для сценариев, которые включают ссылки на объекты Java.
Используйте движок JavaScript, встроенный в JDK:
источник
Другой способ - использовать Spring Expression Language или SpEL, который делает намного больше вместе с оценкой математических выражений, поэтому может быть немного излишним. Вам не нужно использовать Spring Framework, чтобы использовать эту библиотеку выражений, поскольку она автономна. Копирование примеров из документации SpEL:
Прочитайте более краткие примеры SpEL здесь и полные документы здесь
источник
если мы собираемся реализовать это, то мы можем использовать следующий алгоритм:
Пока еще есть токены для чтения,
1.1 Получить следующий токен 1.2 Если токен:
1.2.1 Число: поместите его в стек значений.
1.2.2 Переменная: получите ее значение и поместите в стек значений.
1.2.3 Левая скобка: вставьте ее в стек оператора.
1.2.4 Правая скобка:
1.2.5 Оператор (назовите это thisOp):
Пока стек операторов не пуст, 1 вытолкните оператор из стека операторов. 2 Дважды вытолкните стек значений, получив два операнда. 3 Примените оператор к операндам в правильном порядке. 4 Вставьте результат в стек значений.
На этом этапе стек операторов должен быть пустым, а в стеке значений должно быть только одно значение, что является конечным результатом.
источник
Это еще одна интересная альтернатива https://github.com/Shy-Ta/expression-evaluator-demo
Использование очень простое и выполняет свою работу, например:
источник
Кажется, что JEP должен сделать работу
источник
Я думаю, что когда бы вы ни делали это, это будет включать в себя много условных утверждений. Но для отдельных операций, как в ваших примерах, вы можете ограничить его до 4, если операторы с чем-то вроде
Это становится намного сложнее, когда вы хотите иметь дело с несколькими операциями, такими как «4 + 5 * 6».
Если вы пытаетесь построить калькулятор, я бы предпочел передавать каждую часть вычисления отдельно (каждый номер или оператор), а не как одну строку.
источник
Уже слишком поздно, чтобы ответить, но я столкнулся с той же ситуацией, чтобы оценить выражение в Java, это может помочь кому-то
MVEL
Выполняет оценку выражений во время выполнения, мы можем написать Java-код,String
чтобы оценить его в этом.источник
Вы можете взглянуть на платформу Symja :
Обратите внимание, что определенно более сложные выражения могут быть оценены:
источник
Попробуйте следующий пример кода, используя Javascript движок JDK1.6 с обработкой внедрения кода.
источник
Это фактически дополняет ответ, данный @Boann. Имеется небольшая ошибка, которая приводит к тому, что «-2 ^ 2» дает ошибочный результат -4.0. Проблемой для этого является тот момент, когда возведение в степень оценивается по-своему. Просто переместите возведение в блок parseTerm (), и все будет в порядке. Посмотрите на приведенный ниже ответ , который @ Boann слегка изменил. Модификация есть в комментариях.
источник
-2^2 = -4
на самом деле нормально, а не ошибка. Это группируется как-(2^2)
. Попробуйте это на Desmos, например. Ваш код на самом деле вносит несколько ошибок. Во-первых,^
больше не группируются справа налево. Другими словами,2^3^2
предполагается, что сгруппировать как,2^(3^2)
потому что^
это право-ассоциативно, но ваши изменения делают это сгруппировать как(2^3)^2
. Второе - это то, что^
должен иметь более высокий приоритет, чем*
и/
, но ваши модификации относятся к нему одинаково. Смотрите ideone.com/iN2mMa .}
источник
Как насчет чего-то вроде этого:
и сделайте то же самое для каждого другого математического оператора соответственно ..
источник
Можно преобразовать любую строку выражения в инфиксной записи в постфиксную запись с помощью алгоритма шунтирования ярда Джикстры . Результат алгоритма может затем служить входом для постфиксного алгоритма. с возвращением результата выражения.
Я написал статью об этом здесь, с реализацией в Java
источник
Еще один вариант: https://github.com/stefanhaustein/expressionparser
Я реализовал это, чтобы иметь простой, но гибкий вариант, позволяющий:
TreeBuilder, связанный выше, является частью демонстрационного пакета CAS, который выполняет символическое создание . Существует также пример интерпретатора BASIC, и я начал создавать интерпретатор TypeScript, используя его.
источник
Класс Java, который может оценивать математические выражения:
источник
Внешняя библиотека, такая как RHINO или NASHORN, может быть использована для запуска JavaScript. И javascript может оценить простую формулу без разбора строки. Не влияет на производительность, если код написан хорошо. Ниже приведен пример с RHINO -
источник
источник