Reputation: 2724
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.
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.
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
Upvotes: 1
Views: 121
Reputation: 2473
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
Upvotes: 2
Reputation: 455
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.
Upvotes: 1