Last active
June 18, 2022 20:36
-
-
Save Pharserror/bc337d634e9970b06b83d85176d7bbe8 to your computer and use it in GitHub Desktop.
A simple calculator that can do addition, subtraction, multiplication, division, and exponents.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Calculator.js | |
* A simple calculator written in JS | |
* Note that I wrote this with pre ECMA6 style because that's more | |
* or less what's standard at the moment - not to say I don't love | |
* and always try to use ECMA6 where I can */ | |
var Calculator = function() { | |
// Create some space in memory to store our equation | |
var equation = []; | |
// Declare all possible operations | |
var _operations = { | |
add: function(a, b) { | |
return a + b; | |
}, | |
subtract: function(a, b) { | |
return a - b; | |
}, | |
divide: function(a, b) { | |
return a / b; | |
}, | |
multiply: function(a, b) { | |
return a * b; | |
}, | |
/* I know this isn't in the requirements but most simple calculators have | |
* the ability to calculate exponents */ | |
exponent: function(a, n) { | |
var result; | |
/* If n is negative we divide, if positive we multiply */ | |
if (n < 0) { | |
result = this._negExp(a, n); | |
} else if (n > 0) { | |
result = this._posExp(a, n); | |
} else { | |
// If n is not less than or equal to 0 then it is 0 in which case we return 1 | |
return 1; | |
} | |
// Finally we return the result | |
return result; | |
}, | |
_negExp: function(a, n) { | |
/* Repeat the operation n times | |
* With a library like lodash we could simplify this to _.times(n, function() { // do stuff }); */ | |
n = n * -1; | |
// We can reuse internal methods | |
return this.divide(1, this._posExp(a, n)); | |
}, | |
_posExp: function(a, n) { | |
var i; | |
var result = a; | |
for (i = 1; i < n; i++) { | |
result = this.multiply(a, result); | |
} | |
return result; | |
} | |
}; | |
// Perform an operation with the given node | |
var _performOperation = function(currentNode) { | |
var leftNumber = Number(currentNode.left.value); | |
var rightNumber = Number(currentNode.right.value); | |
switch (currentNode.value) { | |
case "^": | |
return _operations.exponent(leftNumber, rightNumber); | |
break; | |
case "*": | |
return _operations.multiply(leftNumber, rightNumber); | |
break; | |
case "/": | |
return _operations.divide(leftNumber, rightNumber); | |
break; | |
case "+": | |
return _operations.add(leftNumber, rightNumber); | |
break; | |
case "-": | |
return _operations.subtract(leftNumber, rightNumber); | |
break; | |
default: | |
throw "operator must be one of ^, *, /, +, or -!"; | |
} | |
}; | |
// Determine precedence for a node | |
var _determinePrecedence = function(operator) { | |
// PEMDAS | |
switch (operator) { | |
case "^": | |
return 2; | |
break; | |
case "*": | |
return 1; | |
break; | |
case "/": | |
return 1; | |
break; | |
case "+": | |
return 0; | |
break; | |
case "-": | |
return 0; | |
break; | |
default: | |
throw "operator must be one of ^, *, /, +, or -!"; | |
} | |
}; | |
// Recursively create the nodes | |
var _createNodes = function(currentValue, numbersAndOperators, index) { | |
// If the node is a blank string because of the regex then we ignore it | |
if ((typeof currentValue === 'string' && currentValue.length > 0) || (typeof currentValue === 'number')) { | |
/* If we don't have an index yet then we instantiate one with 0; otherwise, | |
* it should be passed in */ | |
index = index || 0; | |
// Declare some space in memory for our left and right variables | |
var left; | |
var right; | |
// If this is the beginning of the equation then the left side is undefined | |
if (index === 0) { | |
left = undefined; | |
} else { | |
left = equation[index - 1]; | |
} | |
// Finally we create the node in the list | |
var isNumber = Number(currentValue) === Number(currentValue); | |
var node = { | |
value: currentValue, | |
left: left, | |
p: isNumber ? -1 : _determinePrecedence(currentValue), // precedence | |
i: index // index | |
}; | |
equation.push(node); | |
// If this is the end of the equation then the right side is undefined | |
if (index === numbersAndOperators.length) { | |
right = undefined; | |
} else { | |
right = numbersAndOperators[index + 1] | |
} | |
index++; | |
// Now we assign the nodes for the right side | |
!!right ? _createNodes(right, numbersAndOperators, index) : right; // right will be undefined if false | |
// We need to reference the nodes we have already created | |
node.right = equation[index]; | |
equation[index - 1] = node; | |
} | |
}; | |
var _createEquation = function(numbersAndOperators) { | |
/* What we want to do here is pass in the first node to create and then the | |
* entire list which we can use to recursively create all related nodes | |
* | |
* We also need to clear the equation out each time we calculate */ | |
equation = []; | |
_createNodes(numbersAndOperators[0], numbersAndOperators); | |
}; | |
var _adjustNodes = function(currentNode, newValue, nodes) { | |
// Check for a new left node value | |
try { | |
var prevOperator = currentNode.left.left; | |
} catch (e) { | |
var prevOperator = undefined; | |
} | |
/* We want to grab the operators adjacent to the operator we just used | |
* and update their left and right values since it has changed */ | |
if (!!prevOperator) { | |
var newLeft = nodes.find(function(node) { | |
return node.i === prevOperator.i; | |
}); | |
// We grab the index because we need to change the node in the array | |
var newLeftIndex = nodes.indexOf(newLeft); | |
} else { | |
var newLeft = undefined; | |
} | |
// Check for a new right node value | |
try { | |
var nextOperator = currentNode.right.right; | |
} catch (e) { | |
var nextOperator = undefined; | |
} | |
if (!!nextOperator) { | |
var newRight = nodes.find(function(node) { | |
return node.i === nextOperator.i; | |
}); | |
var newRightIndex = nodes.indexOf(newRight); | |
} else { | |
var newRight = undefined; | |
} | |
// Create the new node | |
var newNode = { | |
value: newValue, | |
left: newLeft, | |
right: newRight, | |
p: -1 // This should be a numerical node which has -1 precedence | |
}; | |
// Finally with the new value node we set the adjacent operators left and right values | |
if (!!newLeft) { | |
try { | |
nodes[newLeftIndex].right = newNode; | |
} catch (e) { | |
nodes[newLeftIndex].right = undefined; | |
} | |
} | |
if (!!newRight) { | |
try { | |
nodes[newRightIndex].left = newNode; | |
} catch (e) { | |
nodes[newRightIndex].left = undefined; | |
} | |
} | |
}; | |
var _calculate = function(string) { | |
// If we didn't get a string or we didn't get anything then we need to warn the user | |
if (!!string && typeof string === "string") { | |
/* Cut off any whitespace at the beginning or end of the string so that the regex below | |
* will perform better */ | |
string = string.trim(); | |
// Make some space in memory to store our result | |
var result = null; | |
// We can use a regex to split up the operations | |
var numbersAndOperators = string.split(/(\-?\d|[\-\+\*\/\^])/); | |
// Just make sure we didn't start the equation with an operator | |
if (Number(numbersAndOperators[0]) !== Number(numbersAndOperators[0])) { | |
throw "Your equation must start with an integer!"; | |
} | |
// We want to filter out all of the blank strings we got from the regex | |
numbersAndOperators = numbersAndOperators.filter(function(numberOrOperator) { | |
return (typeof numberOrOperator === 'string' && numberOrOperator.trim().length > 0); | |
}); | |
// Then we create the equation nodes | |
_createEquation(numbersAndOperators); | |
/* Now we just have to sort the order of operations array and then we can loop through that | |
* array and execute each operation in order and sum the result to gain the final answer */ | |
var orderOfOperations = equation.sort(function(a, b) { | |
/* Using a fairly complex ternary operation below to sort the array based on precedence and index | |
* that equates to something like this - just for demonstration of chaining ternary operators | |
* if (a.precedence > b.precedence) { | |
* if (a.index < b.index) { | |
* a's precedence is greater and its index is greater so it comes before b | |
* return -1; | |
* } else { | |
* a's precedence is greater but its index is not greater so it comes after b | |
* return 1; | |
* } | |
* } else if (a.precedence === b.precedence) { | |
* if (a.index < b.index) { | |
* a's precedence is equal to b's but it comes before b in the equation | |
* return -1; | |
* } else { | |
* a's precedence is equal to b's but it comes after b in the equation | |
* return 1; | |
* } | |
* } else { | |
* a's precedence is lesser than that of b's | |
* return 1 | |
* } */ | |
return a.p > b.p ? (a.i < b.i ? -1 : 1) : a.p === b.p ? (a.i < b.i ? -1 : 1) : 1; | |
}); | |
/* Now that we've built the list we can loop through it and perform all of the operations | |
* | |
* Basically, this works just like a reducer where we run each operation in order and then | |
* add the result of each operation to the overall result that we finally pass back to the user */ | |
var result; | |
/* We make sure that the length of the array has enough values to still contain operators with a simple equation | |
* if we have 3 values "1". "+", and "2" then the length of the array with operators should be greater than 2 */ | |
while (orderOfOperations.length > (((orderOfOperations.length - 1) / 2) + 1)) { | |
// We use shift so that we don't end up looping over the value nodes | |
var currentNode = orderOfOperations.shift(); | |
if (!!currentNode && currentNode.p > -1 && !!currentNode.left && !!currentNode.right) { | |
var value = _performOperation(currentNode); | |
_adjustNodes(currentNode, value, orderOfOperations); | |
result = value; | |
} | |
} | |
// Finally we have a result to give back to the user | |
return result; | |
} else { | |
throw "You must pass in a string of operations!"; | |
} | |
} | |
return { | |
calculate: _calculate | |
}; | |
}; | |
// Couple of tests | |
var myCalc = new Calculator(); | |
myCalc.calculate("1 + 2 * 4 / 2 ^ 3"); // Should give us 2 | |
/* 2 ^ 3 = 8 | |
* 2 * 4 = 8 | |
* 8 / 8 = 1 | |
* 1 + 1 = 2 */ | |
myCalc.calculate("1 + 2 * 4 / 2"); // Should give us 5 | |
/* 2 * 4 = 8 | |
* 8 / 2 = 4 | |
* 1 + 4 = 5 */ | |
myCalc.calculate("1 + 2 * 4"); // Should give us 9 | |
/* 2 * 4 = 8 | |
* 1 + 8 = 9 */ | |
myCalc.calculate("1 + 2"); // Should give us 3 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment