Recientemente escribí un analizador de expresiones matemáticas llamado exp4j que se publicó bajo la licencia de apache, puede consultarlo aquí: objecthunter.net/exp4j
fasseg
2
¿Qué tipo de expresiones permiten? ¿Solo expresiones de operador único? ¿Se permiten paréntesis?
Parece que hay un gran problema allí; Ejecuta un script, no evalúa una expresión. Para que quede claro, engine.eval ("8; 40 + 2"), salidas 42! Si desea un analizador de expresiones que también verifique la sintaxis, acabo de terminar uno (porque no encontré nada que se adapte a mis necesidades): Javaluator .
Jean-Marc Astesana
44
Como nota al margen, si necesita usar el resultado de esta expresión en otra parte de su código, puede convertir el resultado a un Doble como return (Double) engine.eval(foo);
sigue
38
Nota de seguridad: nunca debe usar esto en un contexto de servidor con la entrada del usuario. El JavaScript ejecutado puede acceder a todas las clases de Java y, por lo tanto, secuestrar su aplicación sin límite.
Boann
3
@Boann, te pido que me des una referencia sobre lo que dijiste (para estar seguro al 100%)
partho
17
@partho new javax.script.ScriptEngineManager().getEngineByName("JavaScript") .eval("var f = new java.io.FileWriter('hello.txt'); f.write('UNLIMITED POWER!'); f.close();");- escribirá un archivo vía JavaScript en (por defecto) el directorio actual del programa
Boann
236
He escrito este evalmétodo para expresiones aritméticas para responder a esta pregunta. Realiza suma, resta, multiplicación, división, exponenciación (usando el ^símbolo) y algunas funciones básicas como sqrt. Admite la agrupación usando (... ), y obtiene la precedencia del operador y las reglas de asociatividad correctas.
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();}
El analizador es un analizador de descenso recursivo , por lo que utiliza internamente métodos de análisis separados para cada nivel de precedencia del operador en su gramática. Lo mantuve breve para que sea fácil de modificar, pero aquí hay algunas ideas con las que puede expandirlo:
Variables:
El bit del analizador que lee los nombres de las funciones también se puede cambiar fácilmente para manejar variables personalizadas, buscando nombres en una tabla de variables que se pasa al eval método, como a Map<String,Double> variables.
Compilación y evaluación separadas:
¿Qué pasa si, después de haber agregado soporte para variables, desea evaluar la misma expresión millones de veces con variables cambiadas, sin analizarla cada vez? Es posible. Primero defina una interfaz para usar para evaluar la expresión precompilada:
Ahora cambie todos los métodos que devuelven doubles, por lo que en cambio devuelven una instancia de esa interfaz. La sintaxis lambda de Java 8 funciona muy bien para esto. Ejemplo de uno de los métodos modificados:
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;}}}
Eso construye un árbol recursivo de Expressionobjetos que representan la expresión compilada (un árbol de sintaxis abstracta ). Luego puede compilarlo una vez y evaluarlo repetidamente con diferentes valores:
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());}}
Diferentes tipos de datos:
En lugar de double, podría cambiar el evaluador para usar algo más poderoso BigDecimal, como una clase que implemente números complejos o números racionales (fracciones). Incluso podría usar Object, permitiendo una combinación de tipos de datos en expresiones, al igual que un lenguaje de programación real. :)
Todo el código en esta respuesta se libera al dominio público . ¡Que te diviertas!
Buen algoritmo, a partir de él logré implicar y operadores lógicos. Creamos clases separadas para funciones para evaluar una función, así que, como su idea de variables, creo un mapa con funciones y cuidando el nombre de la función. Cada función implementa una interfaz con un método eval (T rightOperator, T leftOperator), por lo que en cualquier momento podemos agregar características sin cambiar el código del algoritmo. Y es una buena idea hacer que funcione con tipos genéricos. ¡Gracias!
Vasile Bors
1
¿Puedes explicar la lógica detrás de este algoritmo?
iYonatan
1
Trato de dar una descripción de lo que entiendo del código escrito por Boann, y ejemplos descritos wiki. La lógica de este algoritmo a partir de las reglas de las órdenes de operación. 1. signo de operador | evaluación variable | llamada a funciones | paréntesis (sub-expresiones); 2. exponenciación; 3. multiplicación, división; 4. suma, resta;
Vasile Bors
1
Los métodos de algoritmo se dividen para cada nivel de orden de operaciones de la siguiente manera: parseFactor = 1. signo de operador | evaluación variable | llamada a funciones | paréntesis (sub-expresiones); 2. exponenciación; parseTerms = 3. multiplicación, división; parseExpression = 4. suma, resta. El algoritmo, llama a los métodos en orden inverso (parseExpression -> parseTerms -> parseFactor -> parseExpression (para sub-expresiones)), pero cada método a la primera línea llama al método al siguiente nivel, por lo que los métodos de orden de ejecución completo serán En realidad el orden normal de las operaciones.
Vasile Bors
1
Por ejemplo, el método parseExpression double x = parseTerm(); evalúa el operador izquierdo, después de esto for (;;) {...}evalúa las operaciones sucesivas del nivel de orden real (suma, resta). La misma lógica es y en el método parseTerm. ParseFactor no tiene el siguiente nivel, por lo que solo hay evaluaciones de métodos / variables o, en caso de parálisis, evalúe la subexpresión. El boolean eat(int charToEat)método verifica la igualdad del carácter del cursor actual con el carácter charToEat, si igual devuelve verdadero y mueve el cursor al siguiente carácter, utilizo el nombre 'aceptar' para ello.
Vasile Bors
34
La forma correcta de resolver esto es con un lexer y un analizador . Puede escribir versiones simples de estos usted mismo, o esas páginas también tienen enlaces a lexers y analizadores de Java.
Crear un analizador de descenso recursivo es un muy buen ejercicio de aprendizaje.
Para mi proyecto universitario, estaba buscando un analizador / evaluador que soporte tanto fórmulas básicas como ecuaciones más complicadas (especialmente operadores iterados). Encontré una biblioteca de código abierto muy agradable para JAVA y .NET llamada mXparser. Daré algunos ejemplos para que sientan algo sobre la sintaxis. Para obtener más instrucciones, visite el sitio web del proyecto (especialmente la sección de tutoriales).
Expression e =newExpression("( 2 + 3/4 + sin(pi) )/2");double v = e.calculate()
2 - Argumentos y constantes definidos por el usuario
Argument x =newArgument("x = 10");Constant a =newConstant("a = pi^2");Expression e =newExpression("cos(a*x)", x, a);double v = e.calculate()
3 - Funciones definidas por el usuario
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 - Iteración
Expression e =newExpression("sum( i, 1, 100, sin(i) )");double v = e.calculate()
Encontrado recientemente: en caso de que desee probar la sintaxis (y ver el caso de uso avanzado), puede descargar la aplicación Scalar Calculator que funciona con mXparser.
Hasta ahora, esta es la mejor biblioteca de matemáticas que existe; simple para arrancar, fácil de usar y extensible. Definitivamente debería ser la mejor respuesta.
Descubrí que mXparser no puede identificar fórmulas ilegales, por ejemplo, '0/0' obtendrá un resultado como '0'. ¿Cómo puedo resolver este problema?
L
Acabo de encontrar la solución, expression.setSlientMode ()
lulijun
20
AQUÍ hay otra biblioteca de código abierto en GitHub llamada EvalEx.
A diferencia del motor de JavaScript, esta biblioteca se centra únicamente en la evaluación de expresiones matemáticas. Además, la biblioteca es extensible y admite el uso de operadores booleanos, así como paréntesis.
Esto está bien, pero falla cuando intentamos multiplicar valores de múltiplos de 5 o 10, por ejemplo, 65 * 6 da como resultado 3.9E + 2 ...
paarth batra
. Pero hay una manera de arreglar esto al convertirlo en int, es decir, int output = (int) 65 * 6, ahora resultará en 390
paarth batra
1
Para aclarar, eso no es un problema de la biblioteca, sino más bien un problema con la representación de números como valores de coma flotante.
DavidBittner
Esta biblioteca es realmente buena. @paarth batra Enviar a int eliminará todos los puntos decimales. Utilice esto en su lugar: expression.eval (). ToPlainString ();
Puede evaluar expresiones fácilmente si su aplicación Java ya accede a una base de datos, sin usar ningún otro JAR.
Algunas bases de datos requieren que use una tabla ficticia (por ejemplo, la tabla "dual" de Oracle) y otras le permitirán evaluar expresiones sin "seleccionar" de ninguna tabla.
Por ejemplo, en SQL Server o SQLite
select (((12.10+12.0))/233.0) amount
y en Oracle
select (((12.10+12.0))/233.0) amount from dual;
La ventaja de usar una base de datos es que puede evaluar muchas expresiones al mismo tiempo. Además, la mayoría de las bases de datos le permitirán usar expresiones muy complejas y también tendrán una serie de funciones adicionales que se pueden invocar según sea necesario.
Sin embargo, el rendimiento puede verse afectado si es necesario evaluar individualmente muchas expresiones individuales, especialmente cuando la base de datos se encuentra en un servidor de red.
A continuación se aborda el problema de rendimiento hasta cierto punto, mediante el uso de una base de datos en memoria Sqlite.
Depende de para qué use el DB. Si quiere estar seguro, puede crear fácilmente una base de datos sqlite vacía, específicamente para la evaluación matemática.
Permite scripts que incluyen referencias 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 la información del artículo, en caso de que se rompa el enlace.
DJClayworth
He actualizado la respuesta para incluir partes relevantes del artículo
Brad Parks,
1
en la práctica, JEXL es lento (usa introspección de beans), tiene problemas de rendimiento con multithreading (caché global)
Nishi
¡Es bueno saber @Nishi! - Mi caso de uso fue para depurar cosas en entornos en vivo, pero no es parte de la aplicación implementada normal.
Brad Parks
10
Otra forma es usar Spring Expression Language o SpEL, que hace mucho más junto con la evaluación de expresiones matemáticas, por lo tanto, puede ser un poco exagerado. No tiene que usar Spring Framework para usar esta biblioteca de expresiones, ya que es independiente. Copiar ejemplos de la documentación de SpEL:
si vamos a implementarlo, podemos usar el siguiente algoritmo:
Si bien todavía hay tokens para leer,
1.1 Obtenga el siguiente token. 1.2 Si el token es:
1.2.1 Un número: empujarlo a la pila de valores.
1.2.2 Una variable: obtenga su valor y empuje a la pila de valores.
1.2.3 Un paréntesis izquierdo: empújelo sobre la pila del operador.
1.2.4 Un paréntesis correcto:
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 Un operador (llámelo 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.
Mientras la pila de operadores no está vacía, 1 saque el operador de la pila de operadores. 2 Pop la pila de valores dos veces, obteniendo dos operandos. 3 Aplique el operador a los operandos, en el orden correcto. 4 Empuje el resultado en la pila de valores.
En este punto, la pila de operadores debe estar vacía, y la pila de valores debe tener solo un valor, que es el resultado final.
Creo que de cualquier manera que hagas esto implicará muchas declaraciones condicionales. Pero para operaciones individuales como en sus ejemplos, podría limitarlo a 4 si las declaraciones con algo como
String math ="1+4";if(math.split("+").length ==2){//do calculation}elseif(math.split("-").length ==2){//do calculation}...
Se vuelve mucho más complicado cuando quieres lidiar con múltiples operaciones como "4 + 5 * 6".
Si está tratando de construir una calculadora, superaría cada paso del cálculo por separado (cada número u operador) en lugar de hacerlo como una sola cadena.
Se vuelve mucho más complicado tan pronto como tiene que lidiar con múltiples operaciones, precedencia de operadores, paréntesis, ... de hecho, cualquier cosa que caracterice una expresión aritmética real. No puedes llegar a partir de esta técnica.
Marqués de Lorne
4
Es demasiado tarde para responder, pero me encontré con la misma situación para evaluar la expresión en Java, podría ayudar a alguien
MVELhace una evaluación en tiempo de ejecución de las expresiones, podemos escribir un código java Stringpara evaluarlo en esto.
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"
Tenga en cuenta que definitivamente se pueden evaluar expresiones más complejas:
// 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
Esto en realidad complementa la respuesta dada por @Boann. Tiene un pequeño error que hace que "-2 ^ 2" dé un resultado erróneo de -4.0. El problema para eso es el punto en el que se evalúa la exponenciación en su. Simplemente mueva la exponenciación al bloque de parseTerm (), y todo estará bien. Eche un vistazo a la siguiente, que es la respuesta de @ Boann ligeramente modificada. La modificación está en los comentarios.
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 = -4es realmente normal y no es un error. Se agrupa como -(2^2). Pruébalo en Desmos, por ejemplo. Su código en realidad introduce varios errores. La primera es que ^ya no se agrupa de derecha a izquierda. En otras palabras, 2^3^2se supone que se debe agrupar como 2^(3^2)porque ^es asociativo correcto, pero sus modificaciones hacen que se agrupe como (2^3)^2. El segundo es que ^se supone que tiene mayor prioridad que *y /, pero sus modificaciones lo tratan igual. Ver ideone.com/iN2mMa .
Radiodef
Entonces, lo que está sugiriendo es que la exponenciación se mantiene mejor donde estaba, ¿no es así?
Romeo Sierra
Sí, eso es lo que estoy sugiriendo.
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));}
Debe leer sobre cómo escribir analizadores de expresiones matemáticas eficientes. Hay una metodología informática para ello. Echa un vistazo a ANTLR, por ejemplo. Si piensa bien sobre lo que escribió, verá que cosas como (a + b / -c) * (e / f) no funcionarán con su idea o el código será muy sucio e ineficiente.
Daniel Nuriyev
2
Es posible convertir cualquier cadena de expresión en notación infija a una notación postfix usando el algoritmo de desviación de Djikstra . El resultado del algoritmo puede servir como entrada para el algoritmo de postfix con retornos del resultado de la expresión.
No maneja la precedencia del operador correctamente. Hay formas estándar de hacer esto, y esta no es una de ellas.
Marqués de Lorne
EJP, ¿puede señalar dónde hay un problema con la precedencia del operador? Estoy totalmente de acuerdo con el hecho de que no es la forma estándar de hacerlo. Las formas estándar ya se mencionaron en publicaciones anteriores, la idea era mostrar otra forma de hacerlo.
Efi G
2
Se puede usar una biblioteca externa como RHINO o NASHORN para ejecutar JavaScript. Y JavaScript puede evaluar una fórmula simple sin dividir la cadena. Tampoco tiene impacto en el rendimiento si el código está bien escrito. A continuación se muestra un ejemplo con RHINO:
Respuestas:
Con JDK1.6, puede usar el motor Javascript incorporado.
fuente
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();");
- escribirá un archivo vía JavaScript en (por defecto) el directorio actual del programaHe escrito este
eval
método para expresiones aritméticas para responder a esta pregunta. Realiza suma, resta, multiplicación, división, exponenciación (usando el^
símbolo) y algunas funciones básicas comosqrt
. Admite la agrupación usando(
...)
, y obtiene la precedencia del operador y las reglas de asociatividad correctas.Ejemplo:
Salida: 7.5 (que es correcto)
El analizador es un analizador de descenso recursivo , por lo que utiliza internamente métodos de análisis separados para cada nivel de precedencia del operador en su gramática. Lo mantuve breve para que sea fácil de modificar, pero aquí hay algunas ideas con las que puede expandirlo:
Variables:
El bit del analizador que lee los nombres de las funciones también se puede cambiar fácilmente para manejar variables personalizadas, buscando nombres en una tabla de variables que se pasa al
eval
método, como aMap<String,Double> variables
.Compilación y evaluación separadas:
¿Qué pasa si, después de haber agregado soporte para variables, desea evaluar la misma expresión millones de veces con variables cambiadas, sin analizarla cada vez? Es posible. Primero defina una interfaz para usar para evaluar la expresión precompilada:
Ahora cambie todos los métodos que devuelven
double
s, por lo que en cambio devuelven una instancia de esa interfaz. La sintaxis lambda de Java 8 funciona muy bien para esto. Ejemplo de uno de los métodos modificados:Eso construye un árbol recursivo de
Expression
objetos que representan la expresión compilada (un árbol de sintaxis abstracta ). Luego puede compilarlo una vez y evaluarlo repetidamente con diferentes valores:Diferentes tipos de datos:
En lugar de
double
, podría cambiar el evaluador para usar algo más poderosoBigDecimal
, como una clase que implemente números complejos o números racionales (fracciones). Incluso podría usarObject
, permitiendo una combinación de tipos de datos en expresiones, al igual que un lenguaje de programación real. :)Todo el código en esta respuesta se libera al dominio público . ¡Que te diviertas!
fuente
double x = parseTerm();
evalúa el operador izquierdo, después de estofor (;;) {...}
evalúa las operaciones sucesivas del nivel de orden real (suma, resta). La misma lógica es y en el método parseTerm. ParseFactor no tiene el siguiente nivel, por lo que solo hay evaluaciones de métodos / variables o, en caso de parálisis, evalúe la subexpresión. Elboolean eat(int charToEat)
método verifica la igualdad del carácter del cursor actual con el carácter charToEat, si igual devuelve verdadero y mueve el cursor al siguiente carácter, utilizo el nombre 'aceptar' para ello.La forma correcta de resolver esto es con un lexer y un analizador . Puede escribir versiones simples de estos usted mismo, o esas páginas también tienen enlaces a lexers y analizadores de Java.
Crear un analizador de descenso recursivo es un muy buen ejercicio de aprendizaje.
fuente
Para mi proyecto universitario, estaba buscando un analizador / evaluador que soporte tanto fórmulas básicas como ecuaciones más complicadas (especialmente operadores iterados). Encontré una biblioteca de código abierto muy agradable para JAVA y .NET llamada mXparser. Daré algunos ejemplos para que sientan algo sobre la sintaxis. Para obtener más instrucciones, visite el sitio web del proyecto (especialmente la sección de tutoriales).
https://mathparser.org/
https://mathparser.org/mxparser-tutorial/
https://mathparser.org/api/
Y pocos ejemplos
1 - Fórmula simple
2 - Argumentos y constantes definidos por el usuario
3 - Funciones definidas por el usuario
4 - Iteración
Encontrado recientemente: en caso de que desee probar la sintaxis (y ver el caso de uso avanzado), puede descargar la aplicación Scalar Calculator que funciona con mXparser.
Atentamente
fuente
AQUÍ hay otra biblioteca de código abierto en GitHub llamada EvalEx.
A diferencia del motor de JavaScript, esta biblioteca se centra únicamente en la evaluación de expresiones matemáticas. Además, la biblioteca es extensible y admite el uso de operadores booleanos, así como paréntesis.
fuente
También puede probar el intérprete BeanShell :
fuente
Puede evaluar expresiones fácilmente si su aplicación Java ya accede a una base de datos, sin usar ningún otro JAR.
Algunas bases de datos requieren que use una tabla ficticia (por ejemplo, la tabla "dual" de Oracle) y otras le permitirán evaluar expresiones sin "seleccionar" de ninguna tabla.
Por ejemplo, en SQL Server o SQLite
y en Oracle
La ventaja de usar una base de datos es que puede evaluar muchas expresiones al mismo tiempo. Además, la mayoría de las bases de datos le permitirán usar expresiones muy complejas y también tendrán una serie de funciones adicionales que se pueden invocar según sea necesario.
Sin embargo, el rendimiento puede verse afectado si es necesario evaluar individualmente muchas expresiones individuales, especialmente cuando la base de datos se encuentra en un servidor de red.
A continuación se aborda el problema de rendimiento hasta cierto punto, mediante el uso de una base de datos en memoria Sqlite.
Aquí hay un ejemplo de trabajo completo en Java
Por supuesto, puede extender el código anterior para manejar múltiples cálculos al mismo tiempo.
fuente
Este artículo analiza varios enfoques. Aquí están los 2 enfoques clave mencionados en el artículo:
JEXL de Apache
Permite scripts que incluyen referencias a objetos java.
Use el motor javascript incrustado en el JDK:
fuente
Otra forma es usar Spring Expression Language o SpEL, que hace mucho más junto con la evaluación de expresiones matemáticas, por lo tanto, puede ser un poco exagerado. No tiene que usar Spring Framework para usar esta biblioteca de expresiones, ya que es independiente. Copiar ejemplos de la documentación de SpEL:
Lea más ejemplos concisos de SpEL aquí y los documentos completos aquí.
fuente
si vamos a implementarlo, podemos usar el siguiente algoritmo:
Si bien todavía hay tokens para leer,
1.1 Obtenga el siguiente token. 1.2 Si el token es:
1.2.1 Un número: empujarlo a la pila de valores.
1.2.2 Una variable: obtenga su valor y empuje a la pila de valores.
1.2.3 Un paréntesis izquierdo: empújelo sobre la pila del operador.
1.2.4 Un paréntesis correcto:
1.2.5 Un operador (llámelo thisOp):
Mientras la pila de operadores no está vacía, 1 saque el operador de la pila de operadores. 2 Pop la pila de valores dos veces, obteniendo dos operandos. 3 Aplique el operador a los operandos, en el orden correcto. 4 Empuje el resultado en la pila de valores.
En este punto, la pila de operadores debe estar vacía, y la pila de valores debe tener solo un valor, que es el resultado final.
fuente
Esta es otra alternativa interesante https://github.com/Shy-Ta/expression-evaluator-demo
El uso es muy simple y hace el trabajo, por ejemplo:
fuente
Parece que JEP debería hacer el trabajo
fuente
Creo que de cualquier manera que hagas esto implicará muchas declaraciones condicionales. Pero para operaciones individuales como en sus ejemplos, podría limitarlo a 4 si las declaraciones con algo como
Se vuelve mucho más complicado cuando quieres lidiar con múltiples operaciones como "4 + 5 * 6".
Si está tratando de construir una calculadora, superaría cada paso del cálculo por separado (cada número u operador) en lugar de hacerlo como una sola cadena.
fuente
Es demasiado tarde para responder, pero me encontré con la misma situación para evaluar la expresión en Java, podría ayudar a alguien
MVEL
hace una evaluación en tiempo de ejecución de las expresiones, podemos escribir un código javaString
para evaluarlo en esto.fuente
Puede echar un vistazo al marco de Symja :
Tenga en cuenta que definitivamente se pueden evaluar expresiones más complejas:
fuente
Pruebe el siguiente código de muestra utilizando el motor Javascript de JDK1.6 con manejo de inyección de código.
fuente
Esto en realidad complementa la respuesta dada por @Boann. Tiene un pequeño error que hace que "-2 ^ 2" dé un resultado erróneo de -4.0. El problema para eso es el punto en el que se evalúa la exponenciación en su. Simplemente mueva la exponenciación al bloque de parseTerm (), y todo estará bien. Eche un vistazo a la siguiente, que es la respuesta de @ Boann ligeramente modificada. La modificación está en los comentarios.
fuente
-2^2 = -4
es realmente normal y no es un error. Se agrupa como-(2^2)
. Pruébalo en Desmos, por ejemplo. Su código en realidad introduce varios errores. La primera es que^
ya no se agrupa de derecha a izquierda. En otras palabras,2^3^2
se supone que se debe agrupar como2^(3^2)
porque^
es asociativo correcto, pero sus modificaciones hacen que se agrupe como(2^3)^2
. El segundo es que^
se supone que tiene mayor prioridad que*
y/
, pero sus modificaciones lo tratan igual. Ver ideone.com/iN2mMa .}
fuente
Qué tal algo como esto:
y hacer lo mismo para cualquier otro operador matemático en consecuencia.
fuente
Es posible convertir cualquier cadena de expresión en notación infija a una notación postfix usando el algoritmo de desviación de Djikstra . El resultado del algoritmo puede servir como entrada para el algoritmo de postfix con retornos del resultado de la expresión.
Escribí un artículo al respecto aquí, con una implementación en Java
fuente
Otra opción más: https://github.com/stefanhaustein/expressionparser
He implementado esto para tener una opción simple pero flexible que permita ambos:
El TreeBuilder vinculado anteriormente es parte de un paquete de demostración de CAS que realiza una derivación simbólica. También hay un ejemplo de intérprete BÁSICO y he comenzado a construir un intérprete TypeScript usándolo.
fuente
Una clase de Java que puede evaluar expresiones matemáticas:
fuente
Se puede usar una biblioteca externa como RHINO o NASHORN para ejecutar JavaScript. Y JavaScript puede evaluar una fórmula simple sin dividir la cadena. Tampoco tiene impacto en el rendimiento si el código está bien escrito. A continuación se muestra un ejemplo con RHINO:
fuente
fuente