This is one of those recommended exercises. On the surface it seems a piece of cake, but bodmas was a bit of a gotcha
Two attempts here.
The first uses regular expressions (in order) to globally match squares/square-roots, multiplication/division, and addition/subtraction.
For each operation the string uses string.replace to replace the matches with the calculated values returned from calcOperation.
const operators = {
sqr: x => x * x,
sqrt: x => Math.sqrt(x),
'*': (x, y) => x * y,
'/': (x, y) => x / y,
'+': (x, y) => x + y,
'-': (x, y) => x - y
}
const float = /-?\d+(?:\.\d+)?/.source
const groupExpressions = [
new RegExp(float + 'sqrt?', 'g'), /* power/root */
new RegExp(float + '(?:[*/]' + float + ')+', 'g'), /* multiply/divide */
new RegExp(float + '(?:[-+]' + float + ')+', 'g') /* addition/subtraction */
]
function arrayFromExpr (expression) {
return expression.match(/(-?\d+(?:\.\d+)?|sqrt?|[*/+-])/g)
}
function calcOperation (expression) {
const parts = arrayFromExpr(expression)
return parts.reduce((stored, part, i, partsArray) => {
const operator = operators[part]
return (operator)
? operator(parseFloat(stored), parseFloat(partsArray[i + 1]))
: stored
})
}
function calculate (expression) {
return groupExpressions.reduce(
(result, regEx) => result.replace(regEx, match => calcOperation(match)), expression
)
}
test
console.log('-5+2*3sqr*4 =', calculate('-5+2*3sqr*4')) // -5+2*3sqr*4 = 67
console.log('5+2.5*4/2 =', calculate('5+2.5*4/2')) // 6+2.5*4/2 = 10
The second attempt I wanted to take a bit of the regular expression malarkey out of the equation.
This time the 6 operators are looped through one by one from square to subtraction.
For each iteration the whole expression is passed to calcOperation, along with the operator e.g. sqr : x => x * x and the expression with substituted values is returned.
const operators = [
['sqr', x => x * x],
['sqrt', x => Math.sqrt(x)],
['*', (x, y) => x * y],
['/', (x, y) => x / y],
['+', (x, y) => x + y],
['-', (x, y) => x - y]
]
/**
* @param expression e.g. '5*4sqr'
* @returns expression as an array e.g. ['5','*','4','sqr']
*/
function arrayFromExpr (expression) {
return expression.match(/(-?\d+(?:\.\d+)?|sqrt?|[*/+-])/g)
}
/**
* @param expression e.g. '5*4sqr'
* @param operation e.g. {sqr: x => x * x}
* @returns expression with substituted values e.g. '5*16'
*/
function calcOperation (expression, operation) {
const expr = arrayFromExpr(expression)
const result = []
while (expr.length) {
const part = expr.shift()
const operator = operation[part]
const prev = result.length - 1
if (operator) {
/* first check the arrity of the operator e.g. *(x,y) or sqr(x) */
result[prev] = (operator.length === 2)
? operator(parseFloat(result[prev]), parseFloat(expr.shift()))
: operator(parseFloat(result[prev]))
} else result.push(part)
}
return result.join('')
}
/**
* Loops through the operators in order of operation
* substituting parts of the expression that match the operation
* with a calculated value.
* @param expression e.g. -5+2*3sqr*4
* @returns final calculated value e.g. 67
*/
function calculate (expression) {
return operators.reduce(
(result, [operator, fn]) => calcOperation(result, { [operator]: fn }), expression
)
}
Same test
console.log('-5+2*3sqr*4 =', calculate('-5+2*3sqr*4')) // -5+2*3sqr*4 = 67
console.log('5+2.5*4/2 =', calculate('5+2.5*4/2')) // 6+2.5*4/2 = 10
I am sure there are glaring flaws. I am also undecided as to which is the better approach and what refactoring could be done.
For instance initially operators was an object in the second attempt
operators = {
sqr: x => x * x,
sqrt: x => Math.sqrt(x)
...
}
I would have preferred that to using a map like array, unfortunately I need the predictable ordering of an iterable array.
Confess brains are a bit frazzled so any feedback would be appreciated:)
The next stage I am considering is to add brackets into the equation.