3
\$\begingroup\$

This is a basic algorithm that creates change with the smallest number of coins. How can this be better written? It works perfectly, but I just imagine there is a cleaner and shorter version of this.

const generateCoinChange = cents => {
    let quarter = 0;
    let dime = 0;
    let nickel = 0;
    let penny = 0;
    let remainingChange = cents;

    while (remainingChange >= 25) {
        remainingChange -= 25;
        quarter++;
    }
    while (remainingChange >= 10) {
        remainingChange -= 10;
        dime++;
    }
    while (remainingChange >= 5) {
        remainingChange -= 5;
        nickel++;
    }
    while (remainingChange > 0) {
        remainingChange -= 1;
        penny++;
    }
    console.log(`Quarters: ${quarter}, Dimes: ${dime}, Nickels: ${nickel}, Pennies: ${penny}`)
};
generateCoinChange(94);
generateCoinChange(100);
generateCoinChange(23);
\$\endgroup\$

5 Answers 5

3
\$\begingroup\$

So .. you're repeatedly subtracting a number from another number, and seeing how many times you can do that without going negative? There's a word for that: division.

EDIT: Since you want the actual code

const generateCoinChange = cents => {
    let quarter = Math.floor(cents/25);
    cents -= 25*quarter
    let dime = Math.floor(cents/10);
    cents -= 10*dime
    let nickel = Math.floor(cents/5);
    cents -= 5*nickel
    let penny = cents;
    console.log(`Quarters: ${quarter}, Dimes: ${dime}, Nickels: ${nickel},Pennies: ${penny}`)
};

You can also do:

 const generateCoinChange = cents => {
     let counts = [0,0,0,cents]
     let denominations = [25,10,5]
     for (i=0,i<3,i++){
         counts[i] = Math.floor(counts[3]/denominations[i])
         counts[3] -= counts[i]*denominations[i]
     }
 console.log(`Quarters: ${denominations[0]}, Dimes: ${denominations[1]}, Nickels: ${denominations[2]}, Pennies: ${denominations[3]}`)
 };
\$\endgroup\$
2
  • \$\begingroup\$ sounds good, write it up. I understand there are many ways to do this, thats why I'm asking to see others solutions \$\endgroup\$ Commented Nov 29, 2017 at 0:48
  • \$\begingroup\$ That looks great! Very clean short and simple to read. I used your second code, but adjusted it just a bit to log counts instead of denominations, and added the penny value! Posted it to answer my question below. Thanks for the help! \$\endgroup\$ Commented Nov 30, 2017 at 15:45
1
\$\begingroup\$

Spend a penny get a warning.

Just for fun a warning not a rigorous answer

The common approch

Many places have a different system than have removed the smaller coins for convenience. Thus there is a need to round the change to the nearest coin value before doing the calculation. Also you would do the calculation in the actual unit eg dollars not cents, and you don't give counts of coins, you tally and give the total. Eg not "2 tens" but "20 in tens"

The following calculates change in $ rounding to the nearest lowest coin and does not work, the logic is sound just the number system that cant. The function will display a warning if it can not balance the books.

When dealing with indivisible units using division, be careful.

function getChange(sum) {
  var coins = [100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05];
  var coinIndex = 0;
  sum = Number((Math.round(sum / coins[coins.length - 1]) * coins[coins.length - 1]).toFixed(2));
  log("Change for $" + sum.toFixed(2));
  log("-----------------------------");
  while (sum > 0 && coinIndex < coins.length) {
    const value = coins[coinIndex++];
    const coinChange = Number((Math.floor(sum / value) * value).toFixed(2));

    if (coinChange) {
      log("$" + coinChange + " in $" + value.toFixed(2))
    }

    sum -= coinChange;
  }
  if(sum > 0){ 
      log("-----------------------------");
      log("Balance $"+sum);
      log("WARNING WARNING rounding error. What are you doing using doubles for cash!!!!!!"); 
  }

}

function log(str) {
  if (str === undefined) {
    till.innerHTML = "";
    return;
  }
  var div = document.createElement("div");
  div.textContent = str;
  till.appendChild(div);
}


function randSpend() {
  log(); // clear;
  getChange(Math.random() * 1000);
  log("-----------------------------");
  log("Click to spend more. :) ");
};
document.body.addEventListener("click", randSpend);
randSpend();
<div id="till"></div>

\$\endgroup\$
1
\$\begingroup\$

Your code has a structural repetition. The same cycle is written for every coin nomination. What you could do instead is create a coin registry, and use it in a generalized loop.

const coins = [
  { name: 'quarter', value: 25 },
  { name: 'penny', value: 1 },
];
const coinCount = { };

function generateCoinChange(remainingChange) {
  let indexOfCoin = 0;
  while (remainingChange > 0) {
    const coin = coins[indexOfCoin];
    if (remainingChange >= coin.value) {
      remainingChange -= coin.value;
      coinCount[coin.name] = coinCount[coin.name] == null ? 1 : coinCount[coin.name] + 1;
    } else {
      indexOfCoin++;
    }
  }

  const text = Object.getOwnPropertyNames(coinCount)
    .map(coinName => coinName + ": " + coinCount[coinName])
    .join(`, `);
  console.log(text);
}

generateCoinChange(123);
generateCoinChange(2);
generateCoinChange(423);

Notice, you can add more coin types very quickly now. You just need to "register" each coin as one that has a name and a value.


Update

It is indeed much better to avoid unnecessary loops. I'd still use a "registry" of known coins to keep code generalized:

const knownCoins = [
  { name: 'quarter', value: 25 },
  { name: 'penny', value: 1 },
];
const coinCount = { };

function generateCoinChange(remainingChange) {
  let indexOfCoin = 0;
  while (remainingChange > 0 && indexOfCoin < knownCoins.length) {
    const coin = knownCoins[indexOfCoin];
    const numberOfCoins = Math.floor(remainingChange / coin.value);
    coinCount[coin.name] = numberOfCoins;
    remainingChange -= numberOfCoins * coin.value;
    indexOfCoin++;
  }

  const text = Object.getOwnPropertyNames(coinCount)
    .map(coinName => coinName + ': ' + coinCount[coinName])
    .join(`, `);
  console.log(text);
}
\$\endgroup\$
2
  • \$\begingroup\$ Nice looks good! I will go over it a bit, and then go through my code and clean it up. \$\endgroup\$ Commented Nov 28, 2017 at 21:52
  • \$\begingroup\$ @RobertAmato glad you like it. Just please don't change the code in your original question. This is against the rules of this site. \$\endgroup\$ Commented Nov 28, 2017 at 21:53
1
\$\begingroup\$

You can do it without any loops using Math.floor() calling a method within scope automatically reducing the remainder in the outer scope by the previous number of coins multiplied by the coin's value, similar to:

const generateCoinChange = cents => {
    let quarter = 0;
    let dime = 0;
    let nickel = 0;
    let penny = 0;
    let remainingChange = cents;
    
    const getNumberOfCoins = (coinValue) => {
      let numberOfCoins = Math.floor(remainingChange/coinValue);
      remainingChange = remainingChange - (numberOfCoins * coinValue)
      
      return numberOfCoins
    };
    
    quarter = getNumberOfCoins(25);   
    dime = getNumberOfCoins(10);    
    nickel = getNumberOfCoins(5);    
    penny = getNumberOfCoins(1);
    
    console.log(`Quarters: ${quarter}, Dimes: ${dime}, Nickels: ${nickel}, Pennies: ${penny}`)
};
generateCoinChange(94);
generateCoinChange(100);
generateCoinChange(23);

\$\endgroup\$
1
\$\begingroup\$

@Acccumulation here is his second code which I adjusted just a bit to run what I needed. I adjusted it to log counts instead of denominations, and added the penny value!

const generateCoinChange = cents => {
    let counts = [0, 0, 0, 0, cents];
    let denominations = [25, 10, 5, 1];
    for (let i = 0; i < 4; i++) {
        counts[i] = Math.floor(counts[4] / denominations[i])
        counts[4] -= counts[i] * denominations[i]
    }
    console.log(`Quarters: ${counts[0]}, Dimes: ${counts[1]}, Nickels: ${counts[2]}, Pennies: ${counts[3]}`)
};
generateCoinChange(94)
generateCoinChange(22)
generateCoinChange(175)
generateCoinChange(87)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.