feat: makeChange function supporting multiple cassettes
This commit is contained in:
parent
575235283a
commit
0a3b78f75e
3 changed files with 60 additions and 31 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
const uuid = require('uuid')
|
const uuid = require('uuid')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
// Custom algorith for two cassettes. For three or more denominations, we'll need
|
// 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.
|
// 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,
|
// Note: Everything here is converted to primitive numbers,
|
||||||
// since they're all integers, well within JS number range,
|
// since they're all integers, well within JS number range,
|
||||||
// and this is way more efficient in a tight loop.
|
// and this is way more efficient in a tight loop.
|
||||||
|
const cassetteMap = _.map(cassettes, it => ({
|
||||||
const small = cassettes[0]
|
denomination: it.denomination
|
||||||
const large = cassettes[1]
|
}))
|
||||||
|
|
||||||
const largeDenom = large.denomination
|
|
||||||
const smallDenom = small.denomination
|
|
||||||
const largeBills = Math.min(large.count, Math.floor(amount / largeDenom))
|
|
||||||
const amountNum = amount.toNumber()
|
const amountNum = amount.toNumber()
|
||||||
|
|
||||||
for (let i = largeBills; i >= 0; i--) {
|
const sortedCassettes = _.orderBy(cassetteMap, ['denomination'], ['desc'])
|
||||||
const remainder = amountNum - largeDenom * i
|
|
||||||
|
|
||||||
if (remainder % smallDenom !== 0) continue
|
const finalDist = []
|
||||||
|
|
||||||
const smallCount = remainder / smallDenom
|
let mutableAmount = _.clone(amountNum)
|
||||||
if (smallCount > small.count) continue
|
|
||||||
|
|
||||||
return [
|
while(mutableAmount >= 0) {
|
||||||
{provisioned: smallCount, denomination: small.denomination, id: uuid.v4()},
|
_.each(sortedCassettes, it => {
|
||||||
{provisioned: i, denomination: largeDenom, id: uuid.v4()}
|
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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,12 @@ function addDbBills (tx) {
|
||||||
return _.assign(tx, {
|
return _.assign(tx, {
|
||||||
provisioned1: bills[0].provisioned,
|
provisioned1: bills[0].provisioned,
|
||||||
provisioned2: bills[1].provisioned,
|
provisioned2: bills[1].provisioned,
|
||||||
|
provisioned3: bills[2].provisioned,
|
||||||
|
provisioned4: bills[3].provisioned,
|
||||||
denomination1: bills[0].denomination,
|
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'
|
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 (_.every(_.isNil, _.at(billFields, newObj))) return newObj
|
||||||
if (_.some(_.isNil, _.at(billFields, newObj))) throw new Error('Missing cassette values')
|
if (_.some(_.isNil, _.at(billFields, newObj))) throw new Error('Missing cassette values')
|
||||||
|
|
@ -89,6 +93,14 @@ function toObj (row) {
|
||||||
{
|
{
|
||||||
denomination: newObj.denomination2,
|
denomination: newObj.denomination2,
|
||||||
provisioned: newObj.provisioned2
|
provisioned: newObj.provisioned2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
denomination: newObj.denomination3,
|
||||||
|
provisioned: newObj.provisioned3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
denomination: newObj.denomination4,
|
||||||
|
provisioned: newObj.provisioned4
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,19 +105,18 @@ function plugins (settings, deviceId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeAvailableCassettes (cassettes, redeemableTxs) {
|
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 sumTxs = (sum, tx) => {
|
||||||
const bills = tx.bills
|
const bills = tx.bills
|
||||||
const sameDenominations = a => a[0].denomination === a[1].denomination
|
const sameDenominations = a => a[0].denomination === a[1].denomination
|
||||||
|
|
||||||
console.log('tx', tx)
|
// Order cassettes and bills to make sure they match
|
||||||
console.log('cassettes', cassettes)
|
const orderedBills = _.orderBy(['denomination'], ['desc'], bills)
|
||||||
console.log('bills', bills)
|
console.log('orderedBills', orderedBills)
|
||||||
const doDenominationsMatch = _.every(it => {
|
|
||||||
console.log('it', it)
|
const doDenominationsMatch = _.every(sameDenominations, _.zip(orderedCassettes, orderedBills))
|
||||||
return sameDenominations(it)
|
|
||||||
}, _.zip(cassettes, bills))
|
|
||||||
|
|
||||||
if (!doDenominationsMatch) {
|
if (!doDenominationsMatch) {
|
||||||
throw new Error('Denominations don\'t add up, cassettes were changed.')
|
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))
|
return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills))
|
||||||
}
|
}
|
||||||
|
|
||||||
const provisioned = _.reduce(sumTxs, Array(_.size(cassettes)).fill(0), redeemableTxs)
|
const provisioned = _.reduce(sumTxs, Array(_.size(orderedCassettes)).fill(0), redeemableTxs)
|
||||||
const zipped = _.zip(_.map('count', cassettes), provisioned)
|
const zipped = _.zip(_.map('count', orderedCassettes), provisioned)
|
||||||
const counts = _.map(r => r[0] - r[1], zipped)
|
const counts = _.map(r => r[0] - r[1], zipped)
|
||||||
|
|
||||||
if (_.some(_.lt(_, 0), counts)) {
|
if (_.some(_.lt(_, 0), counts)) {
|
||||||
|
|
@ -136,12 +135,20 @@ function plugins (settings, deviceId) {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
denomination: cassettes[0].denomination,
|
denomination: orderedCassettes[0].denomination,
|
||||||
count: counts[0]
|
count: counts[0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
denomination: cassettes[1].denomination,
|
denomination: orderedCassettes[1].denomination,
|
||||||
count: counts[1]
|
count: counts[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
denomination: orderedCassettes[2].denomination,
|
||||||
|
count: counts[2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
denomination: orderedCassettes[3].denomination,
|
||||||
|
count: counts[3]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue