What you want is a linear function of the array values
with the constraint that
newMax = a * oldMax + b
and
newMin = a * oldMin + b
where oldMin
and oldMax
come from the array, while newMin
and newMax
are given (0
and 140
in your case.)
If you subtract these 2 equations, you get
newMax - newMin = a * (oldMax - oldMin)
i.e.
a = (newMax - newMin) / (oldMax - oldMin)
Replacing this in the second equation you get
newMin = (newMax - newMin) / (oldMax - oldMin) * oldMin + b
i.e.
b = newMin - (newMax - newMin) / (oldMax - oldMin) * oldMin
If we call oldRange = oldMax - oldMin
and newRange = newMax - newMin
, we can rewrite these as:
a = newRange / oldRange
b = newMin - newRange / oldRange * oldMin
and if we call scale = newRange / oldRange
we can further simplify the notation:
a = scale
b = newMin - scale * oldMin
This for the algebra. Now the JavaScript:
let oldArr = [-0.6, -1.3, 0.4, 7.4, 6.1, 4.4, -0.1, 0];
let oldMin = Math.min.apply(null, oldArr);
let oldMax = Math.max.apply(null, oldArr);
let oldRange = oldMax - oldMin;
let newMin = 0;
let newMax = 140;
let newRange = newMax - newMin;
let scale = newRange / oldRange;
let a = scale;
let b = newMin - scale * oldMin;
let newArr = oldArr.map(x => a * x + b);
console.log(newArr);
Never mind that the new max is calculated as 139.99999999999997
, you cannot have total precision with floating point.
Math.min.apply
and Math.max.apply
“should only be used for arrays with relatively few elements”, according to this (but according to the accepted answer to this question, you can safely pass arrays of up to 65535 elements). The alternative, without resorting to a library, is
let oldMin = oldArr.reduce((upToNow, current) => Math.min(upToNow, current));
let oldMax = oldArr.reduce((upToNow, current) => Math.max(upToNow, current));
The reduce
method is only passed a 2-argument “reducer” function here (no initial value), so that on the first call the reducer will receive the first and second element of the array, instead of an undefined initial value and the first element.
Since some people are very keen on efficiency here (which I would be too, if I had evidence of huge arrays entering this algorithm), I'll also write down the fastest way of calculating the min and the max of an array at the same time. I'll write it as a function, for general use.
function arrayMinMax(arr) {
return arr.reduce((minMax, cur) => cur < minMax[0] ? [cur, minMax[1]] :
(cur > minMax[1] ? [minMax[0], cur] : minMax),
[arr[0], arr[0]]);
}
let oldArr = [-0.6, -1.3, 0.4, 7.4, 6.1, 4.4, -0.1, 0];
let [oldMin, oldMax] = arrayMinMax(oldArr);
let oldRange = oldMax - oldMin;
let newMin = 0;
let newMax = 140;
let newRange = newMax - newMin;
let scale = newRange / oldRange;
let a = scale;
let b = newMin - scale * oldMin;
let newArr = oldArr.map(x => a * x + b);
console.log(newArr);
BTW, the kind of mapping you need often involves rounding to integer values. That would best be done at the same time as the linear transformation:
let newArr = oldArr.map(x => Math.round(a * x + b));