UNPKG

@enigmaoffline/node-exp-solver

Version:

Mathematical expression solver / Reverse Polish Notation calculator for NodeJS

150 lines (129 loc) 5.14 kB
import { Aux } from "./utils/aux"; class Solver { /** * Tokenize an infix-notated expression string * @param {string} exp - mathematical expression * @returns {Array<string>} - tokenized expression */ static tokenize = (exp: string): Array<string> => { let res: Array<string> = []; let term: string = ''; exp .replace(/ /g, '') .split('') .forEach((ch: string, index: number) => { switch (true) { // negative numbers case ch === '-' && index === 0: case ch === '-' && term === '' && Aux.isOP(res[res.length - 1]): case ch === '-' && term === '' && res[res.length - 1] === '(': term = '-'; break; // operands case Aux.isOP(ch): case Aux.isPA(ch): if (term !== '') res.push(term); res.push(ch); term = ''; break; // numbers default: term += ch; } }); // push remaining term to result if (term !== '') res.push(term); return res; } /** * Converts an infix-notated expression to reverse polish notation * @param {Array<string>} exp - tokenized infix-notated expression * @returns {Array<string>} - converted RPN expression */ static toRPN = (exp: Array<string>): Array<string> => { let outStack: Array<string> = []; let opStack: Array<string> = []; while (exp.length > 0) { let head: string = exp.splice(0, 1)[0]; switch (true) { // handle parentheses case Aux.isPA(head): if (head === '(') opStack.push(head); else { while (opStack[opStack.length - 1] !== '(') outStack.push(opStack.pop() || ''); opStack.pop(); } break; // handle operand case Aux.isOP(head): while ( opStack.length > 0 && ( Aux.precMap[opStack[opStack.length - 1]] > Aux.precMap[head] || ( Aux.precMap[opStack[opStack.length - 1]] === Aux.precMap[head] && Aux.assoMap[head] === 0 ) )) outStack.push(opStack.pop() || ''); opStack.push(head); break; // handle number default: outStack.push(head); } } // push remaining while (opStack.length > 0) outStack.push(opStack.pop() || ''); return outStack; } /** * Evaluates an infix-notated expression * @param {Array<string>} exp - tokenzied infix-notated expression * @returns - evaluated value */ static solve = (exp: Array<string>): number => Solver.solveRec(Solver.toRPN(exp)); /** * Evaluates a reverse polish notated expression * @param {Array<string>} rpn - tokenized rpn expression * @returns {number} - evaluated value */ static solveRPN = (rpn: Array<string>): number => Solver.solveRec(rpn); // private methods /** * Recursively solve an RPN expression * @param {Array<string>} rpn - original RPN expression * @param {Array<number>} stack - memory stack used for recursive computation * @returns {number} - evaluated value */ private static solveRec = (rpn: Array<string>, stack: Array<number> = []): number => { if (rpn.length === 0) return stack[0]; else { let head: string = rpn.splice(0, 1)[0]; if (Aux.isOP(head)) { let num1: number = stack.splice(stack.length - 1, 1)[0]; let num2: number = stack.splice(stack.length - 1, 1)[0]; // recursive calculation switch (head) { case '+': return Solver.solveRec(rpn, [...stack, num2 + num1]); case '-': return Solver.solveRec(rpn, [...stack, num2 - num1]); case '*': return Solver.solveRec(rpn, [...stack, num2 * num1]); case '/': return Solver.solveRec(rpn, [...stack, num2 / num1]); case '^': return Solver.solveRec(rpn, [...stack, Math.pow(num2, num1)]); } } return Solver.solveRec(rpn, [...stack, parseFloat(head)]); } } } export = Solver;