UnderscoreJS group using numeric intervals for a histogram

Is there a way to group a list of numbers into numerical intervals with underscores?

// Input: var arr = [0,1,2,8]; var interval = 3; // Output: // There are 3 numbers from 0 to <3 // There are 0 numbers from 3 to <6 // There is 1 number from 6 to <9 // Returns [3, 0, 1] 

I noticed that some solutions do not test large values. Try the second test case:

 var arr = [110,113,116,119]; 
+5
source share
4 answers

In simple Javascript, you can simply divide the number by an interval and use the integer part to group.

With an array and missing intervals.

 function getHistogram(array, interval) { var bin, result = []; array.forEach(function (a) { var key = Math.floor(a / interval); if (!bin) { bin = [key, key]; result[0] = 0; } while (key < bin[0]) { --bin[0]; result.unshift(0); } while (key > bin[1]) { ++bin[1]; result.push(0); } ++result[key - bin[0]]; }); return result; } console.log(getHistogram([0, 1, 2, 8], 3)); console.log(getHistogram([110, 113, 116, 119], 3)); console.log(getHistogram([15, 10, 26], 3)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 

With an object and missing intervals.

 function getHistogram(array, interval) { var bin, result = {}; array.forEach(function (a) { var key = Math.floor(a / interval); if (!bin) { bin = [key, key]; result[key] = 0; } while (key < bin[0]) { result[--bin[0]] = 0; } while (key > bin[1]) { result[++bin[1]] = 0; } ++result[key]; }); return result; } console.log(getHistogram([0, 1, 2, 8], 3)); console.log(getHistogram([110, 113, 116, 119], 3)); console.log(getHistogram([15, 10, 26], 3)); 
 .as-console-wrapper { max-height: 100% !important; top: 0; } 
+1
source

The idea behind this solution is to generate a range of X values ​​for the histogram, and then iterate over the array while placing the values ​​less than each X in the counts.

 var getIntervalCounts = function (arr){ // Sort backwards to iterate through in reverse later arr.sort(function(a,b){ return b - a; }); var interval = 3; // Get values for range of histogram x axis var greatestVal = arr[0]; var leastVal = arr[arr.length-1]; var x = _.range(leastVal + interval, greatestVal + interval + 1, interval); // Get value counts for histogram y axis var y = _(x).map(function(num){ var count = 0; // Remove elements from end of array while we iterate through // to avoid duplicate lookups for (var i = arr.length - 1; i >= 0; i--){ // Put everything less than the histogram x in that x value if (arr[i] < num) { count++; arr.pop(); } else { break; } } return count; }); // console.log(x); console.log(y); } getIntervalCounts([0,1,2,8]); getIntervalCounts([110,111,112,118]); getIntervalCounts([110,111,112,118,119]); getIntervalCounts([110,111,112,118,119,120]); 
 <script src="http://underscorejs.org/underscore-min.js"></script> 
0
source

This is my approach using simple JS. I added a few more numbers to your example to better test it.

I use a new array to store the number of occurrences in each interval. The first position will represent the number of elements <interval, the second <2 * interval ... etc. Etc.

I keep a reference to the last valid index so that I can fill in empty cells with zeros.

UPDATE: Minor fix to exclude the first number as undefined when there is no value in the range 0 <= x <= 3

 // Input: var arr = [ 110, 113, 116, 119 ], interval = 3, res = [], lastIdx = -1; arr.forEach(function(el) { var intPart = Math.floor(el / interval), index = el && intPart * interval === el ? intPart - 1 : intPart; res[index] = (res[index] || 0) + 1; res.fill(0, lastIdx + 1, index); lastIdx = index; }); console.log(res); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> 

UPDATE 2:. Version using underscore. It uses countBy to get intervals and avoids using Array.prototype.fill because it is an ES6 function.

 function getZeroFilledArr(len) { return Array.apply(null, Array(len)).map(Number.prototype.valueOf, 0); } function getIntervalLentgh(intervals) { return Number(_.max(intervals)) + 1; } var arr = [110, 113, 116, 119], interval = 3, intervals = _.countBy(arr, function(el) { var intPart = Math.floor(el / interval); return el && intPart * interval === el ? intPart - 1 : intPart; }), zeroFilledArr = getZeroFilledArr(getIntervalLentgh(_.keys(intervals))); console.log(_.reduce(intervals, function(memo, value, key) { memo[key] = value; return memo; }, zeroFilledArr)); 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> 
0
source

I think this is the most elegant way to doit

 _.mixin({ step: function(ar, s) { var dummy = _.times(Math.ceil(_.max(ar)/s), function(){ return 0; }); _.each(ar, function(x) { dummy[Math.floor(x/s)]++; }); return dummy; } }); var arr = [0,1,2,4,6,7,8,9,22]; var res1 = _.step(arr, 3); console.log("RESULT1 ", res1.length, res1); var arr2 = [ 110, 113, 116, 119 ]; var res2 = _.step(arr2, 3); console.log("RESULT2 ", res2.length, res2 ); 
 <script src="http://underscorejs.org/underscore-min.js"></script> 

Previous solutions do not take into account the maximum value of the array, so do not create the correct size of the array of results.

Using _.mixin gives you the ability to define a new underscore function that can be applied to an array. So I created a _.step function that takes an array and a step.

0
source

Source: https://habr.com/ru/post/1262654/


All Articles