在 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>
我有一大组看起来与此类似的数据:
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>