Skip to content

Instantly share code, notes, and snippets.

@Pharserror
Last active June 18, 2022 20:36
Show Gist options
  • Save Pharserror/bc337d634e9970b06b83d85176d7bbe8 to your computer and use it in GitHub Desktop.
Save Pharserror/bc337d634e9970b06b83d85176d7bbe8 to your computer and use it in GitHub Desktop.
A simple calculator that can do addition, subtraction, multiplication, division, and exponents.
/* 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