user3370111 user3370111 - 2 months ago 12
jQuery Question

Regex to split arithmetic expression into parts

I am creating a calculator in which I get a string from an input field. That string could for example be

2+3*9/3-6
.

Now I want to perform multiplication and division operations first,
so I will get
2+ 9 -6
where
9
will be the answer of the higher precedence operations.

So far I have done this. It is a complete function:

function calculateResult(){
var higher = /(\d+[*\/]\d+)/g;
calstr = $('#input-display').val();
var m = '*';
var res = calstr.split(higher);
console.log(res.length+ ' : ' + res);
// console.log(res);
if(res.length > 2){
console.log('split success');
var index = 0;
do{
for(var i = 1; i < res.length; i+=2){
var a = res[i].split(/([0-9\.]+)/g);
console.log('before calculation : '+calstr);
var regex = RegExp('(?:.*?(\\d+[*\/]\\d+)){' + i + '}');
console.log('regex generated : '+regex);
if(a[2] == '*'){
calstr = calstr.replace(regex , Number(a[1])*Number(a[3]));
}
else if(a[2] == '/'){
calstr = calstr.replace(regex , Number(a[1])/Number(a[3]));
}
console.log('replaced result : ' + calstr);
console.log('replaced result at : ' + calstr.search(regex));
}
// console.log('result : ' + calstr);
console.log('multiplication complete');
res = calstr.split(higher);
console.log('result is : '+res);
}while(res.length > 2);
}
else if(res.length == 1)
console.log('split failed');
}


When I try to replace the result inside a string, the replace succeeds. But on some several inputs it replaces the entire string that was passed as argument to this function, which is not what I want.

What did I do wrong?

If still something is unclear, I am happy to give clarifications.

After multiplication and division runs smoothly, I plan to change these regular expressions, to add support for addition and subtraction. It would use the same code but with a next run for the lower precedence operators.

Answer

This regex is the cause of the wrong result you get:

RegExp('(?:.*?(\\d+[*\/]\\d+)){' + i + '}');

It should not match the .*?, as that will make you remove all preceding characters in the replace that follows.

Also, the \ is unnecessary. If you really wanted to escape it for the regular expression, you would have to double it, because it also needs to be escaped in the quoted string. But since the forward slash does not occur in a regular expression literal (like /.../g), it does not have to be escaped at all.

The {' + i + '} part is not useful. This only requires that a pattern occurs that many times, but that could be false after you have made already several replacements. In fact, you need to match the first occurrence every time, since all previous ones were already replaced.

So it should be just this:

    RegExp('(\\d+[*/]\\d+)');

... and so it is in fact no different from the higher regex, except for the global modifier. But that global modifier on higher is not necessary, as split searches all occurrences anyway, even without that modifier. This means you actually only need higher (without g), and not the regex above.

Some other things to improve:

  1. The following debugging line is not of great use, because calstr was already modified, while the regex was applied before the modification:

    console.log('replaced result at : ' + calstr.search(regex));
    
  2. It is better not to access the input element input-display inside the function. Instead pass calstr as a function argument. This makes it independent of your browser context.

  3. Number(...) can be done shorter with the unitary plus (+).

  4. The

Here is the updated code, which also implements the + and - processing, by putting the whole thing in another loop which has 3 iterations: one for * and /, and two others for - (which must come before +!) and +. This had another complexity: a unitary minus should not be confused with the normal minus operator. You'll see that managed with (?:^-)? in the inner regular expression.

function calculateResult(calstr){
    for (var level = 0; level < 3; level++) {
        var search = level == 0 ? '(\\d+[*/]\\d+)'
                   : level == 1 ? '(-?\\d+-\\d+)'
                                : '(-?\\d+\\+\\d+)';
        var higher = RegExp(search);
        var res = calstr.split(higher);
        while(res.length > 2){
            for(var i = 1; i < res.length; i+=2){
                var a = res[i].split(/((?:^-)?[0-9\.]+)/g);
                calstr = calstr.replace(higher, 
                     a[2] == '*' ? +a[1] * +a[3]
                   : a[2] == '/' ? +a[1] / +a[3]
                   : a[2] == '+' ? +a[1] + +a[3]
                                 : +a[1] - +a[3]
                );
            }
            res = calstr.split(higher);
        }
    }
    return res[0];
}

$('button').click(function () {
   var input = $('#input-display').val();
   var result = calculateResult(input);
   $('span').text(result);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="input-display" value="2+3*9/3-6">
<button>Calc</button><br>
Result: <span></span>

Note that you are re-inventing the wheel, as JavaScript offers the eval function to evaluate (controlled) expressions like that. Moreover, this code will not evaluate more complex expressions, like when they have parentheses.