I am looking for feedback on:
any inputs on unforeseen use-cases where the code would be incorrect. (floating-point errors, etc).
my method of using a "static class" acceptable
is using the default of 10 decimal places a good default or should I not round/truncate to decimal places if possible
Acceptable suggestions include a refactor of logic if it makes it more readable/correct.
Code must be runnable with latest chrome/FF and IE9+. That means no ES6 suggestions.
What the code does:
Given an array, this "static" class will balance/equally distribute the values to maintain the original distribution as close as possible while also summing up to a certain value (goal).
It has the benefit of using asking for the return array/answer to be a certain amount of decimal places.
This code was written as a solution to this question: https://stackoverflow.com/questions/41398108/balance-array-of-numbers-to-equal-1#41398108 where numerous comments bring up potential pitfalls which I think are accommodated as best as possible here.
Here is example outcomes:
var a = [0.33333333331, 0.33333333331, 0.33333333333];
console.log(Balancer.balance(a, 1, 3)); //0.333, 0.333, 0.334
var a = [1, 55, 22, 67];
console.log(Balancer.balance(a, 69699, 0)); //480, 26438, 10575, 32206
var a = [10, 10, 10, 10, 15000];
console.log(Balancer.balance(a, 17000, 0)); //11, 11, 11, 11, 16956
var a = [ 0.00005 ,5 ,0.00005 ,0.00005 ,0.00005 ,0.00005 ,0.00005 ,0.00005 ]
console.log(Balancer.balance(a, -5, 5)); // [-0.00005, -4.99965, -0.00005, -0.00005, -0.00005, -0.00005, -0.00005, -0.00005]
var a = [
0.0015974718789698097
,0.755383581038094
,0.13950473043946954
,0.0011978091842754731
,0.005126875727346068
,0.0042250281407886295
,0.0001720958819913952
,0.0047584144830165875
,0.0835073272489086
,0.00016098907002300275
,0.0028037075787230655
,0.0014378579473690483
,0.00012411138102484662
]
console.log(Balancer.balance(a, 1, 3)); // [0.002, 0.755, 0.14, 0.001, 0.005, 0.004, 0, 0.005, 0.084, 0, 0.003, 0.001, 0]
Executable code: http://jsbin.com/motedexifo/1/edit?js,console
var Balancer = (function() {
return {
isNumber: function(input) {
return !isNaN(parseInt(input, 10)) && !isNaN(Number(input));
},
divideEach: function(array, divisor) {
for (var i = 0; i < array.length; i++) {
array[i] /= divisor;
}
return array;
},
indexOfMin: function(arr) {
if (arr.length === 0) {
return -1;
}
var min = arr[0];
var minIndex = 0;
for (var i = 1; i < arr.length; i++) {
if (arr[i] < min) {
minIndex = i;
min = arr[i];
}
}
return minIndex;
},
indexOfMax: function(arr) {
if (arr.length === 0) {
return -1;
}
var max = arr[0];
var maxIndex = 0;
for (var i = 1; i < arr.length; i++) {
if (arr[i] > max) {
maxIndex = i;
max = arr[i];
}
}
return maxIndex;
},
convertToFactors: function(array) {
var total = this.sum(array);
return this.divideEach(array, total);
},
sum: function(array) {
var total = 0;
for (var i = 0; i < array.length; i++) {
if (this.isNumber(array[i])) {
total += Number(array[i]);
}
}
return total;
},
round: function(number, places) {
var multiplier = Math.pow(10, places);
return Math.round(number * multiplier) / multiplier;
},
roundEach: function(array, places) {
for (var i = 0; i < array.length; i++) {
if (this.isNumber(array[i])) {
array[i] = this.round(array[i], places);
}
}
return array;
},
balance: function(array, goal, places) {
places = (places !==undefined) ? places : 10;
var copy = JSON.parse(JSON.stringify(array)); // deep copy
var factors = this.convertToFactors(array);
this.roundEach(copy, places);
goal = this.round(goal, places);
var sum = this.round(this.sum(copy), places);
var old_difference = null;
while (goal !== sum) {
var difference = goal - sum;
if (old_difference == difference) {
var idx = (difference > 0) ? this.indexOfMax(factors) : this.indexOfMin(factors);
copy[idx] += this.round(difference, places);
} else {
for (i = 0; i < factors.length; i++) {
copy[i] += this.round(difference * factors[i], places);
}
}
sum = this.round(this.sum(copy), places);
old_difference = difference;
}
this.roundEach(copy, places);
return copy
},
}
})();
divideEach
can be written asreturn array.map(e => e / divisor);
.indexOfMin
asreturn arr.length ? arr.indexOf(Math.min(...arr)) : -1;
similarlyindexOfMax
asreturn arr.length ? arr.indexOf(Math.max(...arr)) : -1;
andsum
asreturn array.reduce((a, b, 0) => a + b);
assuming all elements of the array are Numbers. \$\endgroup\$