3

I'm trying to mock eval like function. Let's say I have an input like this:

arr = [
"1" ,
"+" ,
"(" ,
"33" ,
"+" ,
"44" ,
")" ,
"+" ,
"2" ,
"+" ,
"(" ,
"55" ,
"+" ,
"66" ,
")" ,
"="
]

Now I want to calculate it like 1+(33+44)+2+(55+66)= without using eval

I have tried something like below with no result:

let result = 0;

  for (let i = 0; i < arr.length; i++) {
    var expr = arr[i];

    if (expr == '+' || expr == "-" || expr == "*" || expr == "(" || expr == ")") {

      if (expr == "(") {
        for (let j = i; j < arr.length; j++) {

          if(expr == "+"){

          }else if(expr == "-"){

          }else if(expr == "*"){

          }else if (expr == ")") {
            i = j+1;
            break;
          }else{
            result = result + parseFloat(expr);
          }

        }
      }

    } else {
      result = result + parseFloat(expr);
    }
    console.log(result)

I removed some code which I tried as it is giving me too many errors. Can any one help me on this. Kindly comments for further details is needed. Thanks in advance.

Reporter
  • 3,897
  • 5
  • 33
  • 47
  • do you have an operator precedence other than parenteses? – Nina Scholz Aug 01 '19 at 10:51
  • @NinaScholz no Im trying only for + - * thats it. – vikram acharya Aug 01 '19 at 10:52
  • https://stackoverflow.com/questions/2276021/evaluating-a-string-as-a-mathematical-expression-in-javascript – Andreas Aug 01 '19 at 10:52
  • @Andreas i seen that. but i want a for loop or while loop function from scratch. so i tried like above. – vikram acharya Aug 01 '19 at 10:53
  • 1
    @vikramacharya better think the problem through. `1 + ( (2+3) - (1+2) + 6) - 3` this will require processing – Rajesh Aug 01 '19 at 10:53
  • @Rajesh yes you are little right. I'm trying to build one algorithm like eval. SO how to convert string + into operator + – vikram acharya Aug 01 '19 at 10:55
  • @nickzoum i need my own implementation – vikram acharya Aug 01 '19 at 10:55
  • @vikramacharya please consider this fiddle https://jsfiddle.net/hfa76cpn/ could it be a starting point? – GrafiCode Aug 01 '19 at 10:59
  • You need to write a parser for that and that's a rather broad topic. There are articles online for this but *very broadly*, you want to go over and turn each item into a token - so, transforming them into what your parser knows about. Then you turn the tokens into a structure your parser understands - from let's say you go from simply having `NumberToken(1)`, `OpToken("+")`, `NumberToken(2)` to reverse polish notation `OpToken("+") -> NumberToken(1) -> NumberToken(2)`. You then go over this representation and act on it. – VLAZ Aug 01 '19 at 11:00
  • @GrafiCode could you please tell me what i need to do their ? – vikram acharya Aug 01 '19 at 11:01
  • If you need code that evaluates arithmetic expressions, then you can push each element of the given expression to a stack and then evaluate using operator precedence rules such as BODMAS (https://www.mathsisfun.com/operation-order-bodmas.html) – Nadir Latif Aug 01 '19 at 11:01
  • @VLAZ is their any documentation? please tell me do we need to dig deep in javascript to do this. i.e mocking a mini eval like function ??? – vikram acharya Aug 01 '19 at 11:03
  • @vikramacharya you have to create it yourself, there is no built-in customisable parser in JS. You can also use a library that supports parsing either mathematical formulas or abstract expressions that you have to then resolve. But since you have to do this manually, you have to write this parser. – VLAZ Aug 01 '19 at 11:37

3 Answers3

3

I recently stumbled upon a similar problem and had to had to come up with my own solution. The code consists of 2 functions, one will convert the code into a function tree and the other will execute it. I've slightly altered it to fill your needs:

var operations = {
  '+': function(left, right) {
    return left + right;
  },
  '-': function(left, right) {
    return left - right;
  },
  '/': function(left, right) {
    return left / right;
  },
  '*': function(left, right) {
    return left * right;
  }
};

var tree = extractEquation("1+(33+44)+2+(55+66)");
console.log(resolveTree(tree));
console.log(tree);

/**
 * Runs a parsed mathematical expression
 * @param {Array<Array|{}|string>} functionTree expression in tree form
 * @returns {number} result of the expression
 */
function resolveTree(functionTree) {
  var operator = "+";
  return functionTree.reduce(function(result, item) {
    if (item instanceof Array) {
      return operations[operator](result, resolveTree(item));
    } else if (typeof item === "object") {
      operator = item.operator;
      return result;
    } else {
      return operations[operator](result, +item);
    }
  }, 0);
}

/**
 * Parses a mathematical expression
 * @param {string} expr expression in text form
 * @returns {Array<Array|{}|string>} expression in tree form
 */
function extractEquation(txt) {
  var root = [];
  var result = [].reduce.call(txt.replace(/:?(\w|\d)+\s*(\*|\/)\s*:?(\w|\d)+/g, function(priorityOperation) {
    return "(" + priorityOperation + ")";
  }).replace(/\s/g, ""), function(result, char) {
    if (char === "(") {
      var newResult = [];
      newResult.parent = result;
      result.push(newResult);
      return newResult;
    } else if (char === ")") {
      if ("parent" in result) return result.parent;
      else throw SyntaxError("Found ) but missing (");
    } else if (Object.keys(operations).includes(char)) {
      if (result.length && typeof result[result.length - 1] !== "string" && "operator" in result[result.length - 1]) throw SyntaxError("Double operator");
      result.push({
        operator: char
      });
    } else {
      if (!result.length) result.push("");
      else if (typeof result[result.length - 1] !== "string" && "operator" in result[result.length - 1]) result.push("");
      result[result.length - 1] += char;
    }
    return result;
  }, root);
  if (result !== root) throw SyntaxError("Unclosed (");
  return root;
}

Here is a more interactive example:

var operations = {
  '+': function(left, right) {
    return left + right;
  },
  '-': function(left, right) {
    return left - right;
  },
  '/': function(left, right) {
    return left / right;
  },
  '*': function(left, right) {
    return left * right;
  }
};

document.getElementById("input").addEventListener("input", function() {
  document.getElementById("tree").innerHTML = "";
  try {
    var tree = extractEquation(document.getElementById("input").value);
    document.getElementById("result").textContent = resolveTree(tree);
    document.getElementById("error").textContent = "";
    fixUITree(document.getElementById("tree"), tree);
  } catch (err) {
    document.getElementById("error").textContent = err.message || err;
    document.getElementById("result").textContent = "";
  }
});

function fixUITree(dom, tree) {
  tree.forEach(function(item) {
    if (item instanceof Array) {
      fixUITree(dom.appendChild(document.createElement("ul")), item);
    } else if (typeof item === "object") {
      dom.appendChild(document.createElement("li")).textContent = item.operator;
    } else {
      dom.appendChild(document.createElement("li")).textContent = item;
    }
  }, 0);
}

/**
 * Runs a parsed mathematical expression
 * @param {Array<Array|{}|string>} functionTree expression in tree form
 * @returns {number} result of the expression
 */
function resolveTree(functionTree) {
  var operator = "+";
  return functionTree.reduce(function(result, item) {
    if (item instanceof Array) {
      return operations[operator](result, resolveTree(item));
    } else if (typeof item === "object") {
      operator = item.operator;
      return result;
    } else {
      return operations[operator](result, +item);
    }
  }, 0);
}

/**
 * Parses a mathematical expression
 * @param {string} expr expression in text form
 * @returns {Array<Array|{}|string>} expression in tree form
 */
function extractEquation(txt) {
  var root = [];
  var result = [].reduce.call(txt.replace(/:?(\w|\d)+\s*(\*|\/)\s*:?(\w|\d)+/g, function(priorityOperation) {
    return "(" + priorityOperation + ")";
  }).replace(/\s/g, ""), function(result, char) {
    if (char === "(") {
      var newResult = [];
      newResult.parent = result;
      result.push(newResult);
      return newResult;
    } else if (char === ")") {
      if ("parent" in result) return result.parent;
      else throw SyntaxError("Found ) but missing (");
    } else if (Object.keys(operations).includes(char)) {
      if (result.length && typeof result[result.length - 1] !== "string" && "operator" in result[result.length - 1]) throw SyntaxError("Double operator");
      result.push({
        operator: char
      });
    } else {
      if (!result.length) result.push("");
      else if (typeof result[result.length - 1] !== "string" && "operator" in result[result.length - 1]) result.push("");
      result[result.length - 1] += char;
    }
    return result;
  }, root);
  if (result !== root) throw SyntaxError("Unclosed (");
  return root;
}
#result {
  color: green;
}

#error {
  color: red;
}
<input id="input" type="text">

<span id="result"></span>
<span id="error"></span>

<ul id="tree">

</ul>

Step by Step

  1. var tree = extractEquation("12 + 6 * 3");
  2. "12 + 6 * 3".replace(/:?(\w|\d)+\s*(\*|\/)\s*:?(\w|\d)+/g, function(priorityOperation) { return "(" + priorityOperation + ")"; }) sets the priority operations. result is : 12 + (6 * 3)
  3. "12 + (6 * 3)".replace(/\s/g, "") will remove the spaces, result is: 12+(6*3)
  4. [].reduce.call("12 + (6 * 3)", function(result, char){}, []); will call function(result, char){} (where result is the return value of the last iteration, starting at [], and char is each character).
  5. First iteration result = [], char = "1"
    • if ("1" === "(") => false
    • if ("1" === ")") => false
    • if (["+", "-", "*", "/"].includes("1")) => false
    • else
    • if (![].length) => true
    • [].push("")
    • [""][0] += "1"
  6. Second iteration result = ["1"], char = "2"
    • if ("2" === "(") => false
    • if ("2" === ")") => false
    • if (["+", "-", "*", "/"].includes("2")) => false
    • else
    • if (![].length) => false
    • if ([""][0] is an operator) => false
    • ["1"][0] += "2"`
  7. Third iteration result = ["12"], char = "+"
    • if ("+" === "(") => false
    • if ("+" === ")") => false
    • if (["+", "-", "*", "/"].includes("+")) => true
    • if (["12"].length && ["12"][0] is an operator) => false
    • ["12"].push({operator: "+"})
  8. Fourth iteration result = ["12", {"+"}], char = "("
    • if ("(" === "(") => true
    • ["12", {"+"}].push([])
  9. Fifth iteration result = [], char = "6"
    • if ("6" === "(") => false
    • if ("6" === ")") => false
    • if (["+", "-", "*", "/"].includes("6")) => false
    • else
    • if (![].length) => true
    • [].push("")
    • [""][0] += "6"
  10. Sixth iteration result = ["6"], char = "*"
    • if ("*" === "(") => false
    • if ("*" === ")") => false
    • if (["+", "-", "*", "/"].includes("*")) => true
    • if (["6"].length && ["6"][0] is an operator) => false
    • ["6"].push({operator: "*"})
  11. Seventh iteration result = ["6", {"*"}], char = "3"
    • if ("3" === "(") => false
    • if ("3" === ")") => false
    • if (["+", "-", "*", "/"].includes("3")) => false
    • else
    • if (!["6", {"*"}].length) => false
    • if (["6", {"*"}] is an operator) => true
    • ["6", {"*"}].push("")
    • ["6", {"*"}] += "3"
  12. Eight iteration result = ["6", {"*"}, "3"], char = ")"
    • if (")" === "(") => false
    • if (")" === ")") => true
    • return ["12", {"+"}, ["6", {"*"}, "3"]]
nick zoum
  • 7,216
  • 7
  • 36
  • 80
  • 1
    Can you please add a more complex expression like: `1 + ( (2+3) - (1+2) + 6) - 3`? – Rajesh Aug 01 '19 at 11:06
  • @nickzoum this is a very standard answer for my question. but seriously i'm not even understanding the syntax what you have wrote. i though js is very simple. now after looking your code I'm struggling to figure it out how the code was working. anyhow i marked it as ANSWER. – vikram acharya Aug 01 '19 at 11:27
  • @nickzoum can you kindly write some comments on how the code flow works. its a request – vikram acharya Aug 01 '19 at 11:28
  • 1
    @vikramacharya I'm sorry it took so long, I was trying to make the code as understandable as I could. I've also added step by step explanations of the `extractEquation` function. I trust you can understand the `resolveTree` function, if it proves harder than I expected, please let me know. – nick zoum Aug 01 '19 at 13:52
  • @nickzoum I think this is the perfect answer for newbies. but since the above answer was little hard to understand but the code is less. that's the reason i marked it as answer. but frankly I'm using your concept to my project. Its better to use understandable code rather than complex code. thank you for detailed explanation. – vikram acharya Aug 01 '19 at 16:14
3

You could take a simplified approach with using a nested array as stack for the levels, indicated by parentheses and use only binary operator and omit =, because this not necessary. At the end all open calculations are made.

This is an object with arrow function and uses unary plus + for getting a number from a string. Then other operators does not need this, because the oparator coerces the value to a number by using these operators. + works for strings as well.

{
    '+': (a, b) => +a + +b
}

The rest is simple. If an open parentesis is found, a new level of the stack is opend and all tokens are collected to this level until a closing parenthesis is found, then the level is calculated and the value is returned to the level before.

calculate takes three items of the stack and performs an operation with left and right value and the middle operator. The result is returned in the array at index zero.

For unary minus, you could check it and remove this items and update the next value, depending on the count of found minus.

function evaluate(string) {

    function calculate(array) {

        function removeUnaryMinus(array, offset) {
            var nonMinusIndex = array.findIndex((s, i) => i >=offset && s !== '-');
            array.splice(offset, nonMinusIndex - offset);
            if (nonMinusIndex % 2) array[offset] *= -1;
        }

        removeUnaryMinus(array, 0);
        while (array.length > 2) {
            removeUnaryMinus(array, 2);                    
            array.splice(0, 3, ops[array[1]](array[0], array[2]));
        }
        return array[0];
    }

    var ops = {
            '+': (a, b) => +a + +b,
            '-': (a, b) => a - b,
            '*': (a, b) => a * b,
            '/': (a, b) => a / b
        },
        stack = [[]],
        level = 0;

    string
        .split(/([+\-*\/()])/)
        .filter(s => s && s !== ' ')
        .forEach(token => {
            if (!token || token === '=') return;
            if (token === '(') {
                ++level;
                stack[level] = [];
                return;
            }
            if (token === ')') {
                --level;
                stack[level].push(calculate(stack[level + 1]));
                return;
            }
            stack[level].push(token);
        });
    return calculate(stack[0]);
}

console.log(evaluate('12 + 3'));                  //   15
console.log(evaluate('-(12 + -23) * (-4 + -7)')); // -121
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • i dont think your answer will work for any negative value inside brackets. have you observed that. try this let exp = '10 + ( 30 + 40 ) + 20 + ( 50 + 60 ) = '; – vikram acharya Aug 01 '19 at 11:19
  • where do you see a negative value? – Nina Scholz Aug 01 '19 at 11:20
  • sry this is the expression '10 + ( 3 * 4 ) + 20 + ( 50 - 60 ) = '; – vikram acharya Aug 01 '19 at 11:30
  • this is also a perfect and minified way. can you kindly tell me what is this "stack = [[]]," and this " '+': (a, b) => +a + +b, " I'm into javascript for about few months now and I'm not getting the exact way of code reference to learn online. can you please add comments so that newbies can also understand your code. – vikram acharya Aug 01 '19 at 11:46
  • can you please tell me the logic of your code. like pseudo code. what steps you are doing. – vikram acharya Aug 01 '19 at 12:02
  • please check this let `exp1 = '20 * ( -2 * -3 ) = ';` the code will not work for negative multiplication. – vikram acharya Aug 01 '19 at 12:45
  • ok i got the error, ["2", "*", "-" ,"3"] for this input. here, after star sign minus will come. Is their any way to handle this. as I'm generating input array from string like this. because we cannot expect strings always separated by " " in order to use split. it might be 2+3+ 5( 4 -2 +1- 3) i.e in consistent – vikram acharya Aug 01 '19 at 17:21
  • that's great, this is the perfect replicate function for built in enum. So, from now onwards everyone will ask this question in interviews i think. thank you – vikram acharya Aug 02 '19 at 04:57
  • May i know is this answer uses recursion or not. I'm confused. recursion means calling same function again and again until it does some work. can you please give one more answer if this answer is not uses recursion. it will help lot of freshers like me to crack interviews. please give some theory of the answer about recursion above. Thank you in advance. – vikram acharya Aug 04 '19 at 08:52
  • 1
    it uses no recursion, but a simple iteration of tokens and a nested array for collecting token who are in nested parentheses. – Nina Scholz Aug 04 '19 at 08:56
  • can you give some ideas on how to do this by recursion. I'm trying it from past 2 days. I got stuck in identifying 3 parameter to do math operation. Since, this was one type of answer, a recursion type of answer will complete this question fully. So then on, no other answers are needed for this question. – vikram acharya Aug 04 '19 at 12:51
  • @vikramacharya, instead of using the comments for an changed question, you could ask a new one and present your code in a new question. – Nina Scholz Aug 04 '19 at 14:38
1
function addbits(s){
    var total= 0, s= s.match(/[+\-]*(\.\d+|\d+(\.\d+)?)/g) || [];
    while(s.length){
        total+= parseFloat(s.shift());
    }
    return total;
} 

   let arr = [
    "1" ,
    "+" ,
    "(" ,
    "33" ,
    "+" ,
    "44" ,
    ")" ,
    "+" ,
    "2" ,
    "+" ,
    "(" ,
    "55" ,
    "+" ,
    "66" ,
    ")" ,
    "="
    ]
let arr_string = arr.join(''); // you will get here "1+(33+44)+2+(55+66)="
let finalStr = arr_string.replace('=','');

addbits(finalStr); // output will be 201
SHUBHAM SINGH
  • 371
  • 3
  • 11