1

Currently, I use this code to total parenthetical values in a string...

if ((string.match(/\((\d+)\)/g)||[]).length > 0) {
    var total = 0;
    string.replace(/\((\d+)\)/g, function(outerValue, innerValue){
        if (!isNaN(innerValue.toString().trim())) {
            total = total + Number(innerValue.toString().trim());
        }
    });

    value = total;
}

...so the string...

(2) dark chocolate, (2) milk chocolate, and (1) white chocolate

...totals 5.

Not willing to leave well-enough alone, I thought it would be cool if I could be a bit fancier and interpret different types of operations, so that someone could write.

(2) dark + (2) milk - (1) white
    -or-
(2) dark and (2) milk minus (1) white

So I changed my code to...

if ((string.match(/\((\d+)\)/g)||[]).length > 0) {
    var total = 0;
    string.replace(/^\((\d+)\)|and\s\((\d+)\)|plus\s\((\d+)\)|\+\s\((\d+)\)/g, function(outerValue, innerValue){
        if (!isNaN(innerValue.toString().trim())) {
            total = total + Number(innerValue.toString().trim());
        }
    });

    value = total;
}

...but the innerValue returns as undefined. I am able to extract the values when I test with the validator in regex101.com, but not in Javascript.

What am I doing incorrectly?

p.s. Obviously, my code is not complete (in addition to being wrong). Ultimately, I would list all of the operator possibilities (e.g., "+", "plus", "and", "less", "minus", "-", etc.) and would examine the string in outerValue to determine the operator. And, of course, I need to write the logic for commas within a sentence (e.g., allow a single operator in the sentence and apply the operation to each item).

Alan M.
  • 1,309
  • 2
  • 19
  • 29

3 Answers3

2

Your argument names (outerValue, innerValue) aren't really accurate. The arguments to the replace function are

function replace(match, p1, p2, ..., pn, offset, string)

So you have

     p1               p2              p3            p4
      |               |               |             |   
/^\((\d+)\)|and\s\((\d+)\)|plus\s\((\d+)\)|\+\s\((\d+)\)/g
                              |
                            match

So when you run (2) dark and (2) milk minus (1) white through your replacer function:

The first match "(2)" has p1=2 since it corresponds to the 1st parenthetical set in your regex, ie, in the first or group. You then have p2=undefined, p3=undefined, p4=undefined.
The next match "and (2)" has p1=undefined since this matches up with the 2nd or group in your regex, so p1=undefined, p2=2, p3=undefined, p4=undefined

See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Specifying_a_function_as_a_parameter

chiliNUT
  • 18,989
  • 14
  • 66
  • 106
0

I would suggest to build a add a parser that would interpret the string. I'd user regexps to extract tokens from text ((1) => 1, - => - ) and pass it to the parser. It would keep track of values and operators and perform calculations.

This thread may be related: https://stackoverflow.com/a/380487/580346

mrzasa
  • 22,895
  • 11
  • 56
  • 94
0

chiliNUT's explanation was extremely helpful, as was his subsequent suggestion in his reply. Inspired to explore some variations, I ended up coding it as follows:

if ((string.match(/\((\d+)\)/g)||[]).length > 0) {
    var total = 0;

    string.match(/^\((\d+)\)|,\s+\((\d+)\)|and\s+\((\d+)\)|plus\s+\((\d+)\)|\+\s+\((\d+)\)|\-\s+\((\d+)\)|minus\s+\((\d+)\)|less\s+\((\d+)\)|subtract\s+\((\d+)\)|times\s+\((\d+)\)|multiply\s+\((\d+)\)|x\s+\((\d+)\)|\*\s+\((\d+)\)/g).forEach(function(element, index){
        element.replace(/\((\d+)\)/g, function(m, p){
            if (!isNaN(p.toString().trim())) {
                p = Number(p);

                if (element.match(/^(minus|less|substract|\-)/g)) {
                    total = total - p;
                }
                else if (element.match(/^(times|multiply|x|\*)/g)) {
                    total = total * p;
                }
                else {
                    total = total + p;
                }
            }
        });
    });
}

I'm sure there's a more efficient way, but for my deepest RegEx dive thus far, and given that it actually works, I figure it's a good start.

Alan M.
  • 1,309
  • 2
  • 19
  • 29