Today, we are going to be working through the Exact Change bonfire. This guy is tough, but we’ll get him for sure. There is a few ways to tackle this problem, but I’ll try to use the most comprehensive version that I’ve been able to come up with in a few hours.
If you are up for a challenge, try to reinvent the upcoming solution using objects and methods.
In any case, let’s get started. We must write a function that given three arguments (purchase price, payment and a cash-in-drawer array) returns an array (with the same shape as the input cash-in-drawer array) containing the change in coins and bills that we should give back, sorted in highest to lowest order.
The cash-in-drawer array has the following shape:
var cidExample = [ ["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.10], ["QUARTER", 4.25], ["ONE", 45.00], ["FIVE", 55.00], ["TEN", 20.00], ["TWENTY", 60.00], ["ONE HUNDRED", 200.00] ]; // [CoinOrBillName, cash]
If the available cash-in-drawer is exactly the same as the change that we have to give back, we simply return ‘Closed’. If we do not have enough money in the drawer to give back, we return ‘Insufficient Funds’.
Here’s a few examples:
var cidArray = [ ["PENNY", 1.01], // 101 pennies ["NICKEL", 2.05], // 41 nickels ["DIME", 3.10], // 31 dimes ["QUARTER", 4.25], // etc. ["ONE", 45.00], ["FIVE", 55.00], ["TEN", 20.00], ["TWENTY", 60.00], ["ONE HUNDRED", 200.00] ]; drawer(25.00, 30.00, cidArray); //-> Need to return 5 in highest coin/bill possible so: [["FIVE", 5.00]] drawer(1.25, 5, cidArray); //-> [["ONE", 1.00], ["QUARTER", 0.25]] drawer(2.00, 5000, cidArray); //-> Insufficient Funds!
NOTE
This problem used to be a bit easier, since every test could be passed with just the above. Now, we also need to make sure that we can actually return the exact change, or return insufficient funds; here’s an example of such a case:
var cidArray = [ ["PENNY", 0.54], // 54 pennies ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 500.00] // 5 hundred dollar bills ]; drawer(15.00, 20.00, cidArray); // We need to return 5 dollars, bu we can only return $0.54 or a hundred bill, so we return Insufficient Funds
Don’t worry about this now though, let’s get started with the basics. We are getting the price and received cash, so we can get the change. We are actually going to store this change in two variables, you’ll see why later on *wink, wink*. Let’s get the bare-bones set first:
function drawer(price, cash, cid) { var change = cash - price, // Total change to be returned. It's equal to the received payment minus the actual price of the purchase. changeLeft = change; // Change left, will update as we go on. }
Now, we need to know the total amount of cash that we have in the drawer. We can compute this out of the cid array that we get. Let’s create a function that does exactly that. I’m going to be working with integers from now on, because decimals can bring in a few problems that could cause us some issues. It’s pretty simple, we’ll just think in cents instead of dollars! We’ll just make sure that we turn them back to dollars right before we add them to the result array:
function drawer(price, cash, cid) { cash = cash * 100; // We change both cash and price to be cents. price = price * 100; var change = cash - price, changeLeft = change; function getTotalCid(cid) { var total = 0; for (var i = 0; i < cid.length; i++) { total += cid[i][1] * 100; // We add the amount of money in each coin or bill (in cents) to the total. } return total; } }
Great, we now know the change that we need to give back, and can also calculate the total cash-in-drawer available. Let’s add some conditionals to check for the special cases:
function drawer(price, cash, cid) { cash = cash * 100; price = price * 100; var change = cash - price, changeLeft = change; var totalCid = getTotalCid(cid); if (change > totalCid) { return 'Insufficient Funds'; } else if (change === totalCid) { return 'Closed'; } // Else, DO MAGIC HERE! function getTotalCid(cid) { var total = 0; for (var i = 0; i < cid.length; i++) { total += cid[i][1] * 100; } return total; } }
Now, we are going to create yet another function, that, given a coin or bill name, will give us the value of said coin or bill. This will surely be useful, since we know how much money we have in every type of coin, but we don’t know how to subtract from those as we given money back. Let’s use a simple switch statement and return the value for each type of coin in cents:
function drawer(price, cash, cid) { cash = cash * 100; price = price * 100; var change = cash - price, changeLeft = change; var totalCid = getTotalCid(cid); var result = []; // Empty array to store the result! if (change > totalCid) { return 'Insufficient Funds'; } else if (change === totalCid) { return 'Closed'; } // Else, DO MAGIC HERE! function getTotalCid(cid) { var total = 0; for (var i = 0; i < cid.length; i++) { total += cid[i][1] * 100; } return total; } function getValue(coinOrBill) { switch (coinOrBill) { case 'PENNY': return 1; case 'NICKEL': return 5; case 'DIME': return 10; case 'QUARTER': return 25; case 'ONE': return 100; case 'FIVE': return 500; case 'TEN': return 1000; case 'TWENTY': return 2000; case 'ONE HUNDRED': return 10000; } } }
Great! Now we will actually get to the logic behind our main function. We are going to transverse the input cid array backwards, to make sure that we use up the bigger coins/bills first. For each one of them, we check if the change left is lesser than it’s value (we can’t return a hundred dollar bill if we just want to return $5.00!).
Then, while the cash left is greater that the current coin/bill and we still have coins/bills of this type, we remove it’s value from the change left and remove a coin. We’ll keep track of how many coins of each type we’ve given back so we can create an array with the result. Let’s see the code and comment each line:
function drawer(price, cash, cid) { cash = cash * 100; price = price * 100; var change = cash - price, changeLeft = change; var totalCid = getTotalCid(cid); var result =[]; if (change > totalCid) { return 'Insufficient Funds'; } else if (change === totalCid) { return 'Closed'; } for (var i = cid.length - 1; i >= 0; i--) { // We loop over the cash-in-drawer array in reverse order. var coinName = cid[i][0], // We set the coin name. coinTotal = cid[i][1] * 100, // We set the total cash in that type of coin (times 100 for cents!). coinValue = getValue(coinName), // We get the value of a single coin/bill using it's name. coinAmount = coinTotal / coinValue, // We get the amount of coins of it's type by dividing the total cash by the value of a single unit. toReturn = 0; // Counter: How many coins of this type we are returning. while (changeLeft >= coinValue && coinAmount > 0) { // While change is greater that the value the current coin/bill: changeLeft -= coinValue; // Substract the value of a single coin/bill from the change left. coinAmount--; // Remove one coin/bill since we are returning it. toReturn++; // Add one to the counter. } // When the loop is done (because there is no coins/bills of the current // type left or the change left is less than the value of the current coin/bill) if (toReturn > 0) { // We push the coin and total to return in that type of coin/bill to the result. // We get this value by multiplying the value of a single coin/bill by it's value, and then divide by 100 to get the value in dollars. result.push([coinName, toReturn * coinValue / 100]); } } // Return something! function getTotalCid(cid) { var total = 0; for (var i = 0; i < cid.length; i++) { total += cid[i][1] * 100; } return total; } function getValue(coinOrBill) { switch (coinOrBill) { case 'PENNY': return 1; case 'NICKEL': return 5; case 'DIME': return 10; case 'QUARTER': return 25; case 'ONE': return 100; case 'FIVE': return 500; case 'TEN': return 1000; case 'TWENTY': return 2000; case 'ONE HUNDRED': return 10000; } } }
We now have our result array ready to be returned! But remember, we need to make sure that we return the exact change, we could not have coins/bills small enough to return. Let’s add an if statement right before we return the results:
function drawer(price, cash, cid) { cash = cash * 100; price = price * 100; var change = cash - price, changeLeft = change; var totalCid = getTotalCid(cid); var result =[]; if (change > totalCid) { return 'Insufficient Funds'; } else if (change === totalCid) { return 'Closed'; } for (var i = cid.length - 1; i >= 0; i--) { var coinName = cid[i][0], coinTotal = cid[i][1] * 100, coinValue = getValue(coinName), coinAmount = coinTotal / coinValue, toReturn = 0; while (changeLeft >= coinValue && coinAmount > 0) { changeLeft -= coinValue; coinAmount--; toReturn++; console.log(changeLeft); } if (toReturn > 0) { result.push([coinName, toReturn * (coinValue / 100)]); } } // We make use of the getTotalCid method that we created earlier to see how much money we are actually returning. // If it's not equal to the original change, it means that we can't return that exact amount with the current cash-in-register. if (getTotalCid(result) != change) { return 'Insufficient Funds'; // Return early. } return result; // Everything went OK, we return the result! function getTotalCid(cid) { var total = 0; for (var i = 0; i < cid.length; i++) { total += cid[i][1] * 100; } return total; } function getValue(coinOrBill) { switch (coinOrBill) { case 'PENNY': return 1; case 'NICKEL': return 5; case 'DIME': return 10; case 'QUARTER': return 25; case 'ONE': return 100; case 'FIVE': return 500; case 'TEN': return 1000; case 'TWENTY': return 2000; case 'ONE HUNDRED': return 10000; } } }
Ready to go! This function should pass every test and get you on your way to the next challenge! If anything is unclear or you’ve got a question, shoot me an email, ping me on twitter or post a comment below!