Chaining Multiple operators in Javascript calculator - javascript

I cant seem to get my calculator to chain multiple operators together for example 5x5x5 should equal 125 but it just calculates 5x5 and will no longer calculate decimal numbers. Could anyone point me in the correct direction.
I have tried to assign the result to the firstNum but it seems to break the code.
button.forEach((button) =>
button.addEventListener('click', ()=> displayTotal(button.innerHTML)));
clearBtn.addEventListener('click', clear);
decmialBtn.addEventListener('click', appendDecimal);
functionBtn.forEach((button) =>
button.addEventListener('click', () => setOperator(button.innerHTML)));
equalsBtn.addEventListener('click', calculate)
let firstNum = "";
let secondNum = "";
let operatorSelection = "null";
let result = "";
function add(a, b){
return a + b
}
function subtract(a,b){
return a - b
}
function divide (a,b)
{
return a / b
}
function times (a,b){
return a*b
}
function operator(operator, numa, numb) {
switch(operator){
case '+':
return add(numa, numb);
break;
case '-':
return subtract(numa, numb);
break;
case '/':
return divide(numa,numb);
break;
case '*':
return times(numa,numb)
break;
}
}
function displayTotal (number){
userDisplay.value += number;
}
function clear (){
userDisplay.value = "";
}
function appendDecimal(){
userDisplay.value += ".";
}
function setOperator(operator){
firstNum = parseInt(userDisplay.value);
operatorSelection = operator;
result = firstNum;
clear()
}
function calculate(){
for (i = 0; i < 100; i++)
secondNum = parseInt(userDisplay.value);
result = operator(operatorSelection, firstNum, secondNum);
userDisplay.value = result;
[i];
}

For a simple JS calculator you could use the eval() function and it will handle all the operators for you taking PEMDAS in consideration, but you have to ensure the inpput is valid or it you won't work.
I have one on my Github if want an example:
https://github.com/gustavo-shigueo/calculator
Note: don't worry about the regexes I used, they are there just to prevent the user from typing stuff that would break the calculator

Here is an example of how I would do it:
operations = [
["*", (a, b) => +a * +b],
["/", (a, b) => +a / +b],
["+", (a, b) => +a + +b],
["-", (a, b) => +a - +b],
];
const calc = calcString => {
const split = [...calcString].reduce((prev, cur) => Number.isInteger(+cur) && Number.isInteger(+prev[prev.length - 1]) ? [...prev.slice(0, -1), prev[prev.length - 1] + cur] : [...prev, cur], [""]);
operations.forEach(([operator, funct]) => {
for (let i = 1; i < split.length - 1; i++) {
if (split[i] === operator) {
split[i - 1] = funct(split[i - 1], split[i + 1]);
split.splice(i, 2);
i--;
}
}
})
return split;
};
It doesn't support numbers with a dot/comma in them (e.g. 3.13) and no braces.
some explanation:
const split, I split the given string in numbers and operations, "3+45*4" would result in ["3", "+", "45", "*", "4"].
go over each operation in the given order to first do the "point calculation" and then + and -.
we go over the in 1. created array and look for our operation.
if we find one, we lool at the number before and after the operation and calculate the result given the 2 numbers and our operation type.
we overwrite those 3 elements with the result of our calculation.
we are done if all operations are done
I also have a more advanced version if you are interested

Related

Math operations from string using Javascript

I am trying to find a simple way to perform a set of javascript math operations without using eval() function. Example: 1+2x3x400+32/2+3 and it must follow the PEMDAS math principle. This is what I have, but it doesn't work exactly it should.
function mdas(equation) {
let operations = ["*", "/", "+", "-"];
for (let outerCount = 0; outerCount < operations.length; outerCount++) {
for (let innerCount = 0; innerCount < equation.length; ) {
if (equation[innerCount] == operations[outerCount]) {
let operationResult = runOperation(equation[innerCount - 1], operations[outerCount], equation[innerCount + 1]);
var leftSideOfEquation = equation.substr(0, equation.indexOf(innerCount - 1));
var rightSideOfEquation = equation.substr(equation.indexOf(innerCount), equation.length);
var rightSideOfEquation = rightSideOfEquation.replace(rightSideOfEquation[0],String(operationResult));
equation = leftSideOfEquation + rightSideOfEquation;
innerCount = 0;
}
else {
innerCount++;
}
}
}
return "Here is it: " + equation; //result of the equation
}
If you don't want to use a complete library like mathjs - and you don't want to tackle creating your own script which would involve: lexical analysis, tokenization, syntax analysis, recursive tree parsing, compiling and output...
the simplest banal suggestion: Function
const calc = s => Function(`return(${s})`)();
console.log( calc("1+2*3*400+32/2+3") ); // 2420
console.log( calc("-3*-2") ); // 6
console.log( calc("-3 * + 1") ); // -3
console.log( calc("-3 + -1") ); // -4
console.log( calc("2 * (3 + 1)") ); // 8
My take at a custom MDAS
Here I created a Regex to retrieve operands and operators, accounting for negative values: /(-?[\d.]+)([*\/+-])?/g.
Firstly we need to remove any whitespace from our string using str.replace(/ /g , "")
Using JavaScript's String.prototype.matchAll() we can get a 2D array with all the matches as [[fullMatch, operand, operator], [.. ] we can than further flatten it using Array.prototype.flat()
Having that flattened array, we can now filter it using Array.prototype.filter() to remove the fullMatch -es returned by the regular expression and remove the last undefined value.
Define a calc Object with the needed operation functions
Iterate over the MDAS groups */ and than +- as regular expressions /\/*/ and /+-/
Consume finally the array of matches until only one array key is left
let str = "-1+2 * 3*+400+-32 /2+3.1"; // 2386.1
str = str.replace(/ +/g, ""); // Remove all spaces!
// Get operands and operators as array.
// Remove full matches and undefined values.
const m = [...str.matchAll(/(-?[\d.]+)([*\/+-])?/g)].flat().filter((x, i) => x && i % 3);
const calc = {
"*": (a, b) => a * b,
"/": (a, b) => a / b,
"+": (a, b) => a + b,
"-": (a, b) => a - b,
};
// Iterate by MDAS groups order (first */ and than +-)
[/[*\/]/, /[+-]/].forEach(expr => {
for (let i = 0; i < m.length; i += 2) {
let [a, x, b] = [m[i], m[i + 1], m[i + 2]];
x = expr.exec(x);
if (!x) continue;
m[i] = calc[x.input](parseFloat(a), parseFloat(b)); // calculate and insert
m.splice(i + 1, 2); // remove operator and operand
i -= 2; // rewind loop
}
});
// Get the last standing result
console.log(m[0]); // 2386.1
It's a little hacky, but you can try something like this:
var eqs = [
'1+2*3*4+1+1+3',
'1+2*3*400+32/2+3',
'-5+2',
'3*-2',
];
for(var eq in eqs) { console.log(mdas(eqs[eq])); }
function mdas(equation) {
console.log(equation);
var failsafe = 100;
var num = '(((?<=[*+-])-|^-)?[0-9.]+)';
var reg = new RegExp(num + '([*/])' + num);
while(m = reg.exec(equation)) {
var n = (m[3] == "*") ? m[1]*m[4] : m[1]/m[4];
equation = equation.replace(m[0], n);
if(failsafe--<0) { return 'failsafe'; }
}
var reg = new RegExp(num + '([+-])' + num);
while(m = reg.exec(equation)) {
var n = (m[3] == "+") ? 1*m[1] + 1*m[4] : m[1]-m[4];
equation = equation.replace(m[0], n);
if(failsafe--<0) { return 'failsafe'; }
}
return equation;
}

javascript: passing basic math operations to function

I am programming a calculator that takes a string representing a calculation and outputs the result (like the eval function).
At some point in my code I define the meaning of the different operations like this:
const _ops = [
[
["^", (a, b) => (+a) ** +b],
],
[
["*", (a, b) => +a * +b],
["/", (a, b) => +a / +b],
["%", (a, b) => +a % +b],
],
[
["+", (a, b) => +a + +b],
["-", (a, b) => +a - +b],
],
];
As you can see I am repeating the function part every time while only one character changes (except for the first time...)
(a, b) => +a ${operation} +b
Do you have any idea how I could do this without repeating the function declaration every time?
PS: If you can think of a better title, feel free to change it.
No, this is not possible in JavaScript without eval. This is because there is no way to treat an operator as a function. This is not the case e.g. in Haskell, where (*) represents the multiplication function, which you can carry around as a value, apply a value to get a partially-applied operator, and apply another value to get the result.
Using eval would look kind of like this.
const dynamicCalculation = (a ,b, operator) => {
if (operator === "^") operator = "**"
return eval("${+a} ${operator} ${+b}")
}
usage:
dynamicCalculation(1,2,"+") // => 3
dynamicCalculation(1,2,"-") // => -1
dynamicCalculation(1,2,"*") // => 2
switch:
const dynamicCalculation = (a ,b, operator) => {
let output;
switch(operator){
case "+":
output = a + b;
break;
case "-":
output = a - b;
break;
case "*":
output = a * b;
break;
case "/":
output = a / b;
break;
case "^":
output = a ** b;
break;
case "%":
output = a % b;
break;
}
return output
}

Concatenate operator in math operation in Javascript

I want to create a random generator of math operations. I'm trying with ASCII codes but what happens is that it just concatenate the operands and operators as a String. Anyone has a suggestion for this?
let a = Math.floor(Math.random()*100)
let b = Math.floor(Math.random()*100)
let ascCode = Math.floor(Math.random()* (46 - 42)) + 42
let op = String.fromCharCode(ascCode)
let c = a + `${op}` + b;
console.log(c)
You can use eval:
The eval() function evaluates JavaScript code represented as a string.
let a = 1
let b = 2
let ascCode = Math.floor(Math.random()* (46 - 42)) + 42
let op = String.fromCharCode(ascCode)
let c = eval(a + `${op}` + b);
console.log(c)
But eval can be troublesome.
eval() is a dangerous function, which executes the code it's passed with the privileges of the caller. If you run eval() with a string that could be affected by a malicious party, you may end up running malicious code on the user's machine with the permissions of your webpage / extension. More importantly, a third-party code can see the scope in which eval() was invoked, which can lead to possible attacks in ways to which the similar Function is not susceptible.
eval() is also slower than the alternatives, since it has to invoke the JS interpreter, while many other constructs are optimized by modern JS engines.
msdn
Another solution would be to use some if:
let a = 1
let b = 2
let ascCode = Math.floor(Math.random()* (46 - 42)) + 42
let op = String.fromCharCode(ascCode)
let c = 0;
if (op == "*")
c = a + b;
if (op == "+")
c = a + b;
if (op == "-")
c = a - b;
/*etc*/
console.log(c)
or even a map where keys are operators and values are functions:
let a = 1
let b = 2
let ascCode = Math.floor(Math.random()* (46 - 42)) + 42
let op = String.fromCharCode(ascCode)
let operators = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b, // handle zero!
'%': (a, b) => a % b // handle zero!
};
console.log(operators[op](a,b))
This solution is similar to things discussed in the comments. It is a different, non-eval way of generating random math operations:
const ops = {
'+': (a, b) => a + b,
'-': (a, b) => a - b,
'*': (a, b) => a * b,
'/': (a, b) => a / b,
'%': (a, b) => a % b,
'>': (a, b) => a > b,
'<': (a, b) => a < b,
// etc
}
const randomOperation = ((ops) => {
const keys = Object.keys(ops)
const randomOpKey = () => keys[Math.floor(Math.random() * keys.length)]
const evalOp = (key, a, b) => ops[key](a, b)
return () => {
const op = randomOpKey()
const a = Math.floor(Math.random() * 100)
const b = Math.floor(Math.random() * 100)
return {
expression: `${a} ${op} ${b}`,
result: evalOp(op, a, b)
}
}
})(ops)
// demo
for (let i = 0; i < 20; i++) {
let {expression, result} = randomOperation()
console.log(`${expression} = ${result}`)
}
Note that what randomOperation returns is objects with two properties: expression as a string, and result as a value, which will be numeric or boolean. The demo code shows one way to use it. You have to manually maintain the list of operations, which is different than with the eval solutions.
I don't know what better suits your needs, but this should show that there are reasonable non-eval solutions possible.
What you could use (though this is definitely not a good practice), is eval(), which evaluates a string as javascript code.
(Another word of warning, eval is evil and shouldn't be used in production)
let a = Math.floor(Math.random()*100)
let b = Math.floor(Math.random()*100)
let ascCode = Math.floor(Math.random()* (46 - 42)) + 42
let op = String.fromCharCode(ascCode)
let c = eval(a + `${op}` + b);
console.log(c)
You should use built-in function eval()
let operators = ['+','-','*','/','%'];
let number1 = 5;
let number2 = 4;
function getRandomOperation(){
let randOpeator = operators[Math.floor(Math.random() * operators.length)];
return eval(`${number1} ${randOpeator} ${number2}`);
}
console.log(getRandomOperation());
You should evalute expression, using javascript eval function. It expects string as input.
const result = eval(c);
console.log(result);
Reference
I presume it happens because you are using concatenation when one from concatenated values is a string:
let op = String.fromCharCode(ascCode)
To obtain a number under variable c you have to make sure that ${op} is number as well.

implementing operator precedence in arithmetic operation

I have a code but it is not working for operator precedence. It is normally executing from first to last operator entered in a string.
It gives correct answer while string follow precedence rule(eg."12 * 10 / 5 + 10 - 1" = 33 <- correct answer). But it gives wrong answer while string doesn't follow precedence rule(eg. "15 + 10 * 5 / 10 - 1" = 49 <- wrong answer. actual answer is 19)
code:
var str="12 * 10 / 5 + 10 - 1";
var res=[],n1=[],num=[],op=[],o1=[];
var result;
res= str.split(" ");
console.error("res",res);
document.write(res.join(",")+"<br>");
for(var i=0;i<res.length;i++)
{
if(i%2==0)
{
num.push(parseInt(res[i]));
}
else
{
op.push(res[i]);
}
}
document.write(num+"<br>");
document.write(op+"<br>");
var myFunction = function(num1,num2,oper){
var j;
if(oper=='-'){
j = num1-num2;
return j;
}else if(oper=='+'){
j = num1+num2;
return j;
}else if(oper=='*'){
j = num1*num2;
return j;
}else if(oper=='/'){
j = num1/num2;
return j;
}else{
j = 0;
return j;
}
}
var x=num[0];
for(var i=1; i<num.length; i++){
x = myFunction(x,num[i],op[i-1]);
}
document.write("result of "+str+" is "+x+"<br>");
Here's one way to do it. I like because this is how we craft a parser. There are other approaches as mentioned, usual when you're using a parser generator like lex/yacc.
As you have noticed, there are times we need to save an intermediate result in order to process another part of an expression. This way, called a recursive descent parser, uses the environment's own stack to save the temp results. Another way is to use maintain a stack yourself, but that's not much valuable.
This is also very flexible. It's pretty easy to extend the grammar to different precedence levels. Change parseFactor below to parse negation and parentheses.
function parse(expression) {
return expression.split(' ')
}
function consumeNumber(tokens) {
const token = tokens.shift()
return parseInt(token)
}
function consumeOp(tokens, allowed) {
const token = tokens[0]
if (allowed.indexOf(token) >= 0) {
tokens.shift()
return token
}
}
function parseFactor(tokens) {
return consumeNumber(tokens)
}
function parseTerm(tokens) {
let value = parseFactor(tokens)
let op
while (op = consumeOp(tokens, ['*', '/'])) {
let nextVal = parseFactor(tokens)
switch (op) {
case '*':
value *= nextVal
break
case '/':
value /= nextVal
break
}
}
return value
}
function parseExpression(tokens) {
let value = parseTerm(tokens)
let op
while (op = consumeOp(tokens, ['+', '-'])) {
let nextVal = parseTerm(tokens)
switch (op) {
case '+':
value += nextVal
break
case '-':
value -= nextVal
break
}
}
return value
}
function evaluate(expression) {
const tokens = parse(expression)
return parseExpression(tokens)
}
evaluate('15 + 10 * 5 / 10 - 1')
Here's a hacked together (read: shouldn't actually be used anywhere real without extra work / error checking) object which gets the correct answer for both test cases. I'm sure this could be improved on and there's likely better algorithms for this. As per my comment it just scans for * or / and processes that calculation, then generates a new number/operator set including that result. I mainly just wrote it for fun.
I haven't bothered with the code to try and parse the string, this just takes an array of numbers and an array of operators.
(Also I get 11.5 as the incorrect result for your second test, both manually and running your code)
<script>
myCalc = {
getResult: function(n, o){
this.numbers = n;
this.operators = o;
// keep going until our list of numbers is just the final result
while( this.numbers.length > 1 )
this.findNext();
return this.numbers[0];
},
findNext: function(){
var opIndex = 0;
// find the next * or / operator
for(i=0;i<=this.operators.length;i++){
if( this.operators[i] == '*' || this.operators[i] == '/' ){
opIndex = i;
break;
}
}
var opResult = this.doCalc(this.operators[opIndex], this.numbers[opIndex], this.numbers[opIndex+1]);
// update our main numbers list and splice out the operator we just processed
// replace "x" with the result, and splice "y"
this.numbers[opIndex] = opResult;
this.numbers.splice(opIndex + 1, 1);
this.operators.splice(opIndex, 1);
console.log(this.numbers);
console.log(this.operators);
},
doCalc: function(op,x,y){
switch(op){
case '*':
return x * y;
case '/':
return x / y;
case '+':
return x + y;
case '-':
return x - y;
}
return 0;
}
};
console.log(myCalc.getResult([15,10,5,10,1], ['+','*','/','-']));
console.log(myCalc.getResult([12,10,5,10,1], ['*','/','+','-']));
</script>
Since nobody mentioned it before, you could simply process your string with eval function:
var str="15 + 10 * 5 / 10 - 1";
console.log(eval(str));
// logs 19
If the argument is an expression, eval() evaluates the expression

looping through math operators using eval

var toyProblem = function () {
var sol= 0;
var operators = ['+','-','*','/'];
console.log(sol)
for(var i in arguments){
for(var j in operators){
sol = eval(sol + (operators[j]) + arguments[i]);
}
}
return sol;
}
toyProblem(6, 0, 10, 3); //6 + 0 - 10 * 3 === -12)
I'm trying to loop through 4 math operators for an unknown number of input values. I'm thinking of using eval in a nest for loop as a way of going through both the unknown number of arguments while also changing the math operator. At the bottom is the solution that I want to arrive at. Is this a good way of going about this problem or am I barking up the wrong tree?
var toyProblem = function () {
var sol_str='';
var operators = ['+','-','*','/'];
for(var i in operators){
var prev_operator=(i-1);
if(sol_str!=''){sol_str+=operators[prev_operator];}
sol_str +=arguments[i];
}
console.log(sol_str);
return eval(sol_str);
}
console.log(toyProblem(6, 0, 10, 3));
Nesting the 2 loops will result in doing 6 + 6 - 6 * 6 / 6 + 0 - 0 * 0 / 0 + 10 - 10 * 10 / 10 + 3 - 3 * 3 / 3
I didn't find a way to do this without eval as looping through operations one by one would modify the operators priority so this is what I propose : Building an operation 'query' to be eval'd and returned.
Hope this helps
var toyProblem = function () {
var operation = '';
var operators = ['+','-','*','/'];
var args = Array.from(arguments);
args.forEach(function (arg, index) {
if (index > 0) {
operation += operators[index - 1];
}
operation += arg;
});
return eval(operation);
}
console.log(6 + 0 - 10 * 3);
console.log(toyProblem(6, 0, 10, 3)); //6 + 0 - 10 * 3 === -24)
Let's decompose the problem. You have a variadic function that accepts unknown number of arguments and applies an operator to each next argument depending on the index of that element.
Because the number of arguments can be greater than the number of operators, it's appropriate to use modulo operator to infinitely loop through the array of operators while going once through the list of arguments.
The eval operation takes a string, evaluates it, and returns the result of evaluation of the expression that string represents. So you're on the right track. But because eval function takes a string as the first argument, I'd recommend using template literals, it's supported in almost all browsers natively and doesn't need to be transpiled into good old ES5.
The function then would look like this:
function toyProblem(first = 0, ...args) {
const operators = ['+', '-', '*', '/'];
let sol = first;
for (let i in args) {
sol = eval(`${sol} ${operators[i % operators.length]} ${args[i]}`);
}
return sol;
}
However, as there is recommended in the comments, using eval isn't something you'd like to ship to users. Instead, I'd suggest using functions. Functions in Javascript are first-class citizens, so you can pass them as an argument.
Imagine that you have a function (a, b) => a + b instead of just a string "+". The code would then look like this:
function toyProblem(first = 0, ...args) {
const operations = [
(a, b) => a + b,
(a, b) => a - b,
(a, b) => a * b,
(a, b) => a / b,
];
let sol = first;
for (let i in args) {
sol = operations[i](sol, args[i]);
}
return sol;
}
You could go even further and make the function universal in terms of possible operations. Have fun!
Since you are hard coding the names of the operators you might as well hard code the functions and avoid eval. You put the functions into an array that will let you loop through. Then you can just reduce through the arguments with a simple one-liner, which will handle any amount of arguments:
const op = [
(a, b) => a + b,
(a, b) => a - b,
(a, b) => a * b,
(a, b) => a / b
]
function prob(...args){
return args.reduce((curr, acc, idx) => op[(idx - 1) % op.length](curr, acc))
}
console.log(prob(6, 0, 10, 3))
console.log(prob(6, 0, 10, 3, 20, 11, 15, 100))
To get product -12 the first three parts of the expression need to be evaluated within parentheses, else the result will be -24. You can use String.prototype.replace() to replace "," characters after calling .toString() on input array, replace the "," with the operator, return the expression (6 + 0 - 10) * 3 from Function() constructor
var toyProblem = function () {
var operators = ['+','-','*'];
var opts = operators.slice(0);
var n = [...arguments];
var res = n.toString().replace(/,/g, () => opts.shift());
var index = res.indexOf(operators[operators.length -1]);
return new Function(`return (${res.slice(0, index)})${res.slice(index)}`)();
}
var product = toyProblem(6, 0, 10, 3);
console.log(product);

Categories