I am trying to generalize the following function that I have implemented:
/** * Calculates an interval for the given age. * * @memberof module:Users/Functions * @function getAgeInterval * @param {number} age - The age of the user. * @param {number} [minimumAge=18] - The minimum age. * @param {number} [range=10] - The range. * @throws {Error} The given age must be greater or equal than the minimum age. * @returns {string} The age interval. */ export default (age, minimumAge = 18, range = 10) => { if (age < minimumAge) { throw new Error( "The given age must be greater or equal than the minimum age."; ); } const start = Math.floor((age - 1) / range) * range + 1; const end = start + range - 1; const interval = `${Math.max(start, minimumAge)}-${end}`; return interval; };
Basically, in this method, I group the age of my users using a minimum age and a range. Here is an example:
const getAgeInterval = (age, minimumAge = 18, range = 10) => { if (age < minimumAge) { throw new Error( "The given age must be greater or equal than the minimum age." ); } const start = Math.floor((age - 1) / range) * range + 1; const end = start + range - 1; const interval = `${Math.max(start, minimumAge)}-${end}`; return interval; }; // // MAIN // for (let age = 18; age < 100; age += Math.round(Math.random() * 10)) { console.log(`${age}: ${getAgeInterval(age)}`); }
For now, the method is only working for “ages”. But I suppose it is possible to make it work with any type of numbers, (i.e. the total followers counter of a user).
Users might have different number of followers, and I need to group it reusing the method I implemented. The output should look like:
0: "0-10" 100: "11-100" 999: "101-1000" 1117: "1001-10000" 9999: "1001-10000" 15201: "10001-100000"; 1620620: "1000001-10000000"
As you can see, the only difference, in order to make it work, is the “dynamic” range. If you take a look at the output, the range goes from 10 to millions.
Any ideas? Any generic implementation to allow dynamic ranges?
UPDATE
Here is the generic method:
const calculateInterval = (counter, minimumCounter = 0, range = 10) => { if (counter < minimumCounter) { throw new Error( "The given counter must be greater or equal than the minimum counter." ); } const start = Math.floor((counter - 1) / range) * range + 1; const end = start + range - 1; const interval = `${Math.max(start, minimumCounter)}-${end}`; return interval; }; // // MAIN // const counters = [0, 100, 999, 1117, 9999, 15201, 1620620]; counters.forEach((totalFollowers) => { console.log(`${totalFollowers}: ${calculateInterval(totalFollowers)}`); }); // // Q: HOW DO I MAKE THE RANGE DYNAMIC (BASED ON THE AMOUNT OF FOLLOWERS)? //
OUTPUT MUST BE:
0: "0-10" 100: "11-100" 999: "101-1000" 1117: "1001-10000" 9999: "1001-10000" 15201: "10001-100000"; 1620620: "1000001-10000000"
Advertisement
Answer
What you’re looking for is called a logarithmic scale. In this case, the interval is not incremented but multiplied by the range in each step.
You can find the beginning of the range by raising r
to the floor of the r
-base logarithm of n-1
, where r
is the range and n
is the number.
To get the edge cases right though, you need to make some adjustments (add one to the start of the range, add a default for values smaller or equal to the range, etc):
const baseNlog = (base, x) => Math.log(x) / Math.log(base) const logarithmicInterval = (n, range = 10) => { if(n <= range) return `0-${range}` const start = range ** Math.floor(baseNlog(range, n-1)); const end = start * range; const interval = `${start + 1}-${end}`; return interval; }; // // MAIN // console.log([ 0, 1, 10, 11, 100, 999, 1117, 9999, 15201, 1620620 ].map(e => `${e}: ${logarithmicInterval(e)}`))