2010-02-16 25 views
48

¿Cómo analizo y evalúo una expresión matemática en una cadena (por ejemplo, '1+1') sin invocar eval(string) para obtener su valor numérico?Evaluar una cadena como expresión matemática en JavaScript

Con ese ejemplo, quiero que la función acepte '1+1' y devuelva 2.

+0

AFAIK no. Para eso eval(), pero supongo que quieres una solución segura ... – Tronic

+3

Muy similar, pero probablemente no sea lo que estás pidiendo: '(Función (" return 1 + 1; "))()'. – Gumbo

Respuesta

2

con el tiempo he ido a esta solución, que trabaja para sumar números enteros positivos y negativos (y con un poco de modificación de la expresión regular funcionará para decimales también):

function sum(string) { 
    return (string.match(/^(-?\d+)(\+-?\d+)*$/)) ? string.split('+').stringSum() : NaN; 
} 

Array.prototype.stringSum = function() { 
    var sum = 0; 
    for(var k=0, kl=this.length;k<kl;k++) 
    { 
     sum += +this[k]; 
    } 
    return sum; 
} 

No estoy seguro de si es más rápido que eval(), pero como tengo que llevar a cabo la operación muchas veces me siento mucho más cómodo ejecutando este script que creando muchas instancias del compilador de JavaScript

+1

Aunque 'return' no se puede usar dentro de una expresión,' sum ("+ 1") 'devuelve * NaN *. – Gumbo

+0

Siempre pregúntese si el retorno tiene que ir o no dentro de una expresión ternaria.Me gustaría excluir "+1" porque aunque 'debería' evaluarse como un número, no es realmente un ejemplo de una suma matemática en el sentido cotidiano. Mi código está diseñado para evaluar y filtrar cadenas permisibles. – wheresrhys

16

Alguien tiene que analizar esa cadena. Si no es el intérprete (a través del eval), tendrá que ser usted quien escriba una rutina de análisis para extraer números, operadores y cualquier otra cosa que quiera apoyar en una expresión matemática.

Así que, no, no hay ninguna forma (simple) sin eval. Si le preocupa la seguridad (porque la entrada que está analizando no proviene de una fuente que usted controla), ¿tal vez puede verificar el formato de la entrada (a través de un filtro de expresión regular de la lista blanca) antes de pasarlo al eval?

+1

No es la seguridad lo que me molesta (ya tengo una expresión regular para el trabajo), es más la carga en el navegador, ya que tengo que procesar muchas cadenas de este tipo. ¿Podría un analizador personalizado ser más rápido que eval()? – wheresrhys

+10

@wheresrhys: ¿Por qué crees que tu analizador, escrito en JS, va a ser más rápido que el proporcionado por el sistema (optimizado, probablemente escrito en C o C++)? –

+4

eval es, de lejos, la forma más rápida de hacerlo. Sin embargo, una expresión regular generalmente no es suficiente para garantizar la seguridad. – levik

14

// Usted puede hacer + o - fácil:

function addbits(s){ 
    var total= 0, s= s.match(/[+\-]*(\.\d+|\d+(\.\d+)?)/g) || []; 
    while(s.length){ 
     total+= parseFloat(s.shift()); 
    } 
    return total; 
} 

var string='1+23+4+5-30'; 
addbits(string) 

más complicado matemáticas hace eval más attractive- y ciertamente más fácil de escribir.

+2

+1 - Probablemente un poco más general que con lo que fui, pero no funcionará para mi situación ya que puedo tener algo así como 1 + -2, y quiero que la expresión regular también excluya las declaraciones inválidas (creo que la tuya permitir algo como "+ 3 + 4 +") – wheresrhys

+1

¡Hombre! ... Esto me ahorró unos 10 kilogramos de búsqueda de Google pesada ... ¡Genial! – ErickBest

+0

He publicado a continuación una respuesta actualizada con una expresión regular más corta y que permite espacios entre operadores –

6

Recientemente lo hice en C# (no Eval() para nosotros ...) mediante la evaluación de la expresión en Notación Polaca Inversa (ese es el bit fácil). La parte difícil es en realidad analizar esta cadena y convertirla en Notación Polaca Inversa. Utilicé el algoritmo de Shunting Yard ya que hay un gran ejemplo en Wikipedia y un pseudocódigo. Me pareció muy fácil de implementar y lo recomendaría si aún no ha encontrado una solución o está buscando alternativas.

+0

¿Puede proporcionar algún ejemplo o enlace a la Wikipedia? – LetynSOFT

47

Puede utilizar el JavaScript Expression Evaluator library, lo que le permite hacer cosas como:

Parser.evaluate("2^x", { x: 3 }); 

O mathjs, que permite cosas como:

math.eval('sin(45 deg)^2'); 

terminé mathjs la elección de uno de mis proyectos .

4

Esta es una pequeña función que inventé hace un momento para resolver este problema: construye la expresión analizando la cadena un carácter a la vez (en realidad es bastante rápido). Esto tomará cualquier expresión matemática (limitada a +, -, *,/operadores solamente) y devolverá el resultado. También puede manejar valores negativos y operaciones de números ilimitados.

El único "por hacer" que queda es asegurarse de que calcula * &/before + & -. Agregaré esa funcionalidad más adelante, pero por ahora esto hace lo que necesito ...

/** 
* Evaluate a mathematical expression (as a string) and return the result 
* @param {String} expr A mathematical expression 
* @returns {Decimal} Result of the mathematical expression 
* @example 
* // Returns -81.4600 
* expr("10.04+9.5-1+-100"); 
*/ 
function expr (expr) { 

    var chars = expr.split(""); 
    var n = [], op = [], index = 0, oplast = true; 

    n[index] = ""; 

    // Parse the expression 
    for (var c = 0; c < chars.length; c++) { 

     if (isNaN(parseInt(chars[c])) && chars[c] !== "." && !oplast) { 
      op[index] = chars[c]; 
      index++; 
      n[index] = ""; 
      oplast = true; 
     } else { 
      n[index] += chars[c]; 
      oplast = false; 
     } 
    } 

    // Calculate the expression 
    expr = parseFloat(n[0]); 
    for (var o = 0; o < op.length; o++) { 
     var num = parseFloat(n[o + 1]); 
     switch (op[o]) { 
      case "+": 
       expr = expr + num; 
       break; 
      case "-": 
       expr = expr - num; 
       break; 
      case "*": 
       expr = expr * num; 
       break; 
      case "/": 
       expr = expr/num; 
       break; 
     } 
    } 

    return expr; 
} 
6

fui en busca de bibliotecas de JavaScript para evaluar expresiones matemáticas, y encontrado los dos candidatos prometedores:

  • JavaScript Expression Evaluator: más pequeño y es de esperar más de peso ligero. Permite expresiones algebraicas, sustituciones y un número de funciones de .

  • mathjs: Permite números complejos, matrices y unidades también. Creado para ser utilizado tanto por JavaScript dentro del navegador como por Node.js.

+0

Ahora he probado el JavaScript Expression Evaluator, y parece rockear. (Probablemente las matemáticas también oscilen, pero parece un poco demasiado grande para mis propósitos y también me gusta la funcionalidad de sustitución en JSEE.) – Itangalo

4

Creé BigEval para el mismo propósito.
Al resolver expresiones, realiza exactamente lo mismo que Eval() y admite operadores como%, ^, &, ** (alimentación) y! (factorial). También se le permite usar funciones y constantes (o decir variables) dentro de la expresión. La expresión se resuelve en PEMDAS order, que es común en lenguajes de programación, incluido JavaScript.

var Obj = new BigEval(); 
var result = Obj.exec("5! + 6.6e3 * (PI + E)"); // 38795.17158152233 
var result2 = Obj.exec("sin(45 * deg)**2 + cos(pi/4)**2"); // 1 
var result3 = Obj.exec("0 & -7^-7 - 0%1 + 6%2"); //-7 

También se pueden hacer para utilizar esas bibliotecas gran número de aritmética en caso de que se trata de números con precisión arbitraria.

2

Trate nerdamer

var result = nerdamer('12+2+PI').evaluate(); 
 
document.getElementById('text').innerHTML = result.text();
<script src="http://nerdamer.com/js/nerdamer.core.js"></script> 
 
<div id="text"></div>

1

Trate AutoCalculator valor https://github.com/JavscriptLab/autocalculate Calcular entradas y la salida Mediante el uso de expresiones de selector

Sólo añadir un atributo para su entrada una salida como datos-ac = "(# firstinput + # secondinput)"

No es necesario inicializar nada, solo agregue el atributo data-ac. Será averiguar elementos añadidos dinámicamente de forma automática

para 'R' add con salida sólo tiene que añadir el soporte interior rizado datos-ac = "{R} (# firstinput + # secondinput)"

3

Una alternativa a la excelente responder por @kennebec, utilizando una expresión regular más corto y que permite espacios entre operadores

function addbits(s) { 
    var total = 0; 
    s = s.replace(/\s/g, '').match(/[+\-]?([0-9\.\s]+)/g) || []; 
    while(s.length) total += parseFloat(s.shift()); 
    return total; 
} 

usarlo como

addbits('5 + 30 - 25.1 + 11'); 

actualización

Aquí hay una versión más optimizada

function addbits(s) { 
    return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || []) 
     .reduce(function(sum, value) { 
      return parseFloat(sum) + parseFloat(value); 
     }); 
} 
+0

Esto es perfecto, siempre y cuando solo necesites sumas y restas. ¡Tan poco código, tanto producto! Tenga la seguridad de que se está utilizando para bien :) –

1

Creo que parseInt y ES6 puede ser útil en esta situación

==> de esta manera:

let func = (str) => { 
let arr = str.split(""); 
return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`}; 
console.log(func("1+1")); 

Lo principal aquí es que parseInt analiza el número con el operador. El código se puede modificar a las necesidades correspondientes.

0

Aquí hay una solución algorítmica similar a la de jMichael que recorre la expresión carácter por carácter y rastrea progresivamente hacia la izquierda/operador/derecha. La función acumula el resultado después de cada turno, encuentra un carácter de operador. Esta versión solo es compatible con los operadores '+' y '-', pero está escrita para ser extendida con otros operadores. Nota: establecemos 'currOp' en '+' antes de un bucle porque suponemos que la expresión comienza con un flotante positivo. De hecho, en general estoy asumiendo que la entrada es similar a lo que vendría de una calculadora.

function calculate(exp) { 
    const opMap = { 
    '+': (a, b) => { return parseFloat(a) + parseFloat(b) }, 
    '-': (a, b) => { return parseFloat(a) - parseFloat(b) }, 
    }; 
    const opList = Object.keys(opMap); 

    let acc = 0; 
    let next = ''; 
    let currOp = '+'; 

    for (let char of exp) { 
    if (opList.includes(char)) { 
     acc = opMap[currOp](acc, next); 
     currOp = char; 
     next = ''; 
    } else { 
     next += char; 
    } 
    } 

    return currOp === '+' ? acc + parseFloat(next) : acc - parseFloat(next); 
} 
Cuestiones relacionadas