0

I have the following array:

var myNumbers = [70.37037037037037, 11.11111111111111, 11.11111111111111, 7.4074074074074066];

I need each number to be rounded and all of their sum to total 100. If they fall short the difference will be made up for by adding 1 to items in decreasing order of their decimal parts. This is called the Largest Remainder Method (and I got the following code form How to make rounded percentages add up to 100%). Here's the underscore code to get this:

var off = 100 - _.reduce(myNumbers, function(acc, x) {
    return acc + Math.round(x)
}, 0);

var rounded_percentages = _.chain(myNumbers)
    .sortBy(function(x) {
        return Math.round(x) - x
    })
    .map(function(x, i) {
        return Math.round(x) + (off > i) - (i >= (myNumbers.length + off))
    })
    .value();

The result is:

[8, 70, 11, 11]

This works great but the order is not preserved. How can I achieve the above while either preserving the order or do the whole operation with an object instead of an array and have the proper key mapping retained?

With the order preserved it should result in:

[70, 11, 11, 8]

With key mapping the initial var would look like:

var myNumbers = {
    firstNum: 70.37037037037037,
    secondNum: 11.11111111111111,
    thirdNum: 11.11111111111111,
    fourthNum: 7.4074074074074066
};

and the result would be:

{
    fourthNum: 8,
    firstNum: 70,
    secondNum: 11,
    thirdhNum: 11
};
Community
  • 1
  • 1
  • 3
    Before starting the process, convert the array into an array of objects with two properties each: the original value, and the original index. Do your algorithm such that the sorting uses the values, but then at the end you can re-sort by the original index and then extract the adjusted values. – Pointy Oct 15 '15 at 16:49
  • 2
    Or even better: Don't re-sort by the original index, just iterate and put the new values in a new array, at the place designated by the index. – Bergi Oct 15 '15 at 16:54
  • @Bergi yes that'd be faster but I'm lazy so I'd probably type the `.sort()` :) – Pointy Oct 15 '15 at 16:56
  • Sorry I've never used underscore before so am not sure how to go about it. Can I get an example of either of these methods. Regarding the sortBy(), I noticed if I don't have that in the code the order is retained but the results are unexpected, the total sometimes adds up to over 100 and the values are sometimes in the negatives. –  Oct 15 '15 at 16:58
  • @Pointy: I think it's easier :-) I'm not even sure whether that thing with `Math.round` and then `Math.round(x) + (off > i) - (i >= (myNumbers.length + off))` is an accurate implementation of the *Largest Remainder Method* – Bergi Oct 15 '15 at 17:14

1 Answers1

1

Don't change the order of the array at all. Do only create a permutation (an array of indexes that is then sorted by a property of the array value each index points to), and play your algorithm on that.

var rounded_percentages = _.map(myNumbers, Math.floor);
var off = _.reduce(rounded_percentages, function(acc, x) { return acc - x; }, 100);
var permutation = _.sortBy(_.map(myNumbers, function(_, i) { return i; }), function(i) {
    return rounded_percentages[i] - myNumbers[i]; // those with the largest diff first
});
for (var i=0; i<off; i++)
    rounded_percentages[permutation[i]]++

Which is a much closer implementation of the Largest Remainder Method, the Math.round in your implementation is odd.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375