Jak przeanalizować i ocenić wyrażenie matematyczne w ciągu (np. '1+1'
) Bez wywoływania w eval(string)
celu uzyskania jego wartości liczbowej?
W tym przykładzie chcę, aby funkcja akceptowała '1+1'
i zwracała 2
.
Jak przeanalizować i ocenić wyrażenie matematyczne w ciągu (np. '1+1'
) Bez wywoływania w eval(string)
celu uzyskania jego wartości liczbowej?
W tym przykładzie chcę, aby funkcja akceptowała '1+1'
i zwracała 2
.
Odpowiedzi:
Możesz użyć biblioteki JavaScript Expression Evaluator , która umożliwia wykonywanie takich czynności jak:
Parser.evaluate("2 ^ x", { x: 3 });
Lub matematyka , która pozwala na takie rzeczy, jak:
math.eval('sin(45 deg) ^ 2');
W końcu wybrałem matematykę do jednego z moich projektów.
Możesz łatwo + lub -:
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)
)
Bardziej skomplikowana matematyka sprawia, że eval jest bardziej atrakcyjny - i na pewno łatwiejszy do napisania.
Ktoś musi przeanalizować ten ciąg. Jeśli nie jest to interpreter (via eval
), to będziesz musiał to być ty, pisząc procedurę parsowania w celu wyodrębnienia liczb, operatorów i wszystkiego, co chcesz obsługiwać w wyrażeniu matematycznym.
Więc nie, nie ma żadnego (prostego) sposobu bez eval
. Jeśli obawiasz się o bezpieczeństwo (ponieważ dane wejściowe, które analizujesz, nie pochodzą ze źródła, które kontrolujesz), może możesz sprawdzić format danych wejściowych (za pomocą filtru regex z białej listy) przed przekazaniem go do eval
?
Alternatywa dla doskonałej odpowiedzi @kennebec, wykorzystująca krótsze wyrażenie regularne i dopuszczająca spacje między operatorami
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;
}
Użyj tego jak
addbits('5 + 30 - 25.1 + 11');
Aktualizacja
Oto bardziej zoptymalizowana wersja
function addbits(s) {
return (s.replace(/\s/g, '').match(/[+\-]?([0-9\.]+)/g) || [])
.reduce(function(sum, value) {
return parseFloat(sum) + parseFloat(value);
});
}
Stworzyłem BigEval w tym samym celu.
W rozwiązywaniu wyrażeń działa dokładnie tak samo, jak Eval()
i obsługuje operatory takie jak%, ^, &, ** (potęga) i! (Factorial). Możesz także używać funkcji i stałych (lub powiedzmy zmiennych) wewnątrz wyrażenia. Wyrażenie jest rozwiązywane w kolejności PEMDAS, która jest powszechna w językach programowania, w tym 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
Można również wykorzystać te biblioteki Big Number do arytmetyki, jeśli masz do czynienia z liczbami z dowolną dokładnością.
Poszukałem bibliotek JavaScript do oceny wyrażeń matematycznych i znalazłem tych dwóch obiecujących kandydatów:
JavaScript Expression Evaluator : Mniejszy i, miejmy nadzieję, lżejszy. Umożliwia wyrażenia algebraiczne, podstawienia i szereg funkcji.
mathjs : umożliwia również liczby zespolone, macierze i jednostki. Zbudowany do użytku zarówno przez JavaScript w przeglądarce, jak i Node.js.
Niedawno zrobiłem to w C # (nie Eval()
dla nas ...), oceniając wyrażenie w odwrotnej notacji polskiej (to łatwy kawałek). Najtrudniejsza część polega na parsowaniu struny i zamianie jej na odwrotną notację polską. Użyłem algorytmu manewrowania , ponieważ jest świetny przykład na Wikipedii i pseudokodzie. Zauważyłem, że wdrożenie obu jest naprawdę proste i polecam to, jeśli nie znalazłeś jeszcze rozwiązania lub szukasz alternatyw.
To jest mała funkcja, którą właśnie teraz dodałem, aby rozwiązać ten problem - buduje wyrażenie, analizując ciąg znaków po jednym znaku na raz (chociaż jest to dość szybkie). Spowoduje to pobranie dowolnego wyrażenia matematycznego (ograniczone tylko do operatorów +, -, *, /) i zwrócenie wyniku. Obsługuje również wartości ujemne i nieograniczoną liczbę operacji.
Jedyne „do zrobienia” to upewnienie się, że oblicza * & / przed + & -. Dodam tę funkcjonalność później, ale na razie robi to, czego potrzebuję ...
/**
* 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;
}
Prosty i elegancki z Function()
function parse(str) {
return Function(`'use strict'; return (${str})`)()
}
parse("1+2+3");
) -----
() `ten nawias w końcu?
parse('process.exit()')
.
Możesz użyć pętli for, aby sprawdzić, czy ciąg zawiera jakiekolwiek nieprawidłowe znaki, a następnie użyć try ... catch z eval, aby sprawdzić, czy obliczenia generują błąd, taki jak eval("2++")
by.
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'))
lub zamiast funkcji możesz ustawić 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'))
Ostatecznie zdecydowałem się na to rozwiązanie, które działa na sumowanie dodatnich i ujemnych liczb całkowitych (i po niewielkiej modyfikacji wyrażenia regularnego będzie działać również dla liczb dziesiętnych):
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;
}
Nie jestem pewien, czy jest szybszy niż eval (), ale ponieważ muszę wykonywać tę operację wiele razy, jestem o wiele wygodniejszy z uruchamianiem tego skryptu niż tworzenie wielu instancji kompilatora javascript
return
nie można go użyć wewnątrz wyrażenia, sum("+1")
zwraca NaN .
Spróbuj 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>
Wierzę, że parseInt
i ES6 mogą być pomocne w tej sytuacji
let func = (str) => {
let arr = str.split("");
return `${Number(arr[0]) + parseInt(arr[1] + Number(arr[2]))}`
};
console.log(func("1+1"));
Najważniejsze jest to, że parseInt
analizuje liczbę z operatorem. Kod można modyfikować do odpowiednich potrzeb.
Wypróbuj AutoCalculator https://github.com/JavscriptLab/autocalculate Oblicz wartość wejściową i wyjściową za pomocą wyrażeń selektora
Po prostu dodaj atrybut do danych wyjściowych, taki jak data-ac = "(# firstinput + # secondinput)"
Nie ma potrzeby żadnej inicjalizacji, wystarczy dodać tylko atrybut data-ac. Automatycznie wykryje dynamicznie dodane elementy
W przypadku dodania „Rs” z wyjściem po prostu dodaj w nawiasach klamrowych 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
przez 1?
Oto rozwiązanie algorytmiczne podobne do rozwiązania jMichaela, które przechodzi przez wyrażenie znak po znaku i stopniowo śledzi lewo / operator / prawo. Funkcja kumuluje wynik po każdej turze, w której znajduje znak operatora. Ta wersja obsługuje tylko operatory „+” i „-”, ale została napisana w celu rozszerzenia o inne operatory. Uwaga: przed zapętleniem ustawiamy „currOp” na „+”, ponieważ zakładamy, że wyrażenie zaczyna się od dodatniego pływaka. W rzeczywistości ogólnie zakładam, że dane wejściowe są podobne do tego, co pochodzi z kalkulatora.
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;"))()
.