簡體   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