JS计算二维数组中相同元素的平均值

6

我有一个由数组表示的两列“表格”。第一列是1到20的数字,它们是标签;第二列是相应的值(以秒为单位):

my_array = [ [ 3,4,5,3,4,5,2 ],[ 12,14,16,11,12,10,20 ] ];

我需要每个标签的平均值(均值):
my_mean_array = [ [ 2,3,4,5 ],[ 20/1, (12+11)/2, (14+12)/2, (16+10)/2 ] ];
// edit: The mean should be a float - the notion above is just for clarification.
// Also the number 'labels' should remain as numbers/integers.

我的尝试:

var a = my_array[0];
var b = my_array[1];
m = [];
n = [];
for( var i = 0; a.length; i++){
    m[ a[i] ] += b[i]; // accumulate the values in the corresponding place
    n[ a[i] ] += 1; // count the occurences
}
var o = [];
var p = [];
o = m / n;
p.push(n);
p.push(o);
3个回答

3
这个怎么样(原生JS,不会在旧浏览器上出问题):
function arrayMean(ary) {
  var index = {}, i, label, value, result = [[],[]];

  for (i = 0; i < ary[0].length; i++) {
    label = ary[0][i];
    value = ary[1][i];
    if (!(label in index)) {
      index[label] = {sum: 0, occur: 0};
    }
    index[label].sum += value;
    index[label].occur++;
  }
  for (i in index) {
    if (index.hasOwnProperty(i)) {
      result[0].push(parseInt(i, 10));
      result[1].push(index[i].occur > 0 ? index[i].sum / index[i].occur : 0);
    }
  }
  return result;
}

如果你想要花哨的效果,我已经创造了一些其他方法来实现它。它们依赖于外部库,并且很可能比本地解决方案慢一个数量级。但是它们看起来更加漂亮。

使用underscore.js,它可以像这样:

function arrayMeanUnderscore(ary) {
  return _.chain(ary[0])
    .zip(ary[1])
    .groupBy(function (item) { return item[0]; })
    .reduce(function(memo, items) {
      var values = _.pluck(items, 1),
          toSum = function (a, b) { return a + b; };

      memo[0].push(items[0][0]);
      memo[1].push(_(values).reduce(toSum) / values.length);
      return memo;
    }, [[], []])
    .value();
}

// --------------------------------------------

arrayMeanUnderscore([[3,4,5,3,4,5,2], [12,14,16,11,12,10,20]]);
// -> [[2,3,4,5], [20,11.5,13,13]]

或者像这样,使用真正伟大的 linq.js(我用的是v2.2):

function arrayMeanLinq(ary) {
  return Enumerable.From(ary[0])
    .Zip(ary[1], "[$, $$]")
    .GroupBy("$[0]")
    .Aggregate([[],[]], function (result, item) {
      result[0].push(item.Key());
      result[1].push(item.Average("$[1]"));
      return result;
    });
}

// --------------------------------------------

arrayMeanLinq([[3,4,5,3,4,5,2], [12,14,16,11,12,10,20]]);
// -> [[3,4,5,2], [11.5,13,13,20]]

怀疑的情况下,“高级”的实现比本地实现慢了一个数量级:jsperf对比

一些改进:
  • 我认为 if 测试:index.hasOwnProperty(i) 是无用的。
  • 我认为 parseInt 是无用的,因为 my_array 有数字。
  • 要测试是否发生为0,请使用句子?而不是 ocurr==0?0:sum/occur。
- Adrian Maire
只是想澄清一下:谢谢大家!我选择了这个答案,因为它可以直接给我正确/预期的结果。虽然robertklep和Adrian Maire提供的解决方案非常优雅,但最终会在我的“label”子数组中得到字符串,这不是问题,但Tomalak提供了一个合适的解决方案。 - Chrugel
2
@AdrianMaire 在使用 for ... in 遍历对象时,hasOwnProperty 是绝不无用的。由于 Tomalak 使用对象(index)来存储出现次数的映射,因此需要使用 parseInt。如果没有 parseInti 将是一个字符串。 - Yoshi
@Yoshi 谢谢你指出这个问题,我也正想写同样的内容。-- Adrian,你永远不知道对象原型是否已被扩展,这就是这个检查的作用。我已经将检查0改为三元运算符,这样更清晰了。 - Tomalak
@Chrugel 没有什么是直截了当的。一切都取决于观点。如果您添加JS库解决方案将变得更好,但是您需要学习该库(并增加页面的负担)。无论如何,我已经创建了替代功能,请查看。 - Tomalak
显示剩余2条评论

0
var temp = {}; 
my_array[0].map(function(label, i) {
  if (! temp[label])
  {
    temp[label] = [];
  }
  temp[label].push(my_array[1][i]);
});
var result = [ [], [] ];
for (var label in temp) {
  result[0].push(label);
  result[1].push(
    temp[label].reduce(function(p, v) { return p + v }) / temp[label].length
  );
}

0

这个函数不会像你的结果示例那样对结果数组进行排序。如果您需要排序,请告诉我,我会添加它。

function getMeanArray(my_array)
{
    m = {}; //id={count,value}
    for( var i = 0; i<my_array[0].length; i++){
        if (m[my_array[0][i]]===undefined)
        { 
            m[my_array[0][i]]={count:0, value:0};
        }
        m[ my_array[0][i] ].value += my_array[1][i]; // accumulate the values in the corresponding place
        m[ my_array[0][i] ].count++; // count the occurences
    }
    var my_mean_array=[[],[]];
    for (var id in m)
    {
        my_mean_array[0].push(id);
        my_mean_array[1].push(m[id].count!=0?m[id].value/m[id].count:0);
    }
    return my_mean_array;
}

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