pivot/other/shuntingyard.js
2020-06-12 17:13:11 -06:00

179 lines
5.2 KiB
JavaScript

const repl = require('./repl.js');
// My implementation of the algorithm from https://en.wikipedia.org/wiki/Shunting-yard_algorithm.
function shuntingYardSolve(exp) {
exp = exp.split('');
// Remove whitespace.
exp = exp.filter(e => /\S+/.test(e));
let output = [];
let opStack = [];
let inNumber = false;
let numStack = '';
for (let i = 0; i < exp.length; i++) {
if (/\d|\./.test(exp[i])) {
if (inNumber)
numStack += exp[i];
else {
inNumber = true;
numStack = exp[i];
}
} else if (/\w/.test(exp[i])) {
if (inNumber) {
inNumber = false;
output.push((numStack));
numStack = '';
}
output.push(exp[i]);
} else if (/\+|-|\*|\/|=|\^/.test(exp[i])) {
if (inNumber) {
inNumber = false;
output.push((numStack));
numStack = '';
}
if (exp[i] == '-') {
if (i == 0)
exp[i] = ':';
else if (exp[i - 1] == '(' || /\+|-|\*|\/|=|\^/.test(exp[i - 1]))
exp[i] = ':';
}
let op = {
value: exp[i]
};
if (exp[i] == ':' || exp[i] == '^')
op.precedence = 4;
else if (exp[i] == '*' || exp[i] == '/')
op.precedence = 3;
else if (exp[i] == '+' || exp[i] == '-')
op.precedence = 2;
else if (exp[i] == '=')
op.precedence = 1;
if (typeof opStack.slice(-1)[0] != 'undefined')
while (opStack.slice(-1)[0].precedence > op.precedence && opStack.slice(-1)[0].value != '(')
output.push(opStack.pop().value);
opStack.push(op);
} else if (exp[i] == '(') {
if (inNumber) {
inNumber = false;
output.push((numStack));
numStack = '';
}
opStack.push({
value: exp[i],
precedence: 5
});
} else if (exp[i] == ')') {
if (inNumber) {
inNumber = false;
output.push((numStack));
numStack = '';
}
if (typeof opStack.slice(-1)[0] != 'undefined') {
while (opStack.slice(-1)[0].value != '(')
output.push(opStack.pop().value);
if (opStack.slice(-1)[0].value == '(')
opStack.pop();
} else throw new SyntaxError('Mismatched parentheses')
}
}
if (numStack.length > 0)
output.push((numStack));
while (opStack.length > 0)
output.push(opStack.pop().value);
return output;
}
// Reverse Polish notation implemented in JavaScript.
function rpnSolve(exp, data) {
let stack = [];
for (let i = 0; i < exp.length; i++) {
let key = exp[i];
if (key.match(/\d|[A-Za-z]/))
stack.push((key));
let opItems;
let result;
switch (key) {
case '+': // Add.
opItems = stack.splice(stack.length - 2, 2);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = opItems[0] + opItems[1];
stack.push(result);
break;
case '-': // Subtract.
opItems = stack.splice(stack.length - 2, 2);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = opItems[0] - opItems[1];
stack.push(result);
break;
case '*': // Multiply
opItems = stack.splice(stack.length - 2, 2);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = opItems[0] * opItems[1];
stack.push(result);
break;
case '/': // Divide
opItems = stack.splice(stack.length - 2, 2);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = opItems[0] / opItems[1];
stack.push(result);
break;
case '^': // Exponentiation
opItems = stack.splice(stack.length - 2, 2);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = opItems[0] ** opItems[1];
stack.push(result);
break;
case ':': // Unary negation
opItems = stack.splice(stack.length - 1, 1);
opItems = opItems.map(item => {
if (typeof data[item] != 'undefined')
return parseFloat(data[item]);
else return parseFloat(item);
});
result = -opItems[0];
stack.push(result);
break;
case '=': // Assignment
opItems = stack.splice(stack.length - 2, 2);
data[opItems[0]] = parseFloat(opItems[1]);
stack.push(parseFloat(opItems[1]));
break;
}
}
return {
result: stack[0],
data: data
};
};
function solve(exp) {
let data = {};
exp = exp.split(';');
exp.forEach(e => {
console.log(shuntingYardSolve(e).join(' '));
let result = rpnSolve(shuntingYardSolve(e), data);
data = result.data;
console.log(result);
});
};
console.log('Math Solver by ElementG9:');
repl('> ', solve);