[英]Group and aggregate array of objects by key names
我想在JS中编写一个函数,该函数将名称列表作为参数,并且能够按指定的列名称进行分组和汇总。 例如,我的数据可能如下所示:
const SALES = [
{ lead: 'Mgr 1', revenue: 49.99, repName: 'Rep 1', forecast: 81.00 },
{ lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 1', forecast: 91.00 },
{ lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 13', forecast: 82.00 },
{ lead: 'Mgr 2', revenue: 99.99, repName: 'Rep 3', forecast: 101.00 },
{ lead: 'Mgr 2', revenue: 9.99, repName: 'Rep 5', forecast: 89.00 },
{ lead: 'Mgr 3', revenue: 199.99, repName: 'Rep 6', forecast: 77.00 }
];
我可以像这样对数据进行分组和汇总:
let grouped = {};
SALES.forEach(({lead, repName, revenue}) => {
grouped[[lead, repName]] = grouped[[lead, repName]] || { lead, repName, revenue: 0 };
grouped[[lead, repName]].revenue = +grouped[[lead, repName]].revenue + (+revenue);
});
grouped = Object.values(grouped);
console.warn('Look at this:\n', grouped);
但是,我希望它更具动态性,这样我就不必为分组和聚合的所有可能组合编写if-else语句。 以下代码显示了一些我想开始使用的东西,但目前还没有。
function groupByTotal(arr, groupByCols, aggregateCols) {
let grouped = {};
arr.forEach(({ groupByCols, aggregateCols }) => {
grouped[groupByCols] = grouped[groupByCols] || { groupByCols, aggregateCols: 0 };
grouped[groupByCols].aggregateCols = +grouped[groupByCols].aggregateCols + (+aggregateCols);
});
grouped = Object.values(grouped);
return grouped;
}
groupByTotal(SALES,['lead','repName'],'revenue')
预期的输出可能如下所示:
[
{ lead: "Mgr 1", repName: "Rep 1", revenue: 59.98 },
{ lead: "Mgr 1", repName: "Rep 13", revenue: 9.99 },
{ lead: "Mgr 2", repName: "Rep 3", revenue: 99.99 },
{ lead: "Mgr 2", repName: "Rep 5", revenue: 9.99 },
{ lead: "Mgr 3", repName: "Rep 6", revenue: 199.99 }
]
理想情况下,我希望能够传入任意数量的列名以进行分组或聚合。 任何帮助将不胜感激。
当前,您正在基于[lead, repName]
的字符串化值创建密钥。 您可以基于groupByCols
动态groupByCols
// gets the values for "groupByCols" seperated by `|` to create a unique key
const values = groupByCols.map(k => o[k]).join("|");
您还需要基于groupByCols
获取对象的子集
const subSet = (o, keys) => keys.reduce((r, k) => (r[k] = o[k], r), {})
// OR if fromEntries() is supported
const subSet = (o, keys) => Object.fromEntries(keys.map(k => [k, o[k]))
其余逻辑将类似于您已经在做的事情。 在grouped
使用唯一性。 获取对象的子集,并根据键是否已经存在来添加/更新aggregateCols
键
const SALES = [ { lead: 'Mgr 1', revenue: 49.99, repName: 'Rep 1', forecast: 81.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 1', forecast: 91.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 13', forecast: 82.00 }, { lead: 'Mgr 2', revenue: 99.99, repName: 'Rep 3', forecast: 101.00 }, { lead: 'Mgr 2', revenue: 9.99, repName: 'Rep 5', forecast: 89.00 }, { lead: 'Mgr 3', revenue: 199.99, repName: 'Rep 6', forecast: 77.00 } ]; const subSet = (o, keys) => keys.reduce((r, k) => (r[k] = o[k], r), {}) function groupByTotal(arr, groupByCols, aggregateCols) { let grouped = {}; arr.forEach(o => { const values = groupByCols.map(k => o[k]).join("|"); if (grouped[values]) grouped[values][aggregateCols] += o[aggregateCols] else grouped[values] = { ...subSet(o, groupByCols), [aggregateCols]: o[aggregateCols] } }) return Object.values(grouped); } console.log("Sum revenue based on lead and repName") console.log(groupByTotal(SALES, ['lead', 'repName'], 'revenue')) console.log("Sum forecast based on lead: ") console.log(groupByTotal(SALES, ['lead'], 'forecast'))
如果您想传递一个列数组进行求和,则可以遍历aggregateCols
并对grouped
每个属性求和:
if (grouped[values]) {
aggregateCols.forEach(col => grouped[values][col] += o[col])
grouped[values].Count++
} else {
grouped[values] = subSet(o, groupByCols);
grouped[values].Count = 1
aggregateCols.forEach(col => grouped[values][col] = o[col])
}
您可以使用纯JavaScript实现类似的算法。
只知道如何创建密钥并聚合数据,如以下情况所示。
const SALES = [ { lead: 'Mgr 1', revenue: 49.99, repName: 'Rep 1', forecast: 81.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 1', forecast: 91.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 13', forecast: 82.00 }, { lead: 'Mgr 2', revenue: 99.99, repName: 'Rep 3', forecast: 101.00 }, { lead: 'Mgr 2', revenue: 9.99, repName: 'Rep 5', forecast: 89.00 }, { lead: 'Mgr 3', revenue: 199.99, repName: 'Rep 6', forecast: 77.00 } ]; console.log(aggregate(SALES, ['lead', 'repName'], 'revenue')); function aggregate(data, keyFields, accumulator) { var createNewObj = (ref, fields) => { return fields.reduce((result, key) => { return Object.assign(result, { [key] : ref[key] }); }, {}); } return Object.values(data.reduce((result, object, index, ref) => { let key = keyFields.map(key => object[key]).join(''); let val = result[key] || createNewObj(object, keyFields); val[accumulator] = (val[accumulator] || 0) + object[accumulator]; return Object.assign(result, { [key] : val }); }, {})); }
.as-console-wrapper { top: 0; max-height: 100% !important; }
[
{
"lead": "Mgr 1",
"repName": "Rep 1",
"revenue": 59.98
},
{
"lead": "Mgr 1",
"repName": "Rep 13",
"revenue": 9.99
},
{
"lead": "Mgr 2",
"repName": "Rep 3",
"revenue": 99.99
},
{
"lead": "Mgr 2",
"repName": "Rep 5",
"revenue": 9.99
},
{
"lead": "Mgr 3",
"repName": "Rep 6",
"revenue": 199.99
}
]
下面的示例使用一个累加器对象,该对象包含一个参考字段和一个应用数学表达式的函数。
{
key: 'revenue',
fn : (total, value) => total + value
}
const SALES = [ { lead: 'Mgr 1', revenue: 49.99, repName: 'Rep 1', forecast: 81.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 1', forecast: 91.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 13', forecast: 82.00 }, { lead: 'Mgr 2', revenue: 99.99, repName: 'Rep 3', forecast: 101.00 }, { lead: 'Mgr 2', revenue: 9.99, repName: 'Rep 5', forecast: 89.00 }, { lead: 'Mgr 3', revenue: 199.99, repName: 'Rep 6', forecast: 77.00 } ]; console.log(aggregate(SALES, ['lead', 'repName'], { key: 'revenue', fn : (total, value) => total + value })); function aggregate(data, keyFields, accumulator) { var createNewObj = (ref, fields) => { return fields.reduce((result, key) => { return Object.assign(result, { [key] : ref[key] }); }, {}); } return Object.values(data.reduce((result, object, index, ref) => { let key = keyFields.map(key => object[key]).join(''); let val = result[key] || createNewObj(object, keyFields); val[accumulator.key] = accumulator.fn(val[accumulator.key] || 0, object[accumulator.key]); return Object.assign(result, { [key] : val }); }, {})); }
.as-console-wrapper { top: 0; max-height: 100% !important; }
如果要累积多个字段,则需要删除该字段以进行引用,而只是修改整个对象,但这通常更危险。
const SALES = [ { lead: 'Mgr 1', revenue: 49.99, repName: 'Rep 1', forecast: 81.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 1', forecast: 91.00 }, { lead: 'Mgr 1', revenue: 9.99, repName: 'Rep 13', forecast: 82.00 }, { lead: 'Mgr 2', revenue: 99.99, repName: 'Rep 3', forecast: 101.00 }, { lead: 'Mgr 2', revenue: 9.99, repName: 'Rep 5', forecast: 89.00 }, { lead: 'Mgr 3', revenue: 199.99, repName: 'Rep 6', forecast: 77.00 } ]; console.log(aggregate(SALES, ['lead', 'repName'], (prev, curr) => { return Object.assign(prev, { revenueTotal : (prev['revenueTotal'] || 0) + curr['revenue'], forecastMax : Math.max((prev['forecastMax'] || -Number.MAX_VALUE), curr['forecast']), forecastMin : Math.min((prev['forecastMin'] || +Number.MAX_VALUE), curr['forecast']) }); })); function aggregate(data, keyFields, accumulatorFn) { var createNewObj = (ref, fields) => { return fields.reduce((result, key) => { return Object.assign(result, { [key] : ref[key] }); }, {}); } return Object.values(data.reduce((result, object, index, ref) => { let key = keyFields.map(key => object[key]).join(''); let val = result[key] || createNewObj(object, keyFields); return Object.assign(result, { [key] : accumulatorFn(val, object) }); }, {})); }
.as-console-wrapper { top: 0; max-height: 100% !important; }
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.