Least number of coins needed using a Hash Map (Timing Out) - javascript

I'm writing a function to find the least number of coins required to make a certain amount of change. See this problem
This is a Dynamic Programming problem, however, instead of using a traditional array I am trying to use an Object to memoize the results. It's called coinAmounts.
The map holds an array of the of least number of coins to make a particular sum, for example, to make an amount of 6, you need [5,1].
I have a getCoinsToMakeAmount and setCoinsToMakeAmount to edit/get values from the map. As we iterate paths, the setCoins will update our map if the path we passed it was a smaller combo of coins to make a given sum.
The base approach is a backtracking algo, and then I make use of the coinMap to memoize sums.
Edit
I believe I am missing an optimization, because the algorithm seems to work with smaller cycles, but with a larger input like this, it seems to not be great.
coins = [3,7,405,436]
sum = 8839
let combos = []
let coinAmounts = {}
let cycles = 0;
/**
* #param {number[]} coins
* #param {number} amount
* #return {number}
*/
var coinChange = function(coins, amount) {
coinAmounts = {}
combos = []
if(amount === 0) return 0;
coinChangeHelper(coins, 0, [], amount,0);
console.log(coinAmounts)
//console.log(combos);
// console.log("CYCLES: " + cycles)
if(!coinAmounts[amount]) return -1;
return coinAmounts[amount].length;
};
var coinChangeHelper = function(coins, amount, _chosen, desiredAmount, minPath){
let chosen = Object.assign([], _chosen);
cycles++;
// save the current iteration if it's better
setLowestCoinsToMakeAmount(amount, chosen);
if(amount > desiredAmount){
return false;
}
if(amount === desiredAmount){
combos.push(chosen);
return true;
}
// see if we already know how to make the amount requested
// need to do something with this chosen coins
const chosenCoins = getCoinsToMakeAmount(amount);
if(chosenCoins && chosenCoins.length < chosen.length){
return true;
}
//debug(coins, amount, chosenCoins, _chosen)
// like a backtracking algo
for(let i = minPath; i<coins.length; i++){
const coinAmount = coins[i];
// choose a coin
chosen.push(coinAmount)
coinChangeHelper(coins,amount+coinAmount, chosen, desiredAmount, i);
// unchoose the coin
chosen.pop();
}
}
// get a coin value
const getCoinsToMakeAmount = (amount, startCoin) => {
const currentCoinCount = coinAmounts[amount];
if(!currentCoinCount){
return undefined;
}
return currentCoinCount;
}
const setLowestCoinsToMakeAmount = (coinsSum, coinsChosen) =>{
if(coinsChosen.length <= 0) return;
const currentCoinCount = coinAmounts[coinsSum];
if(!currentCoinCount){
coinAmounts[coinsSum] = coinsChosen;
return;
}
if(coinsChosen.length < currentCoinCount.length){
coinAmounts[coinsSum] = coinsChosen;
}
}
// nicely print out the output
var debug = (coins, amt, memo, chosen) => {
let tabs = ''
for(let i =0; i<amt; i++){
tabs = tabs+'\t';
}
console.log(`${tabs} change([${amt}], [${memo}],[${chosen}]`)
}
For a case of (coins=[1,2,5], amount=11), the coinAmounts contain the best coin amounts to make a certain sum.
{
'1': [ 1 ],
'2': [ 2 ],
'3': [ 1, 2 ],
'4': [ 2, 2 ],
'5': [ 5 ],
'6': [ 1, 5 ],
'7': [ 2, 5 ],
'8': [ 1, 2, 5 ],
'9': [ 2, 2, 5 ],
'10': [ 5, 5 ],
'11': [ 1, 5, 5 ],
'12': [ 2, 5, 5 ],
'13': [ 1, 2, 5, 5 ],
'14': [ 2, 2, 5, 5 ],
'15': [ 5, 5, 5 ]
}

I believe you can simplify your approach and improve the accuracy of the algorithm at the same time. Creating a getCoins function, we start by sorting the available coins. We then loop, getting the highest coin amount less than or equal to the remaining total, until it is zero:
function getCoins(coins, amount) {
coins.sort((a,b) => b - a);
const result = [];
while (amount > 0) {
let nextCoin = coins.find(coin => coin <= amount);
if (nextCoin) {;
result.push(nextCoin);
amount -= nextCoin;
}
}
return result;
}
let length = 10;
const coins = [1,2,5];
for(let amount = 1; amount <= 20; amount++) {
console.log(`Amount: ${amount}, Coins:`, JSON.stringify(getCoins(coins, amount)));
}

Related

Sum of similar value in n X n dimensional array with n^2 complexity

Given an array [[1, 7, 3, 8],[3, 2, 9, 4],[4, 3, 2, 1]],
how can I find the sum of its repeating elements? (In this case, the sum would be 10.)
Repeated values are - 1 two times, 3 three times, 2 two times, and 4 two times
So, 1 + 3 + 2 + 4 = 10
Need to solve this problem in the minimum time
There are multiple ways to solve this but time complexity is a major issue.
I try this with the recursion function
How can I optimize more
`
var uniqueArray = []
var sumArray = []
var sum = 0
function sumOfUniqueValue (num){
for(let i in num){
if(Array.isArray(num[i])){
sumOfUniqueValue(num[i])
}
else{
// if the first time any value will be there then push in a unique array
if(!uniqueArray.includes(num[i])){
uniqueArray.push(num[i])
}
// if the value repeats then check else condition
else{
// we will check that it is already added in sum or not
// so for record we will push the added value in sumArray so that it will added in sum only single time in case of the value repeat more then 2 times
if(!sumArray.includes(num[i])){
sumArray.push(num[i])
sum+=Number(num[i])
}
}
}
}
}
sumOfUniqueValue([[1, 7, 3, 8],[1, 2, 9, 4],[4, 3, 2, 7]])
console.log("Sum =",sum)
`
That's a real problem, I am just curious to solve this problem so that I can implement it in my project.
If you guys please mention the time it will take to complete in ms or ns then that would be really helpful, also how the solution will perform on big data set.
Thanks
I would probably use a hash table instead of an array search with .includes(x) instead...
And it's also possible to use a classical for loop instead of recursive to reduce call stack.
function sumOfUniqueValue2 (matrix) {
const matrixes = [matrix]
let sum = 0
let hashTable = {}
for (let i = 0; i < matrixes.length; i++) {
let matrix = matrixes[i]
for (let j = 0; j < matrix.length; j++) {
let x = matrix[j]
if (Array.isArray(x)) {
matrixes.push(x)
} else {
if (hashTable[x]) continue;
if (hashTable[x] === undefined) {
hashTable[x] = false;
continue;
}
hashTable[x] = true;
sum += x;
}
}
}
return sum
}
const sum = sumOfUniqueValue2([[1, 7, 3, 8],[[[[[3, 2, 9, 4]]]]],[[4, 3, 2, 1]]]) // 10
console.log("Sum =", sum)
This is probably the fastest way...
But if i could choose a more cleaner solution that is easier to understand then i would have used flat + sort first, chances are that the built in javascript engine can optimize this routes instead of running in the javascript main thread.
function sumOfUniqueValue (matrix) {
const numbers = matrix.flat(Infinity).sort()
const len = numbers.length
let sum = 0
for (let i = 1; i < len; i++) {
if (numbers[i] === numbers[i - 1]) {
sum += numbers[i]
for (i++; i < len && numbers[i] === numbers[i - 1]; i++);
}
}
return sum
}
const sum = sumOfUniqueValue2([[1, 7, 3, 8],[[[[[3, 2, 9, 4]]]]],[[4, 3, 2, 1]]]) // 10
console.log("Sum =", sum)
You could use an objkect for keeping trak of seen values, like
seen[value] = undefined // value is not seen before
seen[value] = false // value is not counted/seen once
seen[value] = true // value is counted/seen more than once
For getting a value, you could take two nested loops and visit every value.
Finally return sum.
const
sumOfUniqueValue = (values, seen = {}) => {
let sum = 0;
for (const value of values) {
if (Array.isArray(value)) {
sum += sumOfUniqueValue(value, seen);
continue;
}
if (seen[value]) continue;
if (seen[value] === undefined) {
seen[value] = false;
continue;
}
seen[value] = true;
sum += value;
}
return sum;
},
sum = sumOfUniqueValue([[1, 7, 3, 8], [3, 2, 9, 4], [4, 3, 2, 1]]);
console.log(sum);
Alternatively take a filter and sum the values. (it could be more performat with omitting same calls.)
const
data = [[1, 7, 3, 8], [3, 2, 9, 4, 2], [4, 3, 2, 1]],
sum = data
.flat(Infinity)
.filter((v, i, a) => a.indexOf(v) !== a.lastIndexOf(v) && i === a.indexOf(v))
.reduce((a, b) => a + b, 0);
console.log(sum);
You can flatten the array, filter-out single-instance values, and sum the result:
const data = [
[ 1, 7, 3, 8 ],
[ 3, 2, 9, 4 ],
[ 4, 3, 2, 1 ]
];
const numbers = new Set( data.flat(Infinity).filter(
(value, index, arr) => arr.lastIndexOf(value) != index)
);
const sum = [ ...numbers ].reduce((a, b) => a + b, 0);
Another approach could be the check the first and last index of the number in a flattened array, deciding whether or not it ought to be added to the overall sum:
let sum = 0;
const numbers = data.flat(Infinity);
for ( let i = 0; i < numbers.length; i++ ) {
const first = numbers.indexOf( numbers[ i ] );
const last = numbers.lastIndexOf( numbers[ i ] );
if ( i == first && i != last ) {
sum = sum + numbers[ i ];
}
}
// Sum of numbers in set
console.log( sum );

Why am I getting wrong results when dividing numbers into arrays with weight percentage?

I have number of users to allocate to number of computers instances like docker or AWS. user can increase the number of instances and also can change the users. The instances have weight percentage.
Users: 10
Locations: 2 [{loc1.weight: 70%}, {loc2.weight: 30%}] so it's simple to have 7 and 3 users for each.
The total percentage might not be equal to hundred so scale factor can be anything.
My other requirement is each instance must have at least 1 user. I have condition applied that minimum user can not be less than locations so that part is good.
Other requirement is all users should be integers.
Example
Case #1
Users: 5
Locations: 4 where 1.weight = 15, 2.weight = 30, 3.weight = 15, 4.weight = 50 (total weight 110%)
Expected Results
Locations:
1.users = 1,
2.users = 1,
3.users = 1,
4.users = 2
Case #2
Users: 10
Locations: 4 where 1.weight = 10, 2.weight = 10, 3.weight = 90, 4.weight = 10 (total weight 120%)
Expected Results
Locations:
1.users = 1,
2.users = 1,
3.users = 7,
4.users = 1
Case #3
Users: 5
Locations: 2 where 1.weight = 50, 2.weight = 50
Expected Results
Locations:
1.users = 3,
2.users = 2
That was all of the explanation of the problem. Below is what I had tried.
function updateUsers(clients, weights) {
let remainingClients = clients;
const maxWeight = weights.reduce((total, weight) => total + parseInt(weight), 0);
let r = [];
weights.forEach(weight => {
let expectedClient = Math.round(clients * (weight / maxWeight));
let val = remainingClients <= expectedClient ? remainingClients : expectedClient;
remainingClients -= expectedClient;
r.push(val > 0 ? val : 1);
});
if ( remainingClients > 0 ) {
r = r.sort((a, b) => a > b ? 1 : -1);
for ( let i = 0; i < remainingClients; i++ ) {
r[i] = r[i] + 1;
}
}
return r;
}
I get good results for some numbers like
updateUsers(12, [5, 5, 5, 90]);
gives
[1, 1, 1, 9]; //total 12 users
but using very odd figures like below
updateUsers(12, [5, 5, 5, 200]);
returns
[2, 1, 1, 11]; //total 15 users which is wrong
At first get percentage, You said that in every quota should at least have 1 user, So we used Math.floor(), If its equal to 0, we return 1 and update userCount like so 1 - percentage.
const sumProcedure = (sum, n) => sum + n;
function updateUsers(userCount, weights) {
let n = userCount,
totalWeight = weights.reduce(sumProcedure),
results = weights.map(weight => {
let percentage = (weight * userCount) / totalWeight,
floor = Math.floor(percentage);
if (floor == 0) {
userCount -= 1 - percentage;
return 1
}
return floor;
}),
remain = n % results.reduce(sumProcedure);
while (remain--) {
let i = weights.indexOf(Math.max(...weights));
weights.splice(i, 1);
results[i]++
}
return results;
}
console.log(updateUsers(5, [50, 50])); // [3 , 2]
console.log(updateUsers(12, [5, 5, 5, 90])); // [1, 1, 1, 9]
console.log(updateUsers(12, [5, 5, 5, 200])); // [1, 1, 1, 9]
console.log(updateUsers(5, [15, 30, 15, 50])); // [ 1, 1, 1, 2 ]
console.log(updateUsers(10, [10, 10, 90, 10])); // [ 1, 1, 7, 1 ]
console.log(updateUsers(55, [5, 5, 5, 90])); // [ 3, 2, 2, 48 ]; It has 2 remainders
This approach works if speed is not super important. I don't know javascript, so a bit of it is going to be in pseudocode. I'll keep your notations though.
Let wSum = sum(weights) be the sum of all weights and unitWeight = wSum / weights.length be the weight each user should be assigned if all were given equal weight. Now, let
r[i] = 1;
weights[i] -= unitWeight;
for i = 0, 1 ... weights.length-1. This ensures that all locations receive at least 1 user and the weights are updated to reflect their 'remaining' weight. Now
remainingClients = clients - weights.length;
Assign the rest of the clients via a while(remainingClients > 0) loop or similar:
while(remainingClients > 0)
{
var indexMax = argMax(weights);
weights[indexMax] -= unitWeight;
r[indexMax] += 1;
remainingClients -= 1;
}
This gives the expected result for all your examples. The argMax should of course just return the index of the array corresponding to the maximum value. Due to argMax, the runtime becomes O(n^2), but it doesn't sound like you have tens of thousands of users, so I hope that's okay.

How to iterate over an object to find out 'periods of time' in which the value is larger than 0?

I am building a booking app. I have created an object with times and the number of vacancies for each of these times.
{
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
}
Times are separated in 30 minute intervals, but services can take longer than 30 minutes (but are all multiples of 30 themselves). E.g.: service1.duration = 90
I now need to build a script that identifies in which periods of time a service can be executed. In the example above, 90/30 = 3, so I would have to find 3 sequential keys in that object that have a value > 0.
There would be two in the example above: [1000, 1030, 1100] and [1030, 1100, 1130].
Ideally, the periods would be returned in an array just as the two I have exemplified.
Problem: I don't know how to iterate over both keys and values. I know Object.keys and Object.values can be used, but not how to combine them.
You can use both Object.keys and Object.values together, as they are both guaranteed to be in ascending order for number-like keys.
const times = {
1000: 1,
1030: 4,
1100: 4,
1130: 2,
1200: 0,
1230: 1,
1300: 0
//...
};
const getPeriods = time => {
const keys = Object.keys(times);
const values = Object.values(times);
const res = [];
for(let i = 0; i <= values.length - time; i++){
let works = true;
for(let j = i; j < i + time && works; j++){
if(values[j] <= 0){
works = false;
}
}
if(works){
res.push(keys.slice(i, i + time));
}
}
return res;
};
console.log(getPeriods(3));

JS Create array of objects containing random unique numbers

In javascript I want to create an array of 20 objects containing 2 random numbers between 1 and 250. All numbers in the array I want to be unique from each other. Basically like this:
const matches = [
{ player1: 1, player2: 2 },
{ player1: 3, player2: 4 },
{ player1: 5, player2: 6 },
{ player1: 7, player2: 8 },
...
]
// all unique numbers
I have found this other method
const indexes = [];
while (indexes.length <= 8) {
const index = Math.floor(Math.random() * 249) + 1;
if (indexes.indexOf(index) === -1) indexes.push(index);
}
But this only returns an array of numbers:
[1, 2, 3, 4, 5, 6, 7, 8, ...]
You could use Array.from method to create an array of objects and then also create custom function that will use while loop and Set to generate random numbers.
const set = new Set()
function getRandom() {
let result = null;
while (!result) {
let n = parseInt(Math.random() * 250)
if (set.has(n)) continue
else set.add(result = n)
}
return result
}
const result = Array.from(Array(20), () => ({
player1: getRandom(),
player2: getRandom()
}))
console.log(result)
You can create an array of 251 elements (0-250) and preset all values to 0 to keep track of the generated elements. Once a value is generated, you mark that value in the array as 1.
Check below:
// create an array of 251 elements (0-250) and set the values to 0
let array = Array.from({ length: 251 }, () => 0);
let matches = [];
function getRandomUniqueInt() {
// generate untill we find a value which hasn't already been generated
do {
var num = Math.floor(Math.random() * 249) + 1;
} while(array[num] !== 0);
// mark the value as generated
array[num] = 1;
return num;
}
while (matches.length <= 4) {
let obj = { "player1" : getRandomUniqueInt(), "player2" : getRandomUniqueInt() };
matches.push(obj);
}
console.log(matches);

Assigning every possible combination of a range of numbers to variables

Given a range of numbers (1-25) how can I create a loop so that at each iteration I get a unique set of variables.
To give an example, if I was doing it with 4 numbers my variables would be:
on loop 1:
a = 1, b = 2, c = 3, d = 4,
on loop 2:
a = 1, b = 2, c = 4, d = 3
etc.
What I am trying to do is iterate over every possible number for each position (think sudoku)
so in a 3x3 grid: a = top left position, b = top middle, etc...
so similar to sudoku I would have a condition (each row = 65: a + b + c + d + e= 65)
so what I want to do is have a loop where I can assign all the values to variables:
for (something) {
var topLeft = (determined from loop)
var nextPosition = etc.
My solution is currently like so:
var numbers = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25];
var a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y;
var vars = [a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y];
var counter = 0;
var found = false;
while(found == false) {
for (var asdf = numbers, i = asdf.length; i--; ) {
var random = asdf.splice(Math.floor(Math.random() * (i + 1)), 1)[0];
vars[i] = random;
}
if (
{a+b+c+d+e = 65,
f+g+h+i+j = 65,
k+l+1+n+o = 65,
p+q+r+s+t = 65,
u+v+w+x+y = 65,
a+f+k+p+u = 65,
b+g+l+q+v = 65,
c+h+1+r+w = 65,
d+i+n+s+x = 65,
e+j+o+t+y = 65,
u+q+1+i+e = 65,
a+g+1+s+y = 65}
) {
console.log(a,b,c,d,e,f,g,h,i,j,k,l,n,o,p,q,r,s,t,u,v,w,x,y);
found = true;
}
counter++;
}
However the obvious problem with this is that it's just randomly selecting values. So it will take an incredible amount of time. I can't work out how to iterate over every possible combination (without having 25 for loops) so I can check which values will pass the condition.
Given a range of numbers (1-25) how can I create a loop so that at each iteration I get a unique set of variables.
It sounds like you are talking about the permutations of a set. You can find a bunch of different algorithms to do this. Here is a nice one from this StackOverflow answer:
function getArrayMutations(arr, perms = [], len = arr.length) {
if (len === 1) perms.push(arr.slice(0))
for (let i = 0; i < len; i++) {
getArrayMutations(arr, perms, len - 1)
len % 2 // parity dependent adjacent elements swap
? [arr[0], arr[len - 1]] = [arr[len - 1], arr[0]]
: [arr[i], arr[len - 1]] = [arr[len - 1], arr[i]]
}
return perms
}
getArrayMutations([1, 2, 3])
> [ [ 1, 2, 3 ],
[ 2, 1, 3 ],
[ 3, 1, 2 ],
[ 1, 3, 2 ],
[ 2, 3, 1 ],
[ 3, 2, 1 ] ]
Be careful though! Permutations are factorial which means they grow really fast.
P(n, k) =
This means that if you want to permute 25 numbers, you are looking at 1.551121e+25 possible combinations which is getting into the not-computable-in-your-lifetime territory.
What I am trying to do is iterate over every possible number for each position (think sudoku) so in a 3x3 grid: a = top left position, b = top middle, etc...
Two dimensional arrays (really just lists of lists) are a great way to store matrix data like this. It doesn't fundamentally change the math to change the representation from a single array, but it might be easier to think about. I'm not 100% sure if you want a 3x3 grid or a 5x5 grid but I'll assume 5x5 since you have 25 numbers in your example. You can easily reshape them like this:
function reshapeArray(array, n=5) {
let result = []
let row = 0
let col = 0
for (let i = 0; i < array.length; i++) {
if (col == 0) {
result[row] = []
}
result[row][col] = array[i]
col++
if (col == n) {
col = 0
row++
}
}
return result
}
reshapeArray([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25])
> [ [ 1, 2, 3, 4, 5 ],
[ 6, 7, 8, 9, 10 ],
[ 11, 12, 13, 14, 15 ],
[ 16, 17, 18, 19, 20 ],
[ 21, 22, 23, 24, 25 ] ]
so similar to sudoku I would have a condition (each row = 65: a + b + c + d + e= 65)
Now that you have your data in an iteratable array, you can very easily check this or any other constraint. For example:
/**
* Checks if a matrix (a 2-d array like the output from reshapeArray())
* meets our criteria.
*/
function checkMatrix(matrix) {
for (let row = 0; row < matrix.length; row++) {
let rowSum = 0
for (let col = 0; col < matrix[row].length; col++) {
rowSum += matrix[row][col]
}
// The row sum does not equal 65, so this isn't the one!
if (rowSum != 65) {
return false
}
}
// All the row sums equal 65
return true
}
If you want add extra rules (like having the columns sum to 65 as well) just modify the code to check for that. You can get the value at any point in the matrix by indexing it matrix[row][col] so matrix[0][0] is the upper-left, etc.
However the obvious problem with this is that it's just randomly selecting values. So it will take an incredible amount of time. I can't work out how to iterate over every possible combination (without having 25 for loops) so I can check which values will pass the condition.
Yes, it will. Sudoku is an NP-Hard problem. If you haven't seen complexity classes before, that's just a very mathematically formal way of saying that there's no clever solution that's going to be significantly faster than just checking every possible solution. This hypothetical problem is not exactly the same, so it might be possible, but it has a very np-ish feel to it.
Currently, your pseudocode solution would look like this:
let permutations = getPermutations() // You're going to need to change this part
// because getting all the permutations
// ahead of time will take too long.
// Just picking random numbers each time is
// not actually a terrible idea. Or, look at
// generator functions (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators)
for (let permutation of permutations) {
let matrix = reshapeArray(permutation)
if (checkMatrix(matrix)) {
console.log("Found it")
console.log(matrix)
break
}
}
If there is only one possible solution that matches your criteria, you will never find it this way. If there is a relatively high density of solutions, you will probably find some. If you really want to solve this problem I would recommend first looking at it from a mathematical perspective -- can you prove that it is or isn't NP? can you make some prediction about the density of solutions?
Not sure what the question really is. I would store the range in an Array:
function range(start, stop = null, upperCase = false){
let b = start, e = stop, s = 'abcdefghijklmnopqrstuvwxyz', x;
const a = [], z = s.split(''), Z = s.toUpperCase().split('');
if(typeof b === 'string'){
s = z.indexOf(b.toLowerCase());
if(e === null){
x = z.length;
}
else{
x = z.indexOf(e.toLowerCase())+1;
}
if(upperCase){
return Z.slice(s, x);
}
return z.slice(s, x);
}
else if(e === null){
e = b; b = 1;
}
for(let i=b; i<=e; i++){
a.push(i);
}
return a;
}
function permuCount(array){
let c = 1;
for(let i=0,n=1,l=array.length; i<l; i++,n++){
c *= n;
}
return c;
}
function comboCount(array){
let l = array.length;
return Math.pow(l, l);
}
console.log(range(2, 23)); console.log(range(10)); console.log(range('d'));
console.log(range('g', 'p')); console.log(range('c', 'j', true));
// here is where you'll have an issue
const testArray = range(0, 9);
console.log(permuCount(testArray));
console.log(comboCount(testArray));
As you can see there are way too many combinations. Also, you should have already see the following post: Permutations in JavaScript?

Categories

Resources