## Tuesday, July 28, 2020

### Cash Register

From: https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/javascript-algorithms-and-data-structures-projects/cash-register

JavaScript Algorithms and Data Structures Projects: Cash Register

Design a cash register drawer function checkCashRegister() that accepts purchase price as the first argument (price), payment as the second argument (cash), and cash-in-drawer (cid) as the third argument.

cid is a 2D array listing available currency.

The checkCashRegister() function should always return an object with a status key and a change key.

Return {status: "INSUFFICIENT_FUNDS", change: []} if cash-in-drawer is less than the change due, or if you cannot return the exact change.

Return {status: "CLOSED", change: [...]} with cash-in-drawer as the value for the key change if it is equal to the change due.

Otherwise, return {status: "OPEN", change: [...]}, with the change due in coins and bills, sorted in highest to lowest order, as the value of the change key.

Currency Unit Amount
Penny \$0.01 (PENNY)
Nickel \$0.05 (NICKEL)
Dime \$0.1 (DIME)
Quarter \$0.25 (QUARTER)
Dollar \$1 (ONE)
Five Dollars \$5 (FIVE)
Ten Dollars \$10 (TEN)
Twenty Dollars \$20 (TWENTY)
One-hundred Dollars \$100 (ONE HUNDRED)

See below for an example of a cash-in-drawer array:
[
["PENNY", 1.01],
["NICKEL", 2.05],
["DIME", 3.1],
["QUARTER", 4.25],
["ONE", 90],
["FIVE", 55],
["TEN", 20],
["TWENTY", 60],
["ONE HUNDRED", 100]
]

Duration: 1 hour. See video: https://www.youtube.com/watch?v=rx7IUYq1wkU
```const UNIT_VALUES = Object.freeze({
'ONE HUNDRED': 100,
TWENTY: 20,
TEN: 10,
FIVE: 5,
ONE: 1,
QUARTER: 0.25,
DIME: 0.1,
NICKEL: 0.05,
PENNY: 0.01
});

function monify(obj) {
if (typeof obj === 'number') {
return Number(obj.toFixed(2).replace('.', ''));
} else if (obj === undefined || obj === null || typeof obj === 'string' || obj instanceof Date) {
return obj;
} else if (Array.isArray(obj)) {
return obj.map(el => monify(el));
} else if (typeof obj === 'object') {
return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: monify(value) }), {});
} else {
throw new Error('Invalid Data', obj);
}
}

console.log(monify(new Date()));

function floatify(obj) {
if (typeof obj === 'number') {
return Number(obj) / 100;
} else if (obj === undefined || obj === null || typeof obj === 'string'|| obj instanceof Date) {
return obj;
} else if (Array.isArray(obj)) {
return obj.map(el => floatify(el));
} else if (typeof obj === 'object') {
return Object.entries(obj).reduce((acc, [key, value]) => ({ ...acc, [key]: floatify(value) }), {});
} else {
throw new Error('Invalid Data', obj);
}
}

function checkCashRegister(argPrice, argCash, argCid, argUnitValues = UNIT_VALUES) {
const {
argPrice: price,
argCash: cash,
argCid: cid,
argUnitValues: UNIT_VALUES
} = monify({ argPrice, argCash, argCid: [...argCid], argUnitValues });

// From here onwards, we can pretend that we are still operating on original values, courtesy of monify function.
// All values are all scaled to 100 times for us, to prevent inaccurate representation of cents in floating point number.
// No need to change the code's logic.

// If we are not using monify, floating point fraction produces inaccurate result, e.g.,
// console.log(100 - 3.26 - 60);
// 		produces incorrect result: 36.739999999999995
// Correct result is: 36.74

cid.sort(([aUnit], [bUnit]) => UNIT_VALUES[bUnit] - UNIT_VALUES[aUnit]);

const change = cash - price;
let uncollectedChange = change;
const collectedChange = [];
for (const [drawerUnit, drawerAmount] of cid) {
const unitValue = UNIT_VALUES[drawerUnit];

if (unitValue > change) {
collectedChange.push([drawerUnit, 0]);
} else {
if (uncollectedChange >= drawerAmount) {
collectedChange.push([drawerUnit, drawerAmount]);
uncollectedChange -= drawerAmount;
} else {
const toDeduct = Math.floor(uncollectedChange / unitValue) * unitValue;
collectedChange.push([drawerUnit, toDeduct]);
uncollectedChange -= toDeduct;
}
}
}

if (uncollectedChange > 0) {
return { status: 'INSUFFICIENT_FUNDS', change: [] };
}

const totalDrawerAmount = cid.reduce((sum, [, drawerAmount]) => sum + drawerAmount, 0);
const status = totalDrawerAmount - change === 0 ? 'CLOSED' : 'OPEN';

return floatify({
status,
change: collectedChange
.filter(([, ccValue]) => status === 'OPEN' && ccValue > 0 || status === 'CLOSED')
.sort(([aUnit, aValue], [bUnit, bValue]) => bValue - aValue || UNIT_VALUES[aUnit] - UNIT_VALUES[bUnit])
});
}

[
checkCashRegister(19.5, 20, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
checkCashRegister(3.26, 100, [["PENNY", 1.01], ["NICKEL", 2.05], ["DIME", 3.1], ["QUARTER", 4.25], ["ONE", 90], ["FIVE", 55], ["TEN", 20], ["TWENTY", 60], ["ONE HUNDRED", 100]]),
checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
checkCashRegister(19.5, 20, [["PENNY", 0.01], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 1], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]]),
].forEach(result => console.log(result));
```

Output:
```{ status: 'OPEN', change: [ [ 'QUARTER', 0.5 ] ] }
{ status: 'OPEN',
change:
[ [ 'TWENTY', 60 ],
[ 'TEN', 20 ],
[ 'FIVE', 15 ],
[ 'ONE', 1 ],
[ 'QUARTER', 0.5 ],
[ 'DIME', 0.2 ],
[ 'PENNY', 0.04 ] ] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'INSUFFICIENT_FUNDS', change: [] }
{ status: 'CLOSED',
change:
[ [ 'PENNY', 0.5 ],
[ 'NICKEL', 0 ],
[ 'DIME', 0 ],
[ 'QUARTER', 0 ],
[ 'ONE', 0 ],
[ 'FIVE', 0 ],
[ 'TEN', 0 ],
[ 'TWENTY', 0 ],
[ 'ONE HUNDRED', 0 ] ] }
```