Posterior a consultar con especialistas en esta materia, programadores de varias ramas y profesores dimos con la respuesta a la interrogande y la compartimos en esta publicación.
Solución:
Con JDK1.6, puede usar el motor Javascript incorporado.
import javax.script.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
public class Test
public static void main(String[] args) throws ScriptException
ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("JavaScript");
String foo = "40+2";
System.out.println(engine.eval(foo));
he escrito esto eval
método de expresiones aritméticas para responder a esta pregunta. Hace sumas, restas, multiplicaciones, divisiones, exponenciaciones (usando el ^
símbolo), y algunas funciones básicas como sqrt
. Es compatible con la agrupación mediante (
…)
y obtiene las reglas de asociatividad y precedencia de operadores correctas.
public static double eval(final String str)
return new Object() expression `+` term .parse();
Ejemplo:
System.out.println(eval("((4 - 2^3 + 1) * -sqrt(3*3+4*4)) / 2"));
Salida: 7.5 (que es correcto)
El analizador es un analizador descendente recursivo, por lo que internamente utiliza métodos de análisis independientes para cada nivel de precedencia de operadores en su gramática. lo guardé corto por lo que es fácil de modificar, pero aquí hay algunas ideas con las que podría querer 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 pasada al
eval
método, como unMap
.variables -
Compilación y evaluación separadas:
¿Qué pasaría si, habiendo agregado soporte para variables, quisiera evaluar la misma expresión millones de veces con variables modificadas, sin analizarla cada vez? Es posible. Primero defina una interfaz para usar para evaluar la expresión precompilada:
@FunctionalInterface interface Expression double eval();
Ahora cambie todos los métodos que devuelven
double
s, por lo que en su lugar 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('+')) // addition Expression a = x, b = parseTerm(); x = (() -> a.eval() + b.eval()); else if (eat('-')) // subtraction Expression a = x, b = parseTerm(); x = (() -> a.eval() - b.eval()); else return x;
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:public static void main(String[] args) Map
variables = new HashMap<>(); Expression exp = parse("x^2 - x + 2", variables); for (double x = -20; x <= +20; x++) variables.put("x", x); System.out.println(x + " => " + exp.eval()); -
Diferentes tipos de datos:
En vez de
double
podría cambiar el evaluador para usar algo más poderoso comoBigDecimal
, o una clase que implementa números complejos, o números racionales (fracciones). Incluso podrías usarObject
, lo que permite cierta combinación de tipos de datos en expresiones, como un lenguaje de programación real. 🙂
Todo el código en esta respuesta liberado al dominio público. ¡Diviértete!
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.
Recuerda algo, que tienes la capacidad de valorar este post si diste con el hallazgo.