124 lines
3.9 KiB
JavaScript
124 lines
3.9 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) {
|
|
while (_.size(cassettes) < 4) {
|
|
cassettes.push({ denomination: 0, count: 0 })
|
|
}
|
|
|
|
const solutions = []
|
|
const amountNum = amount.toNumber()
|
|
for (let i = 0; i * cassettes[0].denomination <= amountNum; i++) {
|
|
for (
|
|
let j = 0;
|
|
i * cassettes[0].denomination + j * cassettes[1].denomination <=
|
|
amountNum;
|
|
j++
|
|
) {
|
|
for (
|
|
let k = 0;
|
|
i * cassettes[0].denomination +
|
|
j * cassettes[1].denomination +
|
|
k * cassettes[2].denomination <=
|
|
amountNum;
|
|
k++
|
|
) {
|
|
for (
|
|
let l = 0;
|
|
i * cassettes[0].denomination +
|
|
j * cassettes[1].denomination +
|
|
k * cassettes[2].denomination +
|
|
l * cassettes[3].denomination <=
|
|
amountNum;
|
|
l++
|
|
) {
|
|
if (
|
|
i * cassettes[0].denomination +
|
|
j * cassettes[1].denomination +
|
|
k * cassettes[2].denomination +
|
|
l * cassettes[3].denomination ===
|
|
amountNum &&
|
|
i <= cassettes[0].count &&
|
|
j <= cassettes[1].count &&
|
|
k <= cassettes[2].count &&
|
|
l <= cassettes[3].count && i > 0 && j > 0 && k > 0 && l > 0
|
|
) {
|
|
solutions.push([
|
|
{
|
|
provisioned: i,
|
|
denomination: cassettes[0].denomination,
|
|
id: uuid.v4()
|
|
},
|
|
{
|
|
provisioned: j,
|
|
denomination: cassettes[1].denomination,
|
|
id: uuid.v4()
|
|
},
|
|
{
|
|
provisioned: k,
|
|
denomination: cassettes[2].denomination,
|
|
id: uuid.v4()
|
|
},
|
|
{
|
|
provisioned: l,
|
|
denomination: cassettes[3].denomination,
|
|
id: uuid.v4()
|
|
}
|
|
])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const sortedSolutions = _.sortBy(solutions, it => {
|
|
_.reduce(it, (acc, value) => acc + value.provisioned, 0)
|
|
})
|
|
return _.head(sortedSolutions)
|
|
}
|