1

Let's say there is an array of x numbers, and the sum of them are 100.

Each number increases or decreases linearly with a step. Each time a number increases the rest have to decrease evenly so that the sum of the numbers doesn't exceed 100. Likewise each time a number decreases the rest have to increase evenly so that the sum of the numbers doesn't go below 100. If a value is causing the sum to exceed or go below 100 the action has to be disallowed with a message to the user.

Let's say that array A: [20,20,20,20,20]

A[2] += 4
//the array A has to become somehow automatically: [19,19,24,19,19]

There is 3 problems on this. Firstly if a number exceeds a value, that will make the rest go below 0, and I don't want that. Example:

A: [-5,100,-5,-5,-5]

And the other one is related with the step. I don't know how much it should increase or decrease (maybe based on the length of the array).

Right now I have step = 1 / A.length

So that if a number increases with step the rest of them have to decrease with step / A.length - 1 (minus one cause I don't count the number the user changed)

And vice-versa

Basically I am trying to do a percentage increment or decrement based on user value (down or up). Can you propose me the logic I have to follow, or some JavaScript code?

EDIT:

I have already implemented something in angular.

At first the values are all equal and sum is 100:

enter image description here

If i increase the first number with the button ( > ), the rest will decrease, and so on..

enter image description here

I am not posting code because its buggy and very meshed up, I just want the logic or a paradigm in code so I can implement it in my app. The check-boxes in the numbers are meant to lock that value so it doesn't increment or decrement.

Dave
  • 7,460
  • 3
  • 26
  • 39
Thanos Sakis
  • 91
  • 13
  • Post the proper JavaScript that utilizes the formula `step = 1 / A.length` as a [mcve]. – zer00ne Mar 18 '22 at 20:44
  • If some element in arr increases by x, the remaining elements should decrease by `x / (arr.length - 1)`. Is it ok to have numbers become decimals? If not, what is the rounding strategy? – James Mar 18 '22 at 21:01
  • Where does the change come from? A user has to pick a number? or is it changed randomly? Some clue of the purpose or implementation might make things clearer. Is it a game? How is the original array generated? can the original array length change? How often, if ever, is a skilled player going to fail (e.g. no solution possible for that array and change)? "doesn't exceed" seems to actually mean "must equal". The array sum must always be 100?. What is a step? This looks like an interesting problem but the question is not clear enough for me to help. – Dave Pritlove Mar 18 '22 at 21:52
  • The array sum must always be 100. A step is a minimum increment (or decrement) based on the array length. The user has only two options for any number of the array, that it to increase it or decrease it with a prefix step (I did step = 1 / A.length). The point of the step is to ensure that the increment/decrement is proportional to the length of the array. The bigger the array, the smaller the step (so the numbers go up/down more slowly), the smaller the array, the bigger the step.. – Thanos Sakis Mar 18 '22 at 22:09
  • what should happen, if you like to decrease an value at an index and you less than four times of the calulated step. [96.8, 0.2, 1, 1, 1]? do you want to disperse `0.2` to the other items? – Nina Scholz Mar 20 '22 at 16:54
  • yes, exactly that – Thanos Sakis Mar 20 '22 at 18:23

2 Answers2

1

You could check if there is a value for decrementing and add all changes to the sum for incrementing a value.

const
    change = (array, index, direction) => {
        if (direction === 1) {
            const step = 1 / array.length;
            for (let i = 0; i < array.length; i++) {
                if (i === index || array[i] === 0) continue;
                const s = Math.min(step, array[i]);
                array[i] -= s;
                array[index] += s;
            }
        } else {
            const step = Math.min(1 / array.length, array[index] / (array.length - 1));
            for (let i = 0; i < array.length; i++) {
                if (i === index) continue;
                array[i] += step;
                array[index] -= step;
            }
        }
        return array;
    },
    values = [96.8, 0.2, 1, 1, 1],
    display = values => values.forEach((v, i) => document.getElementById('value' + i).innerHTML = v.toFixed(2)),
    addEvent = (type, index) => event => {
        change(values, index, type === 'up' ? 1 : -1);
        display(values);
    };
    

[...document.getElementsByTagName('button')].forEach(element => {
    const [type, index] = element.id.match(/\d+|\D+/g);
    element.addEventListener('click', addEvent(type, +index));
});

display(values);
.as-console-wrapper { max-height: 100% !important; top: 0; }
td { text-align: center; width: 20%; }
<table>
    <tr>
        <td><button id="up0">^</button></td>
        <td><button id="up1">^</button></td>
        <td><button id="up2">^</button></td>
        <td><button id="up3">^</button></td>
        <td><button id="up4">^</button></td>
    </tr>
    <tr>
        <td id="value0"></td>
        <td id="value1"></td>
        <td id="value2"></td>
        <td id="value3"></td>
        <td id="value4"></td>
    </tr>
    <tr>
        <td><button id="down0">v</button></td>
        <td><button id="down1">v</button></td>
        <td><button id="down2">v</button></td>
        <td><button id="down3">v</button></td>
        <td><button id="down4">v</button></td>
    </tr>
</table>
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • What happens if the array length is not perfect divided, example: length 3 array: [33.3,33.3,33.3], or the step isn't integer. Also can you provide the logic for increasing the values? – Thanos Sakis Mar 18 '22 at 21:28
  • how much would you like to increase a value? – Nina Scholz Mar 18 '22 at 21:29
  • The same way as the decreasing and with a step made based of the length of array. In a 3 length array step = 1/3. – Thanos Sakis Mar 18 '22 at 21:34
  • the problem with decimals is to get possibly not really the wanted sum (see [here](https://stackoverflow.com/questions/588004/is-floating-point-math-broken)). do you want for 5 items 0.2 for changing? – Nina Scholz Mar 18 '22 at 21:41
  • Yes, that because I want to increase/decrease slowly on big arrays and faster on smaller ones. I don't have a problem if there is a +-1 off. – Thanos Sakis Mar 18 '22 at 21:46
  • Sorry, yesterday I didn't realize that your algorithm was increasing an index and what I wanted to say is that it doesn't give the option for the index to decrease. Your code seems to work just fine for increasing only. What about, when I want to to decrease a value? The function should have an option for the type of the increasing (-1,1) and increase or decrease the index and fix the rest of the values so its still 100 sum. – Thanos Sakis Mar 19 '22 at 17:03
  • what should happen, if you want to decrease the index value and this has not the amount which is necessary to spread the wanted `1 / array.length` value? – Nina Scholz Mar 19 '22 at 17:33
  • The same thing with increasing. If the index value increases to the point the rest can't go below 0 it kind of locks at that maximum value. With your code if I change the decrement variable to negative ( `const decrement = -1 / array.length;` ) a array A = [33.3, 33.3, 33.3] and loop of 50 times calls change(array, 0) will make the array `A = [ -1.3988810110276972e-14, 50.00000000000012, 50.00000000000012 ]` . But a loop of 49 times results the array `A = [ 0.6666666666666526, 49.666666666666785, 49.666666666666785 ]` which array[0] has the minimum value and I want it locked there – Thanos Sakis Mar 19 '22 at 18:23
  • After that if I increase the index array[1] only the array[2] can decrease because array[0] is already one step going below 0. Its very close but I am trying to sort this out for some time now and I can't.. – Thanos Sakis Mar 19 '22 at 18:31
  • You are awesome! Thank you very much, exactly what I needed! – Thanos Sakis Mar 20 '22 at 20:07
0

Details commented example below

// Utility function
const log = data => console.log(JSON.stringify(data));

const arr1 = [20, 20, 20, 20, 20];
const arr2 = [-5, 115, -5, -5];

/**@function
 *@name incDec
 *@description Increase/decrease a number by the given index of a given 
 * array by a given number and inversely decrease/increase the rest of 
 * the numbers evenly until the sum of all numbers within the array is 
 * the same as it was originally.
 *@param {number} index - Index number of the number being changed.
 *@param {number} by - Number to decrease/increase first parameter by.
 *@param {array<number>} array - The array of numbers
 *@returns {array<number>} A new array of modified numbers
 */
const incDec = (index, by, array) => {
  /* 
  Chrome doesn't divide negative numbers correctly.
  Simple divison in of itself needs to be simplified.
  So a ❉ will denote this as the reason.
  */
  let mod = Math.abs(by);
  /*
  Return a new array with the targeted number modified by >by<.
  */
  const newArr = array.map((num, idx) => idx == index ? num + by : num);

  const div = array.length - 1; //❉
  /*
  Divide the number it changed by, by the number of remaining numbers 
  in the array.
  */
  let quo = mod / div;
  quo = by < 0 ? -Math.abs(quo) : quo; //❉
  // Return a new array that has the modified numbers.
  return newArr.map((num, idx) => idx == index ? num : num - quo);
};

log(incDec(3, 8, arr1));
log(incDec(2, -8, arr2));
.as-console-row::after {
  width: 0;
  font-size: 0;
}

.as-console-row-code {
  width: 100%;
  word-break: break-word;
}
zer00ne
  • 41,936
  • 6
  • 41
  • 68