UnderscoreJS按数字间隔进行分组以制作直方图

5
有没有一种方法可以将一组数字分组成带下划线的数值区间?
// 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]

我注意到有些解决方案没有测试更大的值。请尝试第二个测试用例:
var arr = [110,113,116,119];

数据已排序吗? - Nina Scholz
4个回答

1
在纯JavaScript中,您只需将数字除以间隔,使用整数部分进行分组即可。
使用数组和缺失的间隔。

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; }

有一个对象和缺失的时间间隔。

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; }


你的代码输出了0:3和6:1,但我还需要一个3:0。我认为你走在了正确的道路上。 - Jack Wade
1
@JackWade,现在在一个默认为零的数组中。 - Nina Scholz
这对于更大的值来说会出现问题。var arr = [ 110, 113, 116, 119 ], interval = 3, - Jack Wade
数据是否已排序?您只需要填充缺失零的间隔吗? - Nina Scholz

0
这个解决方案的想法是生成直方图X值的范围,然后在迭代数组时将小于每个X的值放入计数中。

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's 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's 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>


1
到目前为止,这是唯一正确的解决方案。我希望有一个更优雅的 _.chain 风格的解决方案。 - Jack Wade
没错...我相信有一种更数学化的方法可以在O(n)时间内轻松处理未排序数组。 - Josh Hibschman

0

这是我使用纯JS的方法。我在你的示例中添加了更多数字以更好地测试它。

我使用一个新数组来存储每个区间中出现的次数。第一个位置表示小于interval的元素数量,第二个位置表示小于2 * interval的元素数量...以此类推。

我保留对最后一个有效索引的引用,以便我可以用零填充空单元格。

更新:修复了一个小错误,避免在范围0 <= x <= 3内没有值时将第一个数字设置为undefined

// 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>

更新2: 使用underscore的版本。它使用countBy来获取间隔,并避免使用Array.prototype.fill,因为它是ES6功能。

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>


您没有按照要求使用下划线。 - hpfs
尝试变量 arr = [ 110, 113, 116, 119 ]; - Jack Wade

0

我认为这是最优雅的做法

_.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>

之前的解决方案没有考虑数组的最大值,因此无法创建正确大小的结果数组。

使用 _.mixin 可以让您定义一个新的函数,适用于数组。因此,我创建了一个 _.step 函数,它接受数组和步长作为参数。


这个代码在以下变量中出现错误: var arr = [ 110, 113, 116, 119 ]; - Jack Wade
为什么您说它"breaks"?它会返回一个包含40个元素的数组,其中36个元素的值为0,最后四个元素的值为1。 - hpfs
在 mixin 中对 ar 的引用进行了修正以解决一个小 bug。 - hpfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接