在 javascript 中重构复杂对象数组的最有效方法?

Most efficient way to restructure complex array of objects in javascript?

我有一大组看起来与此类似的数据:

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

我需要重新排列这些数据,使其看起来更像这样:

var newData = [
  { date : '2014-10-29', us : 45.3, africa : 60.5, south_america : 0 },
  { date : '2014-10-30', us : 30, africa : 0, south_america : 10 }
]

我是处理这样的数据集的新手,我正在努力寻找任何有效的方法来处理这个...我唯一能想到的是使用多个 for 循环,看起来很糟糕。有没有人有什么想法或建议?

首先想到的是这样做

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];
var t = {};
var result = [];
original.forEach(function(entry) {
    var n = t[entry.date] = t[entry.date] || {};
    n.date = entry.date;
    n[entry.country] = entry.cost;
});
for(var k in t) {
    if (t.hasOwnProperty(k)) {
        result.push(t[k]);
    }
}
console.log(result);

没有临时对象可能有更好的方法,但你想要高效,我认为这是我能想到的最有效的方法

不幸的是,看起来粗糙的多个循环是解决这个问题的方法。您可以将可怕的代码放在一个函数中,然后将该函数隐藏在代码的底部,从而使自己对可怕的代码感觉更好。

Jaromanda 的答案在内存和时间方面都更有效率,但如果你真的有大量数据,你应该考虑在服务器上重新组织它,因为输出数据比原始数据小,它会更少转移。尽管您可能喜欢将工作量分摊给客户的想法。

如果您仍然认为重组数据客户端是可行的方法,您可能会喜欢这个功能,如果您更改 属性 名称,它可以很容易地使用。

function groupAndMerge(original,groupBy,merge){

    var grouped={},list=[];

    original.forEach(function(o){
        var k=o[groupBy];
        if(!grouped[k]) grouped[ k ]=[];
        grouped[k].push(o);
    });

    for(var i in grouped){
        var d={};
        list.push(d);
        grouped[i].forEach(function(o){
            d[groupBy]=i;
            for(var k in merge)     d[o[k]]=o[merge[k]];
        });

    }

    return list;

}

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

var result=groupAndMerge(original,'date',{'country':'cost'});

如果数据已经排序,有一种只使用一个循环的更快的方法:

var output = document.querySelector('#output');

var original = [
  { country : 'us', date : '2014-10-29', cost : 45.3 },
  { country : 'africa', date : '2014-10-29', cost : 60.5 },
  { country : 'south_america', date : '2014-10-30', cost : 10 },
  { country : 'us', date : '2014-10-30', cost : 30 }
];

function mapData(data) {
  var newData = [];  
  var newMap = {};
    newMap.date = data[0].date;
  
    for (var i = 0; i < data.length; i++) {
        if (data[i].date === newMap.date) {
            newMap[data[i].country] = data[i].cost;
        }
        else {
            newData.push(newMap);
            newMap = { date: data[i].date, [data[i].country]: data[i].cost };
        }
    }

    newData.push(newMap);
    return newData;
}

output.innerHTML = JSON.stringify(mapData(original));
<html>
    <body>
        <div id="output"></div>
    </body>
</html>

这比 Jaromanda 的解决方案更快。但是,同样,它取决于已经排序的数据。如果必须先对数据进行排序,它很快就会落后:jsPerf .

然而,这两种解决方案都忽略了这样一个事实,即 OP 希望每个国家/地区都有每个日期的条目,即使特定日期的成本不存在(为零)也是如此。此要求还意味着必须使用多个循环并且会影响性​​能。

您可以限制中间集合的创建,方法是使用字典存储指向与给定日期关联的条目的索引的指针。带有 reduce 函数的示例(没有任何 for 循环就可以离开)。

var original = [
    { country : 'us', date : '2014-10-29', cost : 45.3 },
    { country : 'africa', date : '2014-10-29', cost : 60.5 },
    { country : 'south_america', date : '2014-10-30', cost : 10 },
    { country : 'us', date : '2014-10-30', cost : 30 }
];


var newData = original.reduce(function(acc, d) {
  var date = d.date;
  var idx = acc.dict.indexOf(date);
  var entry;
  if (idx === -1) {
    entry = { date: date };
    acc.dict.push(date);
    acc.list.push(entry);
  } else {
    entry = acc.list[idx];
  }
  entry[d.country] = d.cost;
  return acc
}, {
  list: [],
  dict: []
}).list

document.querySelector('#result').innerHTML = JSON.stringify(newData)
<div id='result'></div>

您可以提高代码的可重用性,但这会产生轻微的性能成本。

var original = [
    { country : 'us', date : '2014-10-29', cost : 45.3 },
    { country : 'africa', date : '2014-10-29', cost : 60.5 },
    { country : 'south_america', date : '2014-10-30', cost : 10 },
    { country : 'us', date : '2014-10-30', cost : 30 }
];

var groupByDateAndAggregateCostByCountry = {
getGroup : function(d) { return {k: 'date', v: d.date}; },
assignData: function(d) { return  {[d.country]:  d.cost}; }
}

var newData = original.reduce(function(acc, d) { 
var gp  = acc.fn.getGroup(d);
var idx = acc.dict.indexOf(gp.v); 
var entry;
if(idx === -1) { 
 entry = {[gp.k] : gp.v};
 acc.dict.push(gp.v); 
 acc.list.push(entry);
} else {
 entry = acc.list[idx];
}
Object.assign(entry, acc.fn.assignData(d));
return acc;  
}, {list: [], dict: [], fn: groupByDateAndAggregateCostByCountry}).list;

newData.map(function(d) {console.log(d)});

document.querySelector("#result2").innerHTML = JSON.stringify(newData);
<div id="result2"></div>