I'm having issues with calculating weight percentages when dividing numbers into arrays. Why am I getting incorrect results?

Managing the allocation of users to computer instances, whether in Docker or AWS, can be a complex task. Users have the ability to increase the number of instances and change user assignments within those instances. Each instance is assigned a weight percentage for distribution purposes.

  • Users: 10
  • Locations: 2 [{loc1.weight: 70%}, {loc2.weight: 30%}] which equates to having 7 users in one location and 3 in the other.

The total weight percentage may not always add up to 100, so a scale factor needs to be considered for adjustments.

One important requirement is that each instance must have at least 1 user. There's a condition in place ensuring that the minimum number of users cannot be less than the number of locations.

Another key aspect is that all users should be assigned as 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

This problem has been approached using a JavaScript function shown below:

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;
}

While this function works well with certain numbers like

updateUsers(12, [5, 5, 5, 90]); 

producing the output

[1, 1, 1, 9]; //total 12 users

it may not handle extreme cases smoothly such as

updateUsers(12, [5, 5, 5, 200]);

resulting in

[2, 1, 1, 11]; //total 15 users which is incorrect

Answer №1

To start, calculate the percentage. It was mentioned that each quota should have a minimum of one user. To address this, we utilized the Math.floor() function. If the result is 0, we return 1 and adjust the userCount as follows: 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

Answer №2

If speed is not a top priority, this method can be effective. I am not proficient in javascript, so some parts will be presented in pseudocode while retaining your original notations.

Define wSum = sum(weights) as the total weight sum and

unitWeight = wSum / weights.length
as the weight each user would receive if distributed equally. Then, let

r[i] = 1;
weights[i] -= unitWeight;

for i = 0, 1 ... weights.length-1. This strategy ensures that every location receives at least one user and adjusts the weights accordingly. Next,

remainingClients = clients - weights.length;

Distribute the remaining clients using a while(remainingClients > 0) loop or similar approach:

while(remainingClients > 0)
{
    var indexMax = argMax(weights);
    weights[indexMax] -= unitWeight;
    r[indexMax] += 1;
    remainingClients -= 1;
}

This solution aligns with all provided examples. The function argMax should simply return the array index corresponding to the maximum value. Though the runtime becomes O(n^2) due to argMax, it appears unlikely to cause issues with small user quantities.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Refresh all fields for various products with a single click of a button

Our Magento multi-vendor site allows vendors to easily manage their products. For each single product, vendors can view and update information such as price, selling price, quantity, and more from their vendor account. We have implemented a feature where ...

in AngularJS, check for object attributes existence before proceeding to use them

Currently, I have a filter function that is designed to check the latitude and longitude distance of objects within an array against the range selected by the user. However, there is a problem in which some objects within the array do not possess latitude ...

Updating a specific child element within an array in a MongoDB collection with Mongoose:

I am completely new to working with mongodb/mongoose. Here is an example of a mongoose document that I have: const MySchema = mongoose.Schema({ name: { type: String, minLength: 5, maxlength: 100, required: true }, children: [{ nam ...

Binding hover and load events using jQuery on elements that are dynamically generated

One should note that the click event can be successfully bound to an element with the class name keybox, even if this element is dynamically generated. The code for this would look like: $('body').on('click', '.keybox', funct ...

Express JS form validation using hapi/Joi fails to accurately validate the input data in forms

I am currently facing an issue with my form validation using the hapi/Joi package. The problem is that the schema keys are all taking the value of "undefined", which results in the first validation error being returned. How can I resolve this issue? Additi ...

Customize the background color of highlighted text using HTML and jQuery

Recently, I modified an existing code to divide plain text into four classes by selecting a portion of the text and coloring it. Afterwards, I extracted the text of each class and stored it in my database. While this code works well, I am looking for a way ...

Utilizing Browserify routes and configuring Webstorm

When building my project using gulp and browserify, I made use of path resolution for easier navigation. By following this guide, I configured browserify as shown below: var b = browserify('./app', {paths: ['./node_modules','./src ...

I'm only appending the final element to the JavaScript array

Currently, I have the following code: I'm endeavoring to create a new JSON object named dataJSON by utilizing properties from the GAJSON object. However, my issue arises when attempting to iterate over the GAJSOn object; only its last element is added ...

I have implemented a button in PHP and now I am looking to invoke a function when that button is clicked

I have a PHP-generated HTML button and I'm having trouble calling the mybtn3() function when it is clicked. The button code looks like this: echo"<td width=14% align=center><input type=button value=Export onclick=mybtn3() /></td>"; ...

What is the best way to change a vertical drop-down menu to a horizontal layout?

I need help aligning my dropdown menu horizontally instead of vertically. I tried using the flexbox example from w3 schools, but it didn't work when I included the classes inside my select tag. Even after debugging, I realized that the issue is with t ...

Refresh the location markers on Google Maps API to reflect their current positions

I'm currently in the process of learning how to utilize JavaScript with Rails, and I'm encountering some challenges when it comes to updating my markers based on my current position using AJAX. I suspect that the 'ready page:load' event ...

Interactive PNG Sequence Rotation: Explore 360 Degrees

I am facing an issue with my PNG sequence of 360 images, where each image corresponds to a degree of rotation. Currently, I have a React component that updates the rotation based on the mouse position in the window - with x = 0 corresponding to rotation = ...

Loss of value in .net Jquery CheckBox onchange event

My JQuery code for CheckBoxes is causing some unexpected behavior: $(document).ready(function () { $('#chk_AGVS').change(function () { if ($(this).is(":checked")) { '<%Session["chkAGVS"] ...

Concealing messages in React after a brief period

The task at hand involves making a message disappear after 5 seconds. In the following code snippet, I have a scenario where clicking on the "Generate Room Name" button will populate a URL in a text box. After copying this URL using the Copy button, a mes ...

Unlock the power of Javascript in Wordpress by converting a string into a dynamic Page Title

Imagine having a custom-coded menu of links that needs to be added to multiple Wordpress pages. The menu is consistent across all pages, except for the variable part of the link text enclosed in square brackets. Here is how the HTML structure looks: <l ...

What are some creative ways to customize and animate the cursor or caret within an input field?

Just to clarify, I am working with React and Material-UI. I have a task at hand where I need to modify the appearance of the caret within an input element by adding an animation to it. I have managed to change its color using caret-color and set a default ...

Can you help me understand how to break down a single integer into multiple individual integers in a C program?

I've decided to teach myself C programming. Currently, I'm facing a challenge where I want to divide an integer into several separate integers, such as breaking down 12345 into 12, 34, and 5. Unfortunately, I haven't been successful in findi ...

Removing the initial 0 from the input

I have a form input field that I want to format using jQuery 1.7.2 <input style="text-align:right;" type="text" name="DiversionCalc_Diversion_Rate" id="calc_dr" value="0.25%" /> My goal is to adjust the formatting when the input field loses focus. ...

What is the process for creating a progress bar in PixiJS?

Can someone guide me on creating a progress bar similar to the one in PixiJS? Screenshot ...

What is the best way to reset the selected option in Vue.js when clicking on a (x) button?

Is there a way to create a function or button specifically for clearing select option fields? I attempted using <input type="reset" value="x" /> However, when I clear one field, all fields end up getting cleared. Should I provide my code that incl ...