From 0a3b78f75ee29392d2b54fc19854f4ae71fc5f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Salgado?= Date: Wed, 1 Sep 2021 16:22:52 +0100 Subject: [PATCH] feat: makeChange function supporting multiple cassettes --- lib/bill-math.js | 44 ++++++++++++++++++++------------- lib/cash-out/cash-out-helper.js | 16 ++++++++++-- lib/plugins.js | 31 ++++++++++++++--------- 3 files changed, 60 insertions(+), 31 deletions(-) diff --git a/lib/bill-math.js b/lib/bill-math.js index 9cc8d47e..132be98e 100644 --- a/lib/bill-math.js +++ b/lib/bill-math.js @@ -1,4 +1,5 @@ const uuid = require('uuid') +const _ = require('lodash') // Custom algorith for two cassettes. For three or more denominations, we'll need // to rethink this. Greedy algorithm fails to find *any* solution in some cases. @@ -10,28 +11,37 @@ exports.makeChange = function makeChange (cassettes, amount) { // Note: Everything here is converted to primitive numbers, // since they're all integers, well within JS number range, // and this is way more efficient in a tight loop. - - const small = cassettes[0] - const large = cassettes[1] - - const largeDenom = large.denomination - const smallDenom = small.denomination - const largeBills = Math.min(large.count, Math.floor(amount / largeDenom)) + const cassetteMap = _.map(cassettes, it => ({ + denomination: it.denomination + })) const amountNum = amount.toNumber() - for (let i = largeBills; i >= 0; i--) { - const remainder = amountNum - largeDenom * i + const sortedCassettes = _.orderBy(cassetteMap, ['denomination'], ['desc']) - if (remainder % smallDenom !== 0) continue + const finalDist = [] - const smallCount = remainder / smallDenom - if (smallCount > small.count) continue + let mutableAmount = _.clone(amountNum) - return [ - {provisioned: smallCount, denomination: small.denomination, id: uuid.v4()}, - {provisioned: i, denomination: largeDenom, id: uuid.v4()} - ] + while(mutableAmount >= 0) { + _.each(sortedCassettes, it => { + if (mutableAmount === 0) { + finalDist.push({provisioned: 0, denomination: it.denomination, id: uuid.v4()}) + return + } + + const remainder = mutableAmount % it.denomination + const amountToSub = mutableAmount - remainder + const numberOfBills = amountToSub / it.denomination + mutableAmount -= amountToSub + + finalDist.push({provisioned: numberOfBills, denomination: it.denomination, id: uuid.v4()}) + return + }) + + if (mutableAmount === 0) { + break + } } - return null + return finalDist } diff --git a/lib/cash-out/cash-out-helper.js b/lib/cash-out/cash-out-helper.js index dc4597fd..49adb2c2 100644 --- a/lib/cash-out/cash-out-helper.js +++ b/lib/cash-out/cash-out-helper.js @@ -42,8 +42,12 @@ function addDbBills (tx) { return _.assign(tx, { provisioned1: bills[0].provisioned, provisioned2: bills[1].provisioned, + provisioned3: bills[2].provisioned, + provisioned4: bills[3].provisioned, denomination1: bills[0].denomination, - denomination2: bills[1].denomination + denomination2: bills[1].denomination, + denomination3: bills[2].denomination, + denomination4: bills[3].denomination }) } @@ -76,7 +80,7 @@ function toObj (row) { newObj.direction = 'cashOut' - const billFields = ['denomination1', 'denomination2', 'provisioned1', 'provisioned2'] + const billFields = ['denomination1', 'denomination2', 'denomination3', 'denomination4', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4'] if (_.every(_.isNil, _.at(billFields, newObj))) return newObj if (_.some(_.isNil, _.at(billFields, newObj))) throw new Error('Missing cassette values') @@ -89,6 +93,14 @@ function toObj (row) { { denomination: newObj.denomination2, provisioned: newObj.provisioned2 + }, + { + denomination: newObj.denomination3, + provisioned: newObj.provisioned3 + }, + { + denomination: newObj.denomination4, + provisioned: newObj.provisioned4 } ] diff --git a/lib/plugins.js b/lib/plugins.js index c8e28e95..85e74d85 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -105,19 +105,18 @@ function plugins (settings, deviceId) { } function computeAvailableCassettes (cassettes, redeemableTxs) { - if (_.isEmpty(redeemableTxs)) return cassettes + const orderedCassettes = _.orderBy(['denomination'], ['desc'], cassettes) + if (_.isEmpty(redeemableTxs)) return orderedCassettes const sumTxs = (sum, tx) => { const bills = tx.bills const sameDenominations = a => a[0].denomination === a[1].denomination - console.log('tx', tx) - console.log('cassettes', cassettes) - console.log('bills', bills) - const doDenominationsMatch = _.every(it => { - console.log('it', it) - return sameDenominations(it) - }, _.zip(cassettes, bills)) + // Order cassettes and bills to make sure they match + const orderedBills = _.orderBy(['denomination'], ['desc'], bills) + console.log('orderedBills', orderedBills) + + const doDenominationsMatch = _.every(sameDenominations, _.zip(orderedCassettes, orderedBills)) if (!doDenominationsMatch) { throw new Error('Denominations don\'t add up, cassettes were changed.') @@ -126,8 +125,8 @@ function plugins (settings, deviceId) { return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills)) } - const provisioned = _.reduce(sumTxs, Array(_.size(cassettes)).fill(0), redeemableTxs) - const zipped = _.zip(_.map('count', cassettes), provisioned) + const provisioned = _.reduce(sumTxs, Array(_.size(orderedCassettes)).fill(0), redeemableTxs) + const zipped = _.zip(_.map('count', orderedCassettes), provisioned) const counts = _.map(r => r[0] - r[1], zipped) if (_.some(_.lt(_, 0), counts)) { @@ -136,12 +135,20 @@ function plugins (settings, deviceId) { return [ { - denomination: cassettes[0].denomination, + denomination: orderedCassettes[0].denomination, count: counts[0] }, { - denomination: cassettes[1].denomination, + denomination: orderedCassettes[1].denomination, count: counts[1] + }, + { + denomination: orderedCassettes[2].denomination, + count: counts[2] + }, + { + denomination: orderedCassettes[3].denomination, + count: counts[3] } ] }