Rather a bit overkill I imagine, but here it is.
How can I make it more efficient?
Solution for Cash Register
JS
console.log(checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]))
function checkCashRegister(price, amountPaid, cashOnHand) {
// define currency types in descending order of value
const coinTypes = ["QUARTER", "DIME", "NICKEL", "PENNY"]
const billTypes = ["ONE HUNDRED", "TWENTY", "TEN", "FIVE", "ONE"]
// stipulate an exchange rate for each currency unit
const exchangeRate = {
PENNY: .01,
NICKEL: .05,
DIME: .1,
QUARTER: .25,
ONE: 1,
FIVE: 5,
TEN: 10,
TWENTY: 20,
'ONE HUNDRED': 100
}
// get num of each currency unit in 2 arrays
const [coinArr, billArr] = parser(cashOnHand)
// determine if exact change is available
return solver(price, amountPaid, coinArr, billArr, cashOnHand)
function coinCounter([QUARTERS, DIMES, NICKELS, PENNIES]) {
const coinArr = [QUARTERS, DIMES, NICKELS, PENNIES]
const exchangeRate = [25, 10, 5, 1]
const coinSum = coinArr
.map((numCoins, idx) => numCoins * exchangeRate[idx])
.reduce((acc, cv) => acc + cv)
const computeValue = sum => {
const remainder = sum % 100;
const quotient = (sum - remainder) / 100;
return [quotient, remainder]
}
const [dollars, cents] = computeValue(coinSum)
return [dollars, cents]
}
function getTotalValue(bills, coins) {
const coinRate = [25, 10, 5, 1]
const billRate = [100, 20, 10, 5, 1]
const numCents = coins.reduce((acc, numOfCoin, idx) => acc + numOfCoin * coinRate[idx]) / 100
const numDollars = bills.reduce((acc, numOfBill, idx) => acc + numOfBill * billRate[idx])
// console.log("numCents", numCents)
// console.log("numDollars", numDollars)
return numCents + numDollars
}
function billCounter([HUNDREDS, TWENTIES, TENS, FIVERS, ONES]) {
const billArr = [HUNDREDS, TWENTIES, TENS, FIVERS, ONES]
const exchangeRate = [100, 20, 10, 5, 1]
// console.log("billArr", billArr)
const billSum = billArr
.map((numBills, idx) => numBills * exchangeRate[idx])
.reduce((acc, cv) => acc + cv)
// because bills are a whole number, no need to run computeValue
return billSum
// console.log("billSum", billSum)
}
function getCoinMap(sortedUniques) {
const dollarSet = [...new Set(sortedUniques
.filter(x => x[0] >= 0)
.map(x => x[0])
.sort((a, b) => a - b)
)]
const getCentSet = dollars => [...new Set(sortedUniques
.filter(x => x[0] === dollars)
.map(x => x[1])
)]
const dollarKeys = dollarSet.map(d => {
return [
d,
getCentSet(d)
]
})
const coinMap = Object.fromEntries(new Map(dollarKeys))
return coinMap
}
function getIterations(iterNums, iterBase) {
// Ported (with minor revisions) from the following SO answer:
// https://stackoverflow.com/a/14001422/14347717
const nums = iterNums
// iterBase is optional; create an array of 0's if it isn't provided
const base = iterBase ? iterBase : Array.from(Array(nums.length), () => 0)
const iterations = []
function step() {
let incomplete = false
for (let i = 0; i < base.length; i++) {
incomplete = incomplete || (base[i] < nums[i])
if (incomplete) break;
}
if (!incomplete) return false;
for (let j = 0; j < base.length; j++) {
if (base[j] < nums[j]) {
base[j]++
return true
} else {
base[j] = 0
continue
}
}
}
let processing = true
while (processing) {
processing = step()
if (processing) iterations.push([...base])
}
return iterations
}
function generateOutput(status, dollarChange = [], coinChange = [], cashOnHand) {
// console.log("status", status)
switch (status) {
case "CLOSED":
return {
status,
change: cashOnHand
}
case "OPEN":
return {
status,
change: generateChange(dollarChange, coinChange)
}
case "INSUFFICIENT_FUNDS":
return {
status,
change: []
}
}
function generateChange(dollarChange, coinChange) {
// console.log("dollarChange", dollarChange)
// console.log("coinChange", coinChange)
const changeArr = []
billTypes.forEach((bill, idx) => {
if (dollarChange[idx] > 0) {
changeArr.push([
getVariableName({ bill }),
exchangeRate[bill] * dollarChange[idx]
])
}
})
coinTypes.forEach((coin, idx) => {
if (coinChange[idx] > 0) {
changeArr.push([
getVariableName({ coin }),
exchangeRate[coin] * coinChange[idx]
])
}
})
return changeArr
}
}
function parser(cashOnHand) {
// cashMap: returns object with each unit and total value of unit
const cashMap = Object.fromEntries(cashOnHand)
// create two arrays, for coins and bills, with num of units
// matching the name of each unit in coinTypes/billTypes
const coinArr = coinTypes.map((coinName) => {
// wrap coinName in an object to work with getVariableName
const coinType = getVariableName({ coinName })
const coinSum = cashMap[coinType]
const coinValue = exchangeRate[coinType]
const numCoins = Math.round(coinSum / coinValue)
return numCoins
})
const billArr = billTypes.map((billName) => {
const billType = getVariableName({ billName })
const billSum = cashMap[billType]
const billValue = exchangeRate[billType]
const numBills = billSum / billValue
return numBills
})
// return `coinArr` and `billArr` once completed
return [coinArr, billArr]
}
function solver(price, amountPaid, coinArr, billArr, cashOnHand) {
// calculate amount of dollars and cents owed
const changeOwed = amountPaid - price
console.log(price)
console.log(amountPaid)
console.log(changeOwed)
const [dollarsOwed, centsOwed] = calcChange(changeOwed)
const exactAmountPaid = changeOwed === getTotalValue(billArr, coinArr)
// GENERATE CLOSED OUTPUT IF EXACT PAYMENT PROVIDED
if (exactAmountPaid) return generateOutput("CLOSED", 0, 0, cashOnHand)
// GENERATE INSUFFICIENT FUNDS IF AMOUNT PAID IS LESS THAN PRICE
if (changeOwed < 0) return generateOutput("INSUFFICIENT_FUNDS")
// initialize solution variables
let dollarCombos = []
let coinCombos = []
// intialized optimized solution variables
let optimizedDollarChange = []
let optimizedCoinChange = []
// initialized solution boolean
let hasExactChange = false
// analyze coins. compute all unique coin permutations,
// and store them in an object with dollar values as key
const coinIterations = getIterations(coinArr)
const allCoinTotals = sumCoinTotals(coinIterations)
const uniqueCoinTotals = getUniqueTotals(allCoinTotals)
// analyze bills. compute all unique permutations and store in an arr
const billIterations = getIterations(billArr)
const allBillTotals = sumBillTotals(billIterations)
const uniqueBillTotals = getUniqueTotals(allBillTotals)
// console.log("uniqueBillTotals", uniqueBillTotals)
// sorting is probably not needed for solution;
// using in development to make output easier to read
const sortedUniqueCoinTotals = sortUniqueCoinTotals(uniqueCoinTotals)
const sortedUniqueBillTotals = sortUniqueBillTotals(uniqueBillTotals)
// console.log("solver > sortedUniqueBillTotals", sortedUniqueBillTotals)
// generate a coin map showing cents available per dollar of change
const coinMap = getCoinMap(sortedUniqueCoinTotals)
// console.log("coinMap", coinMap)
// determine whether it is possible to provide number of cents owed
// per dollar of change available
const exactChangePerDollarKey = computeCoinsAvailablePerDollarInterval(centsOwed, coinMap)
const hasCorrectCoinsPotentially = exactChangePerDollarKey.length > 0
// log whether correct coins are potentially available
// console.log(
// "centsOwed:",
// `${centsOwed} -- has exact: `,
// hasCorrectCoinsPotentially
// )
// determine if exact bill change is available (not including from coins)
const hasExactBills = sortedUniqueBillTotals.includes(dollarsOwed)
// console.log(
// "dollarsOwed:",
// `${dollarsOwed} -- has exact:`,
// hasExactBills
// )
// if exact matches for both dollars and coins, generate optimized combo
if (hasCorrectCoinsPotentially && hasExactBills) {
// console.log("has exact for both!")
const additionalDollarsNeeded = 0
dollarCombos = filterBillTotals(
billIterations,
dollarsOwed
)
coinCombos = filterCoinTotals(
coinIterations,
additionalDollarsNeeded,
centsOwed
)
optimizedDollarChange = dollarCombos[0]
optimizedCoinChange = coinCombos[0]
hasExactChange = true
// console.log("optimizedDollarChange", optimizedDollarChange)
// console.log("optimizedCoinChange", optimizedCoinChange)
}
// if no exact bills, check if bill-coin combination allows exact change
else if (hasCorrectCoinsPotentially && !hasExactBills) {
// console.log("checking if bill-coins are available...")
// get max amt of dollars available as change
const maxDollarChangeAvailable = sortedUniqueBillTotals
.filter(x => x < dollarsOwed)
.slice(-1)[0]
// determine if matching change is available
const additionalDollarsNeeded = dollarsOwed - maxDollarChangeAvailable
const dollarCoinMatch = coinMap[additionalDollarsNeeded] || false
// console.log("dollarCoinMatch", dollarCoinMatch)
const hasDollarChange = dollarCoinMatch && dollarCoinMatch.includes(centsOwed)
if (hasDollarChange) {
// console.log("exact change is available!")
dollarCombos = filterBillTotals(
billIterations,
maxDollarChangeAvailable
)
coinCombos = filterCoinTotals(
coinIterations,
additionalDollarsNeeded,
centsOwed
)
optimizedDollarChange = dollarCombos[0]
optimizedCoinChange = coinCombos[0]
// console.log("optimizedDollarChange", optimizedDollarChange)
// console.log("optimizedCoinChange", optimizedCoinChange)
hasExactChange = true
}
// console.log("maxDollarChangeAvailable", maxDollarChangeAvailable)
// console.log("additionalDollarsNeeded", additionalDollarsNeeded)
// console.log("centsOwed", centsOwed)
// console.log("dollarCoinMatch", dollarCoinMatch)
// console.log("hasDollarChange", hasDollarChange)
}
// console.log("hasExactChange", hasExactChange)
// RENDER OUTPUT
if (hasExactChange) {
return generateOutput(
"OPEN",
optimizedDollarChange,
optimizedCoinChange
)
} else if (!hasExactChange) {
return generateOutput("INSUFFICIENT_FUNDS")
}
}
function sumCoinTotals(coinIterations) {
return coinIterations.map(iteration => coinCounter(iteration))
}
function sumBillTotals(billIterations) {
return billIterations.map(iteration => billCounter(iteration))
}
function filterBillTotals(billIterations, maxDollarChangeAvailable) {
return billIterations
.filter(iter => billCounter(iter) === maxDollarChangeAvailable)
.sort((a, b) =>
a.reduce((acc, cv) => acc + cv)
- b.reduce((acc, cv) => acc + cv)
)
}
function filterCoinTotals(coinIterations, dollarsNeeded, centsNeeded) {
return coinIterations
.filter(iter =>
JSON.stringify(coinCounter(iter))
=== JSON.stringify([dollarsNeeded, centsNeeded])
)
.sort((a, b) =>
a.reduce((acc, cv) => acc + cv)
- b.reduce((acc, cv) => acc + cv)
)
}
function getUniqueTotals(totalsArr) {
// stringify arr values to enable equality comparison
const stringArr = totalsArr.map(JSON.stringify)
let uniqueStringArray = new Set(stringArr);
let uniqueArray = Array.from(uniqueStringArray, JSON.parse);
return [0].concat(uniqueArray)
}
function sortUniqueCoinTotals(coinArr) {
const sortedUniques = coinArr.sort((a, b) => b[0] - a[0] || b[1] - a[1])
return sortedUniques
}
function sortUniqueBillTotals(billArr) {
const sortedUniques = billArr.sort((a, b) => a - b)
// console.log("sortedUniques", sortedUniques)
return sortedUniques
}
// filter thru 2D array of all unique dollar/coin combinations
// to find exact coin availability per dollar of change available
function computeCoinsAvailablePerDollarInterval(centsOwed, coinMap) {
const coinMapOE = Object.entries(coinMap)
// uncomment to log all unique coin combination values:
// console.log(coinMapOE)
// return each dollar interval of coin combinations
// that includes the number of exact cents owed
return coinMapOE.filter(x => x[1].includes(centsOwed)).map(x => parseInt(x[0]))
}
// calculate the number of dollars and number of cents owed
// return cents as an integer
function calcChange(sum) {
const remainder = sum % 1;
const centsOwed = Math.round(remainder * 100)
const dollarsOwed = sum - remainder;
return [dollarsOwed, centsOwed]
}
// output the name of a variable passed into a function
function getVariableName(v) {
return Object.values(v)[0]