MortenMoulder
MortenMoulder

Reputation: 6646

Apply math to all numbers from a string?

I am trying to make a cooking recipe calculator, which basically grabs all numbers and divides them (so far). On top of dividing all numbers, it will also turn decimals into fractions. This is important. Now, when I run my loop to look for numbers to replace, I have a problem when numbers on lines are greater than 1. If I enter 1 egg and 2 cups of milk and tell it to divide by 2, it will do this:

first iteration:
find: 1 and replace 1 with 1/2
result: 1/2 egg and 2 cups of milk

second iteration:
find: 2 and replace 2 with 1
result: 1/1 egg and 2 cups of milk

If you understood that correctly, I would now have 1/1 2. Why? Because on the second iteration, it will find 2 and replace it with 1. I basically need to say:

if(number != fraction)
    replace number;

How am I able to do that from my code?

JSFiddle: http://jsfiddle.net/fgzt82yk/8/
My loop:

for(i = 0; i < content.length; i++) {
    //Finds numbers on the line
    var number = content[i].match(regex);

    //number == null if the line is empty
    if(number != null) {

        //There can be more than 1 number on each line, create a new loop
        for(j = 0; j < number.length; j++) {

            //Turns fractions into decimals
            var evalNumber = eval(number[j]);

            //Divides the number
            var divided = parseFloat(evalNumber / selection);

            //We need some kind of precision, so we won't get 0.1666667 and so on
            var precision = selection;
            if(precision == 2) {
                precision = 0;
            }

            //Turns our decimal into a fraction (or a whole-number)
            var newNum = Math.fraction(divided.toString(), precision);

            //Replaces the original number from the content[i] (whole line) with the new number/fraction
            content[i] = content[i].replace(number[j], newNum);
        }
    }
}

Added comments to make it more clear what each line means. It's the last line that bugs me.. I'm sure.

I am basically looking for a better way to apply the math to all numbers (decimals, fractions, mixed numbers, et cetera). So far it kinda works, but I am sure someone has a better way of doing it! Thanks for looking!

Upvotes: 2

Views: 319

Answers (2)

arcyqwerty
arcyqwerty

Reputation: 10685

For your use case, I think the solution is not to find out how to not-divide numbers already divided, but to replace all the numbers at once.

For example, if you were to have a recipe that called for:

  • 12 eggs
  • 6 cups of water
  • ...

and you wanted to halve things, you could end up replacing 12 with 6 and then later replacing 6 again on that pass. (Yes, you could probably do it largest to smallest number, but then you need a pass to order all the numbers as well. Unnecessary complexity).

If you're going to use regexes anyway, I suggest you do something like

function fractionize(n, d) {
  var gcd = (function gcd(a, b) {
      if ( ! b) {
          return a;
      }
      return gcd(b, a % b);
  })(n ,d);
  return d == gcd ? n / d : (n > d ? Math.floor(n / d) + ' ': '') + (n % d / gcd) + '/' + (d / gcd);
}

function decimalize(decimal, precision) {
  var power = Math.pow(10, precision);
  return Math.round(decimal * power) / power;
}

function scaleRecipe(recipe, multiply, divide) {
  return recipe.replace(/((\d*\.\d+)|((\d+)\s+)?(\d+)(\s*\/\s*(\d+))?)/g, function(_, _, decimal, _, wn, n, _, d) {
    d = d || 1;
    return decimal ? decimalize(decimal * multiply / divide, decimal.match(/\d/g).length) : fractionize(((wn || 0) * d + parseInt(n)) * multiply, divide * d);
  });
}

For example:

scaleRecipe('12 eggs, 6 cups of water, 13 chickens, 4 lb rice, 17 tsp vanilla, 60g sugar, 3/4 cups chocolate chips, 2/3 lb beef, 1/5 oz secret sauce, 3 1 / 2 teaspoons salt', 1, 6)

would give you

"2 eggs, 1 cups of water, 2 1/6 chickens, 2/3 lb rice, 2 5/6 tsp vanilla, 10g sugar, 1/8 cups chocolate chips, 1/9 lb beef, 1/30 oz secret sauce, 7/12 teaspoons salt, 0.1 lb ham, 0.027 oz honey, 0.35 pint blueberries"

and that same recipe /8 would give you

"1 1/2 eggs, 3/4 cups of water, 1 5/8 chickens, 1/2 lb rice, 2 1/8 tsp vanilla, 7 1/2g sugar, 3/32 cups chocolate chips, 1/12 lb beef, 1/40 oz secret sauce, 7/16 teaspoons salt, 0.1 lb ham, 0.02 oz honey, 0.26 pint blueberries"

You could also have additional logic to reduce fractions (see: JS how to find the greatest common divisor) and to format as a mixed number (take integer and remainder parts separately).

Plurals might be another aspect you'd want to consider, but English is a tricky language with many edge cases. :)

Upvotes: 7

Hipolith
Hipolith

Reputation: 461

It looks like a job for String.prototype.replace() - exactly the one you're using already.

Just pass a regular expression as a first and transforming function as a second parameter, so you'll change all the numbers at once - you won't need to worry about numbers being affected twice

Upvotes: 0

Related Questions