lamassu-server/lib/bill-math.js
2021-10-12 20:58:52 +01:00

84 lines
2.7 KiB
JavaScript

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.
// Dynamic programming may be too inefficient for large amounts.
//
// We can either require canononical denominations for 3+, or try to expand
// this algorithm.
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.
// Another note: While this greedy algorithm possibly works for all major denominations,
// it still requires a fallback for cases where it might not provide any solution.
// Example: Denominations: [3, 5, 10] | User inputs 4 times the [3] button, resulting in a 12 fiat tx
// This algorithm resolves for 1 x [10], and can't resolve the remainder of 2
return _.length(cassettes) > 2 ? makeChangeDynamic(cassettes, amount) : makeChangeDuo(cassettes, amount)
}
function makeChangeDuo (cassettes, amount) {
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 amountNum = amount.toNumber()
for (let i = largeBills; i >= 0; i--) {
const remainder = amountNum - largeDenom * i
if (remainder % smallDenom !== 0) continue
const smallCount = remainder / smallDenom
if (smallCount > small.count) continue
return [
{provisioned: smallCount, denomination: small.denomination, id: uuid.v4()},
{provisioned: i, denomination: largeDenom, id: uuid.v4()}
]
}
return null
}
function makeChangeDynamic (cassettes, amount) {
const cassetteMap = _.map(cassettes, it => ({
denomination: it.denomination
}))
const amountNum = amount.toNumber()
const sortedCassettes = _.orderBy(cassetteMap, ['denomination'], ['desc'])
const finalDist = []
let mutableAmount = _.clone(amountNum)
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 finalDist
}