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 ] ] }

No comments:

Post a Comment