Search code examples
javascriptnormalize

How to map a number in some ranges


I have a range of values like, for example, [0, 100] = [minValue, maxValue] and the number of bands, for example BANDS_NUMBER = 5. So I can obtain these bands:

[0 - 20]
[21 - 40]
[41 - 60]
[61 - 80]
[81 - 100]

Then I want to associate a scale value at each range:

i: 0   -->  [0 - 20]   -->  0.2
i: 1   -->  [21 - 40]  -->  0.4
i: 2   -->  [41 - 60]  -->  0.6
i: 3   -->  [61 - 80]  -->  0.8
i: 4   -->  [81 - 100] -->  1

This value is computed in this way: (i + 1) / BANDS_NUMBER where i is the index of a hypothetical loop.

Then I have an input n whose value is in range [minValue, maxValue] = [0, 100].

What I want is the scale value related to this number. So, for example, if:

n = 0  --> scaleValue = 0.2 
n = 10 --> scaleValue = 0.2 
n = 20 --> scaleValue = 0.2 
n = 35 --> scaleValue = 0.4
n = 68 --> scaleValue = 0.8 
n = 99 --> scaleValue = 1
...

How can I create a function like that? I imagine a function like that:

function map(n, minValue, maxValue, bandsNumber) {
  const scaleValue = ...
  return scaleValue
}

All the values here are examples, I want that all works with any other values.

I don't know how to do to that. I need some help...


Solution

  • Nina Scholz's answer is wrong. Her normalize function returns 0.4 instead of 0.2 for the value 20:

    function normalize(min, max, bands, n) {
        return n === max
            ? 1
            : Math.floor(1 + ((n - min) / (max - min)) * bands) / bands;
    }
    
    console.log(normalize(0, 100, 5, 20)); // expected 0.2, actual 0.4

    Because 20 is in the first band, it should have the value 0.2:

    i: 0   -->  [0  - 20]  -->  0.2
    i: 1   -->  [21 - 40]  -->  0.4
    i: 2   -->  [41 - 60]  -->  0.6
    i: 3   -->  [61 - 80]  -->  0.8
    i: 4   -->  [81 - 100] -->  1
    

    The correct answer is:

    const index = (min, max, bands, n) =>
        Math.floor(bands * (n - min) / (max - min + 1));
    
    const band = n => index(0, 100, 5, n);
    
    console.log(band(0),  band(20));  // 0 0
    console.log(band(21), band(40));  // 1 1
    console.log(band(41), band(60));  // 2 2
    console.log(band(61), band(80));  // 3 3
    console.log(band(81), band(100)); // 4 4

    As you can see, the edge cases are handled correctly. How did we get to this answer?

    1. First, we find the length of the range which is max - min + 1. The + 1 is important because there are 101 elements in the range [0 - 100] inclusive.
    2. Next, we get the index of the number n in the given range (i.e. n - min).
    3. Then, we divide the index of n by the number of elements in the range to get a value in the range [0 - 1). Note that 1 is not in the range.
    4. Finally, we multiply this value by the number of bands and discard the fractional part. The result is our index.

    Note that if the length of the range is not divisible by the number of bands then the first x bands will have one additional element, where x is the remainder of dividing the length of the range by the number of bands.

    Finally, we can get the value you want by incrementing the resulting index and then dividing it by the number of bands:

    const index = (min, max, bands, n) =>
        Math.floor(bands * (n - min) / (max - min + 1));
    
    const value = (min, max, bands, n) =>
        (index(min, max, bands, n) + 1) / bands;
    
    const result = n => value(0, 100, 5, n);
    
    console.log(result(0),  result(20));  // 0.2 0.2
    console.log(result(21), result(40));  // 0.4 0.4
    console.log(result(41), result(60));  // 0.6 0.6
    console.log(result(61), result(80));  // 0.8 0.8
    console.log(result(81), result(100)); // 1 1

    Hope that helps.