Last week I attended interview where I was asked to write code for universal ATM algorithm.
So I had to implement function ATM that takes all available banknotes with their limits and desired amount of money and returns any possible way to combine this amount of money from given banknotes and their limits.
The 1st iteration of ATM function implementation was quite simple, because limits looked like
{
1000: 20,
500: 430,
100: 300,
50: 23,
10: 23
}
But in the last iteration of task limits looked like
{
1000: 20,
500: 430,
100: 300,
50: 23,
30: 23
}
I suppose, I succeed to implement working version of this algorithm, but I am unhappy with its final complexity and I feel there should be something much more efficient.
My decision is following:
- Creating all possible subsets for given currencies, so [100, 50, 30] set will be converted into 7 subsets: [[100], [50], [30], [100, 50], [100, 30], [50, 30], [100, 50, 30]].
Try to form needed amount within each subset, so if I have to deal with 180 amount I'll try to form this amount
- only with 100 banknotes, this attempt fails
- only with 50 banknotes, this attempt fails
- only with 30 banknotes, this attempt succeeds with result like {30: 6}
- with 50 and 30 banknotes, this attempt succeeds with result like {50: 3, 30: 1}
- with 100 and 50 banknotes, this attempt fails
- with 100 and 30 banknotes, this attempt fails
- with 100, 50 and 30 banknotes, this attempt succeeds with result like {100: 1, 50: 1, 30: 1}
Select from successful variants any I like and return it as a result.
The working code for my decision in JavaScript is below:
function getAllSubsetsInSet(set) {
const result = [];
let mask = 1;
do {
let maskString = mask.toString(2).padStart(set.length, '0');
result.push(set.filter((item, index) => maskString[index] === '1'));
mask++;
}while (mask < (2 ** set.length));
return result;
}
function getMoney(currencies, limits, amount) {
const sorted = currencies.sort((a, b) => b - a);
let workingLimits = {
...limits
};
let workingAmount = amount;
let result = {};
for (let i = 0; i < sorted.length; i++) {
let currentCurrency = sorted[i];
let desiredBanknotes = Math.floor(workingAmount / currentCurrency);
let availableBanknotes = workingLimits[currentCurrency];
let banknotesToBeUsed = (availableBanknotes < desiredBanknotes) ? availableBanknotes : desiredBanknotes;
workingAmount = (workingAmount - (banknotesToBeUsed * currentCurrency));
workingLimits[currentCurrency] = availableBanknotes - banknotesToBeUsed;
result[currentCurrency] = banknotesToBeUsed;
}
if (workingAmount > 0) {
return {
result: {},
limits,
error: true
}
}
return {
result: result,
limits: workingLimits,
error: false
}
}
function ATM(limits, amount) {
let currencies = Object.keys(limits).map(item => Number(item));
let allCurrencyCombinations = getAllSubsetsInSet(currencies);
let resultsForEachCombination = allCurrencyCombinations.map(combination => {
return getMoney(combination, limits, amount);
});
const succeedResults = resultsForEachCombination.filter(variant => !variant.error);
if (succeedResults.length) {
return succeedResults;
}
return {
result: 'No possible ways',
limits
}
}
console.log(ATM(
{
1000: 20,
500: 430,
100: 300,
50: 23,
30: 90
},
180
));
But please, could anyone help me with the right and eficient way to implement this logic? Any programing language and pseudocode would be OK.
Thank you!