Как мне проанализировать и оценить математическое выражение в строке (например '1+1'
), не вызывая eval(string)
его числового значения?
В этом примере я хочу, чтобы функция принимала '1+1'
и возвращала 2
.
Ответы:
Вы можете использовать библиотеку JavaScript Expression Evaluator , которая позволяет делать такие вещи, как:
Parser.evaluate("2 ^ x", { x: 3 });
Или mathjs , который позволяет такие вещи, как:
math.eval('sin(45 deg) ^ 2');
В итоге я выбрал mathjs для одного из своих проектов.
Вы можете легко сделать + или -:
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';
console.log(
addbits(string)
)
Более сложная математика делает eval более привлекательным и, безусловно, более простым в написании.
Кто-то должен проанализировать эту строку. Если это не интерпретатор (через eval
), то вам нужно будет написать подпрограмму синтаксического анализа для извлечения чисел, операторов и всего остального, что вы хотите поддержать в математическом выражении.
Итак, нет, без (простого) пути нет eval
. Если вас беспокоит безопасность (поскольку входные данные, которые вы анализируете, не из контролируемого вами источника), возможно, вы можете проверить формат входных данных (с помощью фильтра регулярных выражений белого списка), прежде чем передавать его eval
?
Альтернатива отличному ответу @kennebec с использованием более короткого регулярного выражения и допуском пробелов между операторами
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;
}
Используйте это как
addbits('5 + 30 - 25.1 + 11');
Обновить
Вот более оптимизированная версия
function addbits(s) {
return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
.reduce(function(sum, value) {
return parseFloat(sum) + parseFloat(value);
});
}
Я создал BigEval для той же цели.
При решении выражений он работает точно так же Eval()
и поддерживает такие операторы, как%, ^, &, ** (мощность) и! (факториал). Вы также можете использовать функции и константы (или, скажем, переменные) внутри выражения. Выражение решается в порядке PEMDAS, который является обычным для языков программирования, включая 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
Также можно использовать эти библиотеки Big Number для арифметики, если вы имеете дело с числами с произвольной точностью.
Я искал библиотеки JavaScript для вычисления математических выражений и нашел этих двух многообещающих кандидатов:
Оценщик выражений JavaScript : меньше по размеру и, надеюсь, легче. Позволяет использовать алгебраические выражения, замены и ряд функций.
mathjs : позволяет также использовать комплексные числа, матрицы и единицы измерения. Создан для использования как в браузере JavaScript, так и в Node.js.
Я недавно сделал это на C # (нет Eval()
для нас ...), оценив выражение в обратной польской нотации (это простой бит). Самая сложная часть - это фактически разобрать строку и преобразовать ее в обратную польскую нотацию. Я использовал алгоритм Shunting Yard , так как есть отличный пример в Википедии и псевдокоде. Мне показалось очень простым реализовать оба варианта, и я бы порекомендовал это, если вы еще не нашли решения или ищете альтернативы.
Это небольшая функция, которую я собрал только что для решения этой проблемы - она строит выражение, анализируя строку по одному символу за раз (хотя на самом деле это довольно быстро). Это будет принимать любое математическое выражение (ограниченное только операторами +, -, *, /) и возвращать результат. Он также может обрабатывать отрицательные значения и неограниченное количество операций.
Единственное, что осталось сделать, это убедиться, что он вычисляет * & / перед + & -. Позже добавлю эту функциональность, а пока это то, что мне нужно ...
/**
* 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;
}
Просто и элегантно с Function()
function parse(str) {
return Function(`'use strict'; return (${str})`)()
}
parse("1+2+3");
) -----
() `эта скобка наконец?
parse('process.exit()')
.
Вы можете использовать цикл for, чтобы проверить, содержит ли строка какие-либо недопустимые символы, а затем использовать try ... catch с eval, чтобы проверить, не вызывает ли вычисление подобную ошибку eval("2++")
.
function evaluateMath(str) {
for (var i = 0; i < str.length; i++) {
if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
return NaN;
}
}
try {
return eval(str)
} catch (e) {
if (e.name !== 'SyntaxError') throw e
return NaN;
}
}
console.log(evaluateMath('2 + 6'))
или вместо функции вы можете установить Math.eval
Math.eval = function(str) {
for (var i = 0; i < str.length; i++) {
if (isNaN(str[i]) && !['+', '-', '/', '*', '%', '**'].includes(str[i])) {
return NaN;
}
}
try {
return eval(str)
} catch (e) {
if (e.name !== 'SyntaxError') throw e
return NaN;
}
}
console.log(Math.eval('2 + 6'))
В конце концов я выбрал это решение, которое работает для суммирования положительных и отрицательных целых чисел (и с небольшой модификацией регулярного выражения будет работать и для десятичных чисел):
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;
}
Я не уверен, что это быстрее, чем eval (), но, поскольку мне приходится выполнять операцию много раз, мне гораздо удобнее запускать этот скрипт, чем создавать множество экземпляров компилятора javascript
return
не может использоваться внутри выражения, sum("+1")
возвращает NaN .
Попробуйте ботаник
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>
Я считаю, что parseInt
и ES6 может быть полезен в этой ситуации.
let func = (str) => {
let arr = str.split("");
return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`
};
console.log(func("1+1"));
Здесь главное, что номер parseInt
разбирается с оператором. Код может быть изменен в соответствии с потребностями.
Попробуйте AutoCalculator https://github.com/JavscriptLab/autocalculate Рассчитайте входные значения и выходные данные с помощью выражений селектора
Просто добавьте атрибут для выходного ввода, например data-ac = "(# firstinput + # secondinput)"
Нет необходимости в какой-либо инициализации, просто добавьте только атрибут data-ac. Он автоматически обнаружит динамически добавленные элементы
Для добавления 'Rs' с Output просто добавьте в фигурные скобки data-ac = "{Rs} (# firstinput + # secondinput)"
const operatorToFunction = {
"+": (num1, num2) => +num1 + +num2,
"-": (num1, num2) => +num1 - +num2,
"*": (num1, num2) => +num1 * +num2,
"/": (num1, num2) => +num1 / +num2
}
const findOperator = (str) => {
const [operator] = str.split("").filter((ch) => ["+", "-", "*", "/"].includes(ch))
return operator;
}
const executeOperation = (str) => {
const operationStr = str.replace(/[ ]/g, "");
const operator = findOperator(operationStr);
const [num1, num2] = operationStr.split(operator)
return operatorToFunction[operator](num1, num2);
};
const addition = executeOperation('1 + 1'); // ans is 2
const subtraction = executeOperation('4 - 1'); // ans is 3
const multiplication = executeOperation('2 * 5'); // ans is 10
const division = executeOperation('16 / 4'); // ans is 4
num
на 1?
Вот алгоритмическое решение, подобное jMichael, которое перебирает выражение символ за символом и постепенно отслеживает left / operator / right. Функция накапливает результат после каждого хода нахождения символа оператора. Эта версия поддерживает только операторы «+» и «-», но написана для расширения другими операторами. Примечание: мы устанавливаем currOp на «+» перед циклом, потому что предполагаем, что выражение начинается с положительного числа с плавающей запятой. Фактически, в целом я предполагаю, что ввод аналогичен тому, что поступает с калькулятора.
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);
}
(Function("return 1+1;"))()
.