繁体   English   中英

按键名称对对象数组进行分组和聚合

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM