Làm cách nào để phân tích cú pháp và đánh giá một biểu thức toán học trong một chuỗi (ví dụ '1+1'
) mà không cần gọi eval(string)
ra giá trị số của nó?
Với ví dụ đó, tôi muốn hàm chấp nhận '1+1'
và trả về 2
.
Làm cách nào để phân tích cú pháp và đánh giá một biểu thức toán học trong một chuỗi (ví dụ '1+1'
) mà không cần gọi eval(string)
ra giá trị số của nó?
Với ví dụ đó, tôi muốn hàm chấp nhận '1+1'
và trả về 2
.
Câu trả lời:
Bạn có thể sử dụng thư viện Trình đánh giá biểu thức JavaScript , cho phép bạn thực hiện những việc như:
Parser.evaluate("2 ^ x", { x: 3 });
Hoặc mathjs , cho phép những thứ như:
math.eval('sin(45 deg) ^ 2');
Cuối cùng tôi đã chọn mathjs cho một trong những dự án của mình.
Bạn có thể làm + hoặc - dễ dàng:
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)
)
Toán học phức tạp hơn làm cho eval hấp dẫn hơn- và chắc chắn viết đơn giản hơn.
Ai đó phải phân tích cú pháp chuỗi đó. Nếu đó không phải là trình thông dịch (thông qua eval
) thì đó sẽ cần là bạn, viết quy trình phân tích cú pháp để trích xuất các số, toán tử và bất kỳ thứ gì khác mà bạn muốn hỗ trợ trong một biểu thức toán học.
Vì vậy, không, không có cách nào (đơn giản) mà không có eval
. Nếu bạn lo lắng về bảo mật (vì đầu vào bạn đang phân tích cú pháp không phải từ nguồn bạn kiểm soát), có thể bạn có thể kiểm tra định dạng của đầu vào (thông qua bộ lọc regex danh sách trắng) trước khi chuyển nó đến eval
?
Một giải pháp thay thế cho câu trả lời tuyệt vời của @kennebec, sử dụng biểu thức chính quy ngắn hơn và cho phép khoảng cách giữa các toán tử
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;
}
Sử dụng nó như
addbits('5 + 30 - 25.1 + 11');
Cập nhật
Đây là một phiên bản được tối ưu hóa hơn
function addbits(s) {
return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
.reduce(function(sum, value) {
return parseFloat(sum) + parseFloat(value);
});
}
Tôi đã tạo ra BigEval cho cùng một mục đích.
Trong việc giải các biểu thức, nó hoạt động chính xác giống như Eval()
và hỗ trợ các toán tử như%, ^, &, ** (power) và! (yếu tố). Bạn cũng được phép sử dụng các hàm và hằng số (hoặc biến) bên trong biểu thức. Biểu thức được giải theo thứ tự PEMDAS phổ biến trong các ngôn ngữ lập trình bao gồm 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
Nó cũng có thể được thực hiện để sử dụng các thư viện Big Number đó cho số học trong trường hợp bạn đang xử lý các số với độ chính xác tùy ý.
Tôi đã tìm kiếm các thư viện JavaScript để đánh giá các biểu thức toán học và tìm thấy hai ứng cử viên đầy hứa hẹn sau:
Trình đánh giá biểu thức JavaScript : Nhỏ hơn và hy vọng có trọng lượng nhẹ hơn. Cho phép biểu thức đại số, thay thế và một số hàm.
mathjs : Cho phép cả số phức, ma trận và đơn vị. Được xây dựng để sử dụng bởi cả JavaScript trong trình duyệt và Node.js.
Gần đây tôi đã thực hiện điều này trong C # (không Eval()
cho chúng tôi ...) bằng cách đánh giá biểu thức trong Ký hiệu Ba Lan ngược (đó là một chút dễ dàng). Phần khó thực sự là phân tích cú pháp chuỗi và biến nó thành Ký hiệu đánh bóng ngược. Tôi đã sử dụng thuật toán Shunting Yard , vì có một ví dụ tuyệt vời trên Wikipedia và mã giả. Tôi thấy thực sự đơn giản để thực hiện cả hai và tôi khuyên bạn nên làm điều này nếu bạn chưa tìm thấy giải pháp hoặc đang tìm kiếm các giải pháp thay thế.
Đây là một hàm nhỏ mà tôi đã tổng hợp lại để giải quyết vấn đề này - nó xây dựng biểu thức bằng cách phân tích chuỗi một ký tự tại một thời điểm (mặc dù nó thực sự khá nhanh). Điều này sẽ nhận bất kỳ biểu thức toán học nào (chỉ giới hạn ở các toán tử +, -, *, /) và trả về kết quả. Nó cũng có thể xử lý các giá trị âm và các phép toán số lượng không giới hạn.
Việc "cần làm" duy nhất còn lại là đảm bảo nó tính toán * & / trước + & -. Sẽ thêm chức năng đó sau, nhưng hiện tại điều này không làm những gì tôi cần ...
/**
* 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;
}
Đơn giản và thanh lịch với Function()
function parse(str) {
return Function(`'use strict'; return (${str})`)()
}
parse("1+2+3");
) -----
() `dấu ngoặc này cuối cùng là gì?
parse('process.exit()')
.
Bạn có thể sử dụng vòng lặp for để kiểm tra xem chuỗi có chứa bất kỳ ký tự không hợp lệ nào không và sau đó sử dụng try ... catch với eval để kiểm tra xem phép tính có gặp lỗi như thế eval("2++")
không.
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'))
hoặc thay vì một hàm, bạn có thể đặt 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'))
Cuối cùng, tôi đã tìm ra giải pháp này, giải pháp này hoạt động để tính tổng các số nguyên dương và âm (và với một chút sửa đổi đối với regex cũng sẽ hoạt động cho các số thập phâ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;
}
Tôi không chắc liệu nó có nhanh hơn eval () hay không, nhưng vì tôi phải thực hiện thao tác này rất nhiều lần nên tôi cảm thấy thoải mái hơn khi chạy tập lệnh này hơn là tạo vô số phiên bản của trình biên dịch javascript
return
không thể được sử dụng bên trong một biểu thức, nhưng sum("+1")
trả về NaN .
Hãy thử 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>
Tôi tin rằng điều đó parseInt
và ES6 có thể hữu ích trong tình huống này
==> theo cách này:
let func = (str) => {
let arr = str.split("");
return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`};
console.log(func("1+1"));
Điều chính ở đây là parseInt
phân tích cú pháp số với toán tử. Mã có thể được sửa đổi theo nhu cầu tương ứng.
Bạn có thể sử dụng thư viện được duy trì tốt này từ Github , hoạt động trên cả Nodejs và Trình duyệt , nhanh hơn các thư viện thay thế khác được cung cấp tại đây
Sử dụng
mexp = require('math-expression-evaluator')
var value = mexp.eval(exp);
Thử AutoCalculator https://github.com/JavscriptLab/autocalculate Tính giá trị đầu vào và đầu ra bằng cách sử dụng biểu thức bộ chọn
Chỉ cần thêm một thuộc tính cho đầu vào đầu ra của bạn như data-ac = "(# firstinput + # secondinput)"
Không cần bất kỳ khởi tạo nào, chỉ cần thêm thuộc tính data-ac. Nó sẽ tự động tìm ra các phần tử được thêm động
FOr thêm 'Rs' với Đầu ra chỉ cần thêm vào bên trong dấu ngoặc nhọn 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
với 1?
Đây là một giải pháp thuật toán tương tự như của jMichael lặp lại thông qua ký tự biểu thức theo ký tự và theo dõi dần dần sang trái / toán tử / phải. Hàm tích lũy kết quả sau mỗi lượt nó tìm thấy một ký tự toán tử. Phiên bản này chỉ hỗ trợ các toán tử '+' và '-' nhưng được viết để mở rộng với các toán tử khác. Lưu ý: chúng tôi đặt 'currOp' thành '+' trước khi lặp vì chúng tôi giả sử biểu thức bắt đầu bằng một số thực dương. Trên thực tế, về tổng thể, tôi đang đưa ra giả định rằng đầu vào tương tự như đầu vào từ máy tính.
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;"))()
.