Comment analyser et évaluer une expression mathématique dans une chaîne de caractères (par ex. '1+1'
) sans invoquer eval(string)
pour obtenir sa valeur numérique ?
Avec cet exemple, je veux que la fonction accepte '1+1'
et retourner 2
.
Comment analyser et évaluer une expression mathématique dans une chaîne de caractères (par ex. '1+1'
) sans invoquer eval(string)
pour obtenir sa valeur numérique ?
Avec cet exemple, je veux que la fonction accepte '1+1'
et retourner 2
.
L'évaluation était bien trop lente pour moi. J'ai donc développé un StringMathEvaluator(SME), qui suit l'ordre des opérations et fonctionne pour toutes les équations arithmétiques contenant ce qui suit :
Résultats du test de vitesse : (Exécuté dans le navigateur chromium)
~(80 - 99)% plus rapide avec une complexité d'expression raisonnable.
500000 iterations (SME/eval)
Integer Test '4'
(0.346/35.646)Sec - SME 99.03% faster
Simple Equation Test '4+-3'
(0.385/35.09)Sec - SME 98.9% faster
Complex Equation Test '(16 / 44 * 2) + ((4 + (4+3)-(12- 6)) / (2 * 8))'
(2.798/38.116)Sec - SME 92.66% faster
Variable Evaluation Test '2 + 5.5 + Math.round(Math.sqrt(Math.PI)) + values.one + values.two + values.four.nested'
(6.113/38.177)Sec - SME 83.99% faster
Exemple d'utilisation :
Initialiser :
Sans variables :
const math = new StringMathEvaluator();
const twentyOne = math.eval('11 + 10');
console.log('BlackJack' + twentyOne);
// BlackJack21
Avec des variables
const globalScope = {Math};
const math = new StringMathEvaluator(globalScope);
const localScope = {a: [[1, () => ({func: () => [17,13]})],[11,64,2]]};
const str = '((a[0][1]().func()[0] + a[0][1]().func()[1]) * a[1][2] - Math.sqrt(a[1][1]) - a[1][0]) / a[0][0]';
const fortyOne = math.eval(str, localScope);
console.log('Sum' + fortyOne);
// Sum41
PME :
class StringMathEvaluator {
constructor(globalScope) {
globalScope = globalScope || {};
const instance = this;
let splitter = '.';
function resolve (path, currObj, globalCheck) {
if (path === '') return currObj;
try {
if ((typeof path) === 'string') path = path.split(splitter);
for (let index = 0; index < path.length; index += 1) {
currObj = currObj[path[index]];
}
if (currObj === undefined && !globalCheck) throw Error('try global');
return currObj;
} catch (e) {
return resolve(path, globalScope, true);
}
}
function multiplyOrDivide (values, operands) {
const op = operands[operands.length - 1];
if (op === StringMathEvaluator.multi || op === StringMathEvaluator.div) {
const len = values.length;
values[len - 2] = op(values[len - 2], values[len - 1])
values.pop();
operands.pop();
}
}
const resolveArguments = (initialChar, func) => {
return function (expr, index, values, operands, scope, path) {
if (expr[index] === initialChar) {
const args = [];
let endIndex = index += 1;
const terminationChar = expr[index - 1] === '(' ? ')' : ']';
let terminate = false;
let openParenCount = 0;
while(!terminate && endIndex < expr.length) {
const currChar = expr[endIndex++];
if (currChar === '(') openParenCount++;
else if (openParenCount > 0 && currChar === ')') openParenCount--;
else if (openParenCount === 0) {
if (currChar === ',') {
args.push(expr.substr(index, endIndex - index - 1));
index = endIndex;
} else if (openParenCount === 0 && currChar === terminationChar) {
args.push(expr.substr(index, endIndex++ - index - 1));
terminate = true;
}
}
}
for (let index = 0; index < args.length; index += 1) {
args[index] = instance.eval(args[index], scope);
}
const state = func(expr, path, scope, args, endIndex);
if (state) {
values.push(state.value);
return state.endIndex;
}
}
}
};
function chainedExpressions(expr, value, endIndex, path) {
if (expr.length === endIndex) return {value, endIndex};
let values = [];
let offsetIndex;
let valueIndex = 0;
let chained = false;
do {
const subStr = expr.substr(endIndex);
const offsetIndex = isolateArray(subStr, 0, values, [], value, path) ||
isolateFunction(subStr, 0, values, [], value, path) ||
(subStr[0] === '.' &&
isolateVar(subStr, 1, values, [], value));
if (Number.isInteger(offsetIndex)) {
value = values[valueIndex];
endIndex += offsetIndex - 1;
chained = true;
}
} while (offsetIndex !== undefined);
return {value, endIndex};
}
const isolateArray = resolveArguments('[',
(expr, path, scope, args, endIndex) => {
endIndex = endIndex - 1;
let value = resolve(path, scope)[args[args.length - 1]];
return chainedExpressions(expr, value, endIndex, '');
});
const isolateFunction = resolveArguments('(',
(expr, path, scope, args, endIndex) =>
chainedExpressions(expr, resolve(path, scope).apply(null, args), endIndex - 1, ''));
function isolateParenthesis(expr, index, values, operands, scope) {
const char = expr[index];
if (char === '(') {
let openParenCount = 1;
let endIndex = index + 1;
while(openParenCount > 0 && endIndex < expr.length) {
const currChar = expr[endIndex++];
if (currChar === '(') openParenCount++;
if (currChar === ')') openParenCount--;
}
const len = endIndex - index - 2;
values.push(instance.eval(expr.substr(index + 1, len), scope));
multiplyOrDivide(values, operands);
return endIndex;
}
};
function isolateOperand (char, operands) {
switch (char) {
case '*':
operands.push(StringMathEvaluator.multi);
return true;
break;
case '/':
operands.push(StringMathEvaluator.div);
return true;
break;
case '+':
operands.push(StringMathEvaluator.add);
return true;
break;
case '-':
operands.push(StringMathEvaluator.sub);
return true;
break;
}
return false;
}
function isolateValueReg(reg, resolver, splitter) {
return function (expr, index, values, operands, scope) {
const match = expr.substr(index).match(reg);
let args;
if (match) {
let endIndex = index + match[0].length;
let value = resolver(match[0], scope);
if (!Number.isFinite(value)) {
const state = chainedExpressions(expr, scope, endIndex, match[0]);
if (state !== undefined) {
value = state.value;
endIndex = state.endIndex;
}
}
values.push(value);
multiplyOrDivide(values, operands);
return endIndex;
}
}
}
const isolateNumber = isolateValueReg(StringMathEvaluator.numReg, Number.parseFloat);
const isolateVar = isolateValueReg(StringMathEvaluator.varReg, resolve);
this.eval = function (expr, scope) {
scope = scope || globalScope;
const allowVars = (typeof scope) === 'object';
let operands = [];
let values = [];
let prevWasOpperand = true;
for (let index = 0; index < expr.length; index += 1) {
const char = expr[index];
if (prevWasOpperand) {
let newIndex = isolateParenthesis(expr, index, values, operands, scope) ||
isolateNumber(expr, index, values, operands, scope) ||
(allowVars && isolateVar(expr, index, values, operands, scope));
if (Number.isInteger(newIndex)) {
index = newIndex - 1;
prevWasOpperand = false;
}
} else {
prevWasOpperand = isolateOperand(char, operands);
}
}
let value = values[0];
for (let index = 0; index < values.length - 1; index += 1) {
value = operands[index](values[index], values[index + 1]);
values[index + 1] = value;
}
return value;
}
}
}
StringMathEvaluator.numReg = /^(-|)[0-9\.]{1,}/;
StringMathEvaluator.varReg = /^((\.|)([a-zA-Z][a-zA-Z0-9\.]*))/;
StringMathEvaluator.multi = (n1, n2) => n1 * n2;
StringMathEvaluator.div = (n1, n2) => n1 / n2;
StringMathEvaluator.add = (n1, n2) => n1 + n2;
StringMathEvaluator.sub = (n1, n2) => n1 - n2;
Le meilleur moyen et le plus simple est d'utiliser math.js bibliothèque. Voici quelques exemples de code démontrant comment utiliser la bibliothèque. Cliquez sur aquí pour bricoler.
// functions and constants
math.round(math.e, 3) // 2.718
math.atan2(3, -3) / math.pi // 0.75
math.log(10000, 10) // 4
math.sqrt(-4) // 2i
math.derivative('x^2 + x', 'x') // 2*x+1
math.pow([[-1, 2], [3, 1]], 2)
// [[7, 0], [0, 7]]
// expressions
math.evaluate('1.2 * (2 + 4.5)') // 7.8
math.evaluate('12.7 cm to inch') // 5 inch
math.evaluate('sin(45 deg) ^ 2') // 0.5
math.evaluate('9 / 3 + 2i') // 3 + 2i
math.evaluate('det([-1, 2; 3, 1])') // -7
// chaining
math.chain(3)
.add(4)
.multiply(2)
.done() // 14
J'ai créé une petite fonction pour analyser une expression mathématique contenant +,/,-,*. J'ai utilisé if
les déclarations que je pense switch cases
sera meilleur. Tout d'abord, j'ai séparé la chaîne de caractères dans l'opérateur et ses nombres, puis j'ai converti la chaîne de caractères en flottant, puis j'ai itéré en effectuant l'opération.
const evaluate=(mathExpStr) => {
mathExpStr.replace(/[+-\/*]$/, "");
let regExp = /\d+/g;
let valueArr = (mathExpStr.match(regExp) || []).map((val) =>
Number.parseFloat(val)
);
let operatorArr = mathExpStr.match(/[+-\/*]/g) || [];
return converter(valueArr, operatorArr)
}
const converter = (arr,operators)=>{
let arr2=[...arr]
for(let i=0;i<arr.length;i++){
let o;
if(arr2.length<2){return arr2[0]}
if(operators[i]=="+"){
o=arr2[0]+arr2[1]
arr2.splice(0, 2, o)
console.log(o,arr2, operators[i])
}
if(operators[i]=="-"){
o=arr2[0]-arr2[1]
arr2.splice(0,2, o)
console.log(o,arr2, operators[i])
}
if(operators[i]=="*"){
o=arr2[0]*arr2[1]
arr2.splice(0,2,o)
console.log(o,arr2, operators[i])
}
if(operators[i]=="/"){
o=arr2[0]/arr2[1]
arr2.splice(0,2, o)
console.log(o,arr2, operators[i])
}
}
}
// console.log(converter(valueArr, operatorArr))
console.log(evaluate("1+3+5+6-4*2/4"))
Voici une solution algorithmique similaire à celle de jMichael qui boucle sur l'expression caractère par caractère et suit progressivement gauche/opérateur/droite. La fonction accumule le résultat après chaque tour où elle trouve un caractère opérateur. Cette version ne supporte que les opérateurs '+' et '-' mais elle est écrite pour être étendue à d'autres opérateurs. Note : nous définissons 'currOp' à '+' avant de boucler car nous supposons que l'expression commence par un flottant positif. En fait, dans l'ensemble, je suppose que l'entrée est similaire à ce qui proviendrait d'une calculatrice.
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);
}
Basé sur l'ouvrage d'Aniket Kudale parse
Pour ajouter des variables contextuelles à l'expression
function parseExpr(str: string, params: any) {
const names = Object.keys(params);
const vals = Object.values(params);
return Function(...names, `'use strict'; return (${str})`)(...vals);
}
exemple
> parseExpr('age > 50? x : x/2', {x: 40, age: 46})
20
> parseExpr('age > 50? x : x/2', {x: 40, age: 60})
40
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.