Recentemente, escrevi um analisador de expressões matemáticas chamado exp4j, que foi lançado sob a licença apache, você pode conferir aqui: objecthunter.net/exp4j
fasseg
2
Que tipos de expressões você permite? Apenas expressões de operador único? Parênteses são permitidos?
Parece que há um grande problema lá; Ele executa um script, não avalia uma expressão. Para ficar claro, engine.eval ("8; 40 + 2") gera 42! Se você deseja um analisador de expressão que também verifique a sintaxe, acabei de terminar um (porque não encontrei nada que atenda às minhas necessidades): Javaluator .
Jean-Marc Astesana
4
Como uma nota lateral, se você precisa usar o resultado desta em outros lugares expressão em seu código, você pode estereotipado o resultado para um casal assim: return (Double) engine.eval(foo);
Ben Visness
38
Nota de segurança: você nunca deve usá-lo em um contexto de servidor com entrada do usuário. O JavaScript executado pode acessar todas as classes Java e, assim, seqüestrar seu aplicativo sem limite.
Boann
3
@Boann, eu pedir-lhe para me dar uma referência sobre o que você disse (para ter certeza de 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();");- gravará um arquivo via JavaScript no diretório atual do programa
Boann
236
Eu escrevi esse evalmétodo para expressões aritméticas para responder a essa pergunta. Faz adição, subtração, multiplicação, divisão, exponenciação (usando o ^símbolo) e algumas funções básicas, como sqrt. Ele suporta o agrupamento usando (... )e corrige as regras de precedência e associatividade do operador .
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();}
O analisador é um analisador de descida recursiva ; portanto, usa internamente métodos de análise separados para cada nível de precedência do operador em sua gramática. Eu o mantive curto, para facilitar a modificação, mas aqui estão algumas idéias que você pode querer expandi-lo:
Variáveis:
O bit do analisador que lê os nomes das funções também pode ser facilmente alterado para manipular variáveis personalizadas, procurando nomes em uma tabela de variáveis passada para o evalmétodo, como a Map<String,Double> variables.
Compilação e avaliação separadas:
E se, tendo adicionado suporte para variáveis, você quisesse avaliar a mesma expressão milhões de vezes com variáveis alteradas, sem analisá-la toda vez? É possível. Primeiro, defina uma interface a ser usada para avaliar a expressão pré-compilada:
Agora altere todos os métodos que retornam doubles; portanto, eles retornam uma instância dessa interface. A sintaxe lambda do Java 8 funciona muito bem para isso. Exemplo de um dos métodos alterados:
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;}}}
Isso cria uma árvore recursiva de Expressionobjetos que representam a expressão compilada (uma árvore de sintaxe abstrata ). Em seguida, você pode compilá-lo uma vez e avaliá-lo repetidamente com valores diferentes:
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());}}
Tipos de dados diferentes:
Em vez de double, você pode alterar o avaliador para usar algo mais poderoso como BigDecimal, ou uma classe que implemente números complexos ou números racionais (frações). Você pode até usar Object, permitindo alguma mistura de tipos de dados em expressões, como uma linguagem de programação real. :)
Bom algoritmo, a partir dele consegui implementar operadores lógicos e. Criamos classes separadas para funções para avaliar uma função, assim como sua ideia de variáveis, eu crio um mapa com funções e cuidando do nome da função. Toda função implementa uma interface com um método eval (T rightOperator, T leftOperator), para que a qualquer momento possamos adicionar recursos sem alterar o código do algoritmo. E é uma boa ideia fazê-lo funcionar com tipos genéricos. Te agradece!
Vasile Bors
1
Você pode explicar a lógica por trás desse algoritmo?
IYonatan
1
Eu tento dar uma descrição do que entendo do código escrito por Boann e exemplos descritos no wiki. A lógica desse algoritmo a partir de regras de ordens de operação. 1. sinal de operador | avaliação de variáveis | chamada de função | parênteses (sub-expressões); 2. exponenciação; 3. multiplicação, divisão; 4. adição, subtração;
Vasile Bors
1
Os métodos de algoritmo são divididos para cada nível de ordem de operações, da seguinte forma: parseFactor = 1. sinal do operador | avaliação de variáveis | chamada de função | parênteses (sub-expressões); 2. exponenciação; parseTerms = 3. multiplicação, divisão; parseExpression = 4. adição, subtração. O algoritmo chama métodos na ordem inversa (parseExpression -> parseTerms -> parseFactor -> parseExpression (para subexpressões)), mas todos os métodos da primeira linha chamam o método para o próximo nível, para que todos os métodos da ordem de execução sejam ordem realmente normal de operações.
Vasile Bors
1
Por exemplo, o método parseExpression double x = parseTerm(); avalia o operador esquerdo, depois for (;;) {...}avalia as operações sucessivas do nível de pedido real (adição, subtração). A mesma lógica é e no método parseTerm. O parseFactor não possui próximo nível, portanto, existem apenas avaliações de métodos / variáveis ou, no caso de parênteses - avaliar subexpressão. O boolean eat(int charToEat)método verifica a igualdade do caractere atual do cursor com o caractere charToEat, se igual retornar true e mover o cursor para o próximo caractere, eu uso o nome 'accept' para ele.
Vasile Bors
34
A maneira correta de resolver isso é com um lexer e um analisador . Você mesmo pode escrever versões simples delas, ou essas páginas também possuem links para analisadores e analisadores Java.
Criar um analisador de descida recursiva é realmente um bom exercício de aprendizado.
Para o meu projeto universitário, eu estava procurando por um analisador / avaliador que suporta fórmulas básicas e equações mais complicadas (especialmente operadores iterados). Eu achei uma biblioteca de código aberto muito boa para JAVA e .NET chamada mXparser. Vou dar alguns exemplos para entender a sintaxe. Para obter mais instruções, visite o site do projeto (especialmente a seção do tutorial).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Argumentos e constantes definidas pelo usuário
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Funções definidas pelo usuário
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 - Iteração
Expression e =newExpression("sum( i, 1, 100, sin(i) )");double v = e.calculate()
Encontrado recentemente - caso queira experimentar a sintaxe (e veja o caso de uso avançado), você pode fazer o download do aplicativo Calculadora Escalar, desenvolvido com mXparser.
Até agora, esta é a melhor biblioteca de matemática existente; Simples de iniciar, fácil de usar e extensível. Definitivamente deve ser a melhor resposta.
Descobri que o mXparser não pode identificar a fórmula ilegal; por exemplo, '0/0' obterá um resultado como '0'. Como posso resolver este problema?
lulijun
Acabei de encontrar a solução, expression.setSlientMode ()
lulijun
20
O HERE é outra biblioteca de código aberto no GitHub chamada EvalEx.
Diferentemente do mecanismo JavaScript, esta biblioteca é focada na avaliação apenas de expressões matemáticas. Além disso, a biblioteca é extensível e suporta o uso de operadores booleanos, bem como parênteses.
Este é ok, mas falha quando tentamos valores multiplicam de múltiplos de 5 ou 10, por exemplo, 65 * 6 resultados em 3.9E + 2 ...
batra paarth
.Mas existe uma maneira de fixar este, lançando-a int saída ou seja int = (int) 65 * 6 que irá resultar agora 390
Batra paarth
1
Para esclarecer, isso não é um problema da biblioteca, mas um problema com a representação de números como valores de ponto flutuante.
DavidBittner
Esta biblioteca é realmente boa. @paarth batra A transmissão para int removerá todos os pontos decimais. Use isso: expression.eval (). ToPlainString ();
Você pode avaliar expressões facilmente se seu aplicativo Java já acessar um banco de dados, sem usar outros JARs.
Alguns bancos de dados exigem o uso de uma tabela fictícia (por exemplo, a tabela "dupla" do Oracle) e outros permitem avaliar expressões sem "selecionar" em qualquer tabela.
Por exemplo, no Sql Server ou Sqlite
select (((12.10+12.0))/233.0) amount
e no Oracle
select (((12.10+12.0))/233.0) amount from dual;
A vantagem de usar um banco de dados é que você pode avaliar muitas expressões ao mesmo tempo. Além disso, a maioria dos bancos de dados permitirá que você use expressões altamente complexas e também terá várias funções extras que podem ser chamadas conforme necessário.
No entanto, o desempenho pode sofrer se muitas expressões únicas precisarem ser avaliadas individualmente, principalmente quando o banco de dados estiver localizado em um servidor de rede.
A seguir, aborda o problema de desempenho, até certo ponto, usando um banco de dados Sqlite na memória.
Depende do que você usa o banco de dados. Se você quiser ter certeza, poderá criar facilmente um banco de dados sqlite vazio, especificamente para a avaliação matemática.
Permite scripts que incluem referências a objetos 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);
Resuma as informações do artigo, caso o link esteja quebrado.
quer
Atualizei a resposta para incluir bits relevantes do artigo
Brad Parks
1
na prática, o JEXL é lento (usa introspecção de beans), apresenta problemas de desempenho com multithreading (cache global)
Nishi
É bom saber @Nishi! - Meu caso de uso era para depurar coisas em ambientes ativos, mas não fazia parte do aplicativo implantado normal.
Brad Parks
10
Outra maneira é usar o Spring Expression Language ou SpEL, que faz muito mais junto com a avaliação de expressões matemáticas, portanto, talvez um pouco exagero. Você não precisa usar a estrutura Spring para usar esta biblioteca de expressões, pois é independente. Copiando exemplos da documentação do SpEL:
se vamos implementá-lo, podemos usar o algoritmo abaixo: -
Embora ainda haja tokens para serem lidos,
1.1 Obtenha o próximo token. 1.2 Se o token for:
1.2.1 Um número: empurre-o para a pilha de valores.
1.2.2 Uma variável: obtenha seu valor e empurre para a pilha de valores.
1.2.3 Um parêntese esquerdo: empurre-o para a pilha do operador.
1.2.4 Um parêntese direito:
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 Um operador (chame-o 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.
Enquanto a pilha do operador não estiver vazia, 1 Retire o operador da pilha do operador. 2 Coloque a pilha de valores duas vezes, obtendo dois operandos. 3 Aplique o operador aos operandos, na ordem correta. 4 Empurre o resultado na pilha de valores.
Nesse ponto, a pilha do operador deve estar vazia e a pilha de valores deve ter apenas um valor, que é o resultado final.
Eu acho que de qualquer maneira que você faça isso, isso envolverá muitas declarações condicionais. Porém, para operações únicas, como nos seus exemplos, você pode limitar a 4 se declarações com algo como
String math ="1+4";if(math.split("+").length ==2){//do calculation}elseif(math.split("-").length ==2){//do calculation}...
Fica muito mais complicado quando você deseja lidar com várias operações como "4 + 5 * 6".
Se você está tentando construir uma calculadora, seria melhor passar cada seção do cálculo separadamente (cada número ou operador), e não como uma única string.
Fica muito mais complicado assim que você precisa lidar com várias operações, precedência do operador, parênteses, ... de fato, qualquer coisa que caracterize uma expressão aritmética real. Você não pode chegar lá a partir desta técnica.
Marquês de Lorne
4
É tarde demais para responder, mas me deparei com a mesma situação para avaliar a expressão em java, isso pode ajudar alguém
MVELfaz avaliação de expressões em tempo de execução, podemos escrever um código java Stringpara que seja avaliado nisso.
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"
Observe que expressões definitivamente mais complexas podem ser avaliadas:
// 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
Na verdade, isso complementa a resposta dada por @Boann. Ele tem um pequeno erro que faz com que "-2 ^ 2" dê um resultado incorreto de -4,0. O problema é o ponto em que a exponenciação é avaliada na dele. Apenas mova a exponenciação para o bloco de parseTerm () e você ficará bem. Dê uma olhada no abaixo, que é a resposta de @ Boann ligeiramente modificada. A modificação está nos comentários.
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é realmente normal, e não um bug. É agrupado como -(2^2). Experimente no Desmos, por exemplo. Seu código realmente apresenta vários bugs. A primeira é que ^não agrupa mais da direita para a esquerda. Em outras palavras, 2^3^2é suposto agrupar como 2^(3^2)porque ^é associativo à direita, mas suas modificações o tornam como grupo (2^3)^2. A segunda é que ^deveria ter maior precedência que *e /, mas suas modificações tratam da mesma forma. Consulte ideone.com/iN2mMa .
Radiodef 16/08/19
Então, o que você está sugerindo é que a exponenciação é melhor mantida onde estava, não é?
Romeo Sierra
Sim, é o que estou sugerindo.
Radiodef 16/08/19
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));}
Você deve ler sobre como escrever analisadores de expressões matemáticas eficientes. Existe uma metodologia de ciência da computação. Dê uma olhada no ANTLR, por exemplo. Se você pensar bem sobre o que escreveu, verá que coisas como (a + b / -c) * (e / f) não funcionarão com a sua ideia ou o código ficará super sujo e ineficiente.
precisa saber é o seguinte
2
É possível converter qualquer string de expressão na notação infix em uma notação postfix usando o algoritmo shunting-yard do Djikstra . O resultado do algoritmo pode então servir como entrada para o algoritmo postfix com retorna o resultado da expressão.
Não manipula a precedência do operador corretamente. Existem maneiras padrão de fazer isso, e essa não é uma delas.
Marquês de Lorne
EJP, você pode apontar onde há um problema com a precedência do operador? Concordo plenamente com o fato de que essa não é a maneira padrão de fazê-lo. Como as formas padrão já foram mencionadas nos posts anteriores, a ideia era mostrar outra maneira de fazê-lo.
Efi G
2
Biblioteca externa como RHINO ou NASHORN pode ser usada para executar o javascript. E o javascript pode avaliar uma fórmula simples sem separar a string. Sem impacto no desempenho também se o código for escrito corretamente. Abaixo está um exemplo com RHINO -
Respostas:
Com o JDK1.6, você pode usar o mecanismo Javascript interno.
fonte
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();");
- gravará um arquivo via JavaScript no diretório atual do programaEu escrevi esse
eval
método para expressões aritméticas para responder a essa pergunta. Faz adição, subtração, multiplicação, divisão, exponenciação (usando o^
símbolo) e algumas funções básicas, comosqrt
. Ele suporta o agrupamento usando(
...)
e corrige as regras de precedência e associatividade do operador .Exemplo:
Saída: 7,5 (correta)
O analisador é um analisador de descida recursiva ; portanto, usa internamente métodos de análise separados para cada nível de precedência do operador em sua gramática. Eu o mantive curto, para facilitar a modificação, mas aqui estão algumas idéias que você pode querer expandi-lo:
Variáveis:
O bit do analisador que lê os nomes das funções também pode ser facilmente alterado para manipular variáveis personalizadas, procurando nomes em uma tabela de variáveis passada para o
eval
método, como aMap<String,Double> variables
.Compilação e avaliação separadas:
E se, tendo adicionado suporte para variáveis, você quisesse avaliar a mesma expressão milhões de vezes com variáveis alteradas, sem analisá-la toda vez? É possível. Primeiro, defina uma interface a ser usada para avaliar a expressão pré-compilada:
Agora altere todos os métodos que retornam
double
s; portanto, eles retornam uma instância dessa interface. A sintaxe lambda do Java 8 funciona muito bem para isso. Exemplo de um dos métodos alterados:Isso cria uma árvore recursiva de
Expression
objetos que representam a expressão compilada (uma árvore de sintaxe abstrata ). Em seguida, você pode compilá-lo uma vez e avaliá-lo repetidamente com valores diferentes:Tipos de dados diferentes:
Em vez de
double
, você pode alterar o avaliador para usar algo mais poderoso comoBigDecimal
, ou uma classe que implemente números complexos ou números racionais (frações). Você pode até usarObject
, permitindo alguma mistura de tipos de dados em expressões, como uma linguagem de programação real. :)Todo o código nesta resposta foi liberado para o domínio público . Diverta-se!
fonte
double x = parseTerm();
avalia o operador esquerdo, depoisfor (;;) {...}
avalia as operações sucessivas do nível de pedido real (adição, subtração). A mesma lógica é e no método parseTerm. O parseFactor não possui próximo nível, portanto, existem apenas avaliações de métodos / variáveis ou, no caso de parênteses - avaliar subexpressão. Oboolean eat(int charToEat)
método verifica a igualdade do caractere atual do cursor com o caractere charToEat, se igual retornar true e mover o cursor para o próximo caractere, eu uso o nome 'accept' para ele.A maneira correta de resolver isso é com um lexer e um analisador . Você mesmo pode escrever versões simples delas, ou essas páginas também possuem links para analisadores e analisadores Java.
Criar um analisador de descida recursiva é realmente um bom exercício de aprendizado.
fonte
Para o meu projeto universitário, eu estava procurando por um analisador / avaliador que suporta fórmulas básicas e equações mais complicadas (especialmente operadores iterados). Eu achei uma biblioteca de código aberto muito boa para JAVA e .NET chamada mXparser. Vou dar alguns exemplos para entender a sintaxe. Para obter mais instruções, visite o site do projeto (especialmente a seção do tutorial).
https://mathparser.org/
https://mathparser.org/mxparser-tutorial/
https://mathparser.org/api/
E alguns exemplos
1 - Férmula simples
2 - Argumentos e constantes definidas pelo usuário
3 - Funções definidas pelo usuário
4 - Iteração
Encontrado recentemente - caso queira experimentar a sintaxe (e veja o caso de uso avançado), você pode fazer o download do aplicativo Calculadora Escalar, desenvolvido com mXparser.
Cumprimentos
fonte
O HERE é outra biblioteca de código aberto no GitHub chamada EvalEx.
Diferentemente do mecanismo JavaScript, esta biblioteca é focada na avaliação apenas de expressões matemáticas. Além disso, a biblioteca é extensível e suporta o uso de operadores booleanos, bem como parênteses.
fonte
Você também pode tentar o intérprete BeanShell :
fonte
Você pode avaliar expressões facilmente se seu aplicativo Java já acessar um banco de dados, sem usar outros JARs.
Alguns bancos de dados exigem o uso de uma tabela fictícia (por exemplo, a tabela "dupla" do Oracle) e outros permitem avaliar expressões sem "selecionar" em qualquer tabela.
Por exemplo, no Sql Server ou Sqlite
e no Oracle
A vantagem de usar um banco de dados é que você pode avaliar muitas expressões ao mesmo tempo. Além disso, a maioria dos bancos de dados permitirá que você use expressões altamente complexas e também terá várias funções extras que podem ser chamadas conforme necessário.
No entanto, o desempenho pode sofrer se muitas expressões únicas precisarem ser avaliadas individualmente, principalmente quando o banco de dados estiver localizado em um servidor de rede.
A seguir, aborda o problema de desempenho, até certo ponto, usando um banco de dados Sqlite na memória.
Aqui está um exemplo completo de trabalho em Java
Obviamente, você pode estender o código acima para lidar com vários cálculos ao mesmo tempo.
fonte
Este artigo discute várias abordagens. Aqui estão as 2 principais abordagens mencionadas no artigo:
JEXL do Apache
Permite scripts que incluem referências a objetos java.
Use o mecanismo javascript incorporado no JDK:
fonte
Outra maneira é usar o Spring Expression Language ou SpEL, que faz muito mais junto com a avaliação de expressões matemáticas, portanto, talvez um pouco exagero. Você não precisa usar a estrutura Spring para usar esta biblioteca de expressões, pois é independente. Copiando exemplos da documentação do SpEL:
Leia mais exemplos concisos de SpEL aqui e os documentos completos aqui
fonte
se vamos implementá-lo, podemos usar o algoritmo abaixo: -
Embora ainda haja tokens para serem lidos,
1.1 Obtenha o próximo token. 1.2 Se o token for:
1.2.1 Um número: empurre-o para a pilha de valores.
1.2.2 Uma variável: obtenha seu valor e empurre para a pilha de valores.
1.2.3 Um parêntese esquerdo: empurre-o para a pilha do operador.
1.2.4 Um parêntese direito:
1.2.5 Um operador (chame-o thisOp):
Enquanto a pilha do operador não estiver vazia, 1 Retire o operador da pilha do operador. 2 Coloque a pilha de valores duas vezes, obtendo dois operandos. 3 Aplique o operador aos operandos, na ordem correta. 4 Empurre o resultado na pilha de valores.
Nesse ponto, a pilha do operador deve estar vazia e a pilha de valores deve ter apenas um valor, que é o resultado final.
fonte
Esta é outra alternativa interessante https://github.com/Shy-Ta/expression-evaluator-demo
O uso é muito simples e faz o trabalho, por exemplo:
fonte
Parece que o JEP deveria fazer o trabalho
fonte
Eu acho que de qualquer maneira que você faça isso, isso envolverá muitas declarações condicionais. Porém, para operações únicas, como nos seus exemplos, você pode limitar a 4 se declarações com algo como
Fica muito mais complicado quando você deseja lidar com várias operações como "4 + 5 * 6".
Se você está tentando construir uma calculadora, seria melhor passar cada seção do cálculo separadamente (cada número ou operador), e não como uma única string.
fonte
É tarde demais para responder, mas me deparei com a mesma situação para avaliar a expressão em java, isso pode ajudar alguém
MVEL
faz avaliação de expressões em tempo de execução, podemos escrever um código javaString
para que seja avaliado nisso.fonte
Você pode dar uma olhada na estrutura Symja :
Observe que expressões definitivamente mais complexas podem ser avaliadas:
fonte
Experimente o seguinte código de exemplo usando o mecanismo Javascript do JDK1.6 com manipulação de injeção de código.
fonte
Na verdade, isso complementa a resposta dada por @Boann. Ele tem um pequeno erro que faz com que "-2 ^ 2" dê um resultado incorreto de -4,0. O problema é o ponto em que a exponenciação é avaliada na dele. Apenas mova a exponenciação para o bloco de parseTerm () e você ficará bem. Dê uma olhada no abaixo, que é a resposta de @ Boann ligeiramente modificada. A modificação está nos comentários.
fonte
-2^2 = -4
é realmente normal, e não um bug. É agrupado como-(2^2)
. Experimente no Desmos, por exemplo. Seu código realmente apresenta vários bugs. A primeira é que^
não agrupa mais da direita para a esquerda. Em outras palavras,2^3^2
é suposto agrupar como2^(3^2)
porque^
é associativo à direita, mas suas modificações o tornam como grupo(2^3)^2
. A segunda é que^
deveria ter maior precedência que*
e/
, mas suas modificações tratam da mesma forma. Consulte ideone.com/iN2mMa .}
fonte
Que tal algo como isso:
e faça o mesmo para todos os outros operadores matemáticos.
fonte
É possível converter qualquer string de expressão na notação infix em uma notação postfix usando o algoritmo shunting-yard do Djikstra . O resultado do algoritmo pode então servir como entrada para o algoritmo postfix com retorna o resultado da expressão.
Eu escrevi um artigo sobre isso aqui, com uma implementação em java
fonte
Mais uma opção: https://github.com/stefanhaustein/expressionparser
Eu implementei isso para ter uma opção simples, mas flexível, para permitir ambos:
O TreeBuilder vinculado acima faz parte de um pacote de demonstração CAS que faz derivação simbólica. Há também um exemplo de intérprete BASIC e eu comecei a criar um intérprete TypeScript usando-o.
fonte
Uma classe Java que pode avaliar expressões matemáticas:
fonte
Biblioteca externa como RHINO ou NASHORN pode ser usada para executar o javascript. E o javascript pode avaliar uma fórmula simples sem separar a string. Sem impacto no desempenho também se o código for escrito corretamente. Abaixo está um exemplo com RHINO -
fonte
fonte