简体   繁体   English

对JS对象数组中的项目一次X个项目进行分组,并将每个组的值相加

[英]Group items in JS array of objects X items at a time, and sum up the values of each group

Assume the following array of objects, each comprising a label property (a string representing a date) and a spend property (that holds a number):假设以下对象数组,每个对象都包含一个label属性(表示日期的字符串)和一个spend属性(包含一个数字):

myMonthlySpend = [
  { label: "2021-02-03", spend: 4.95 },
  { label: "2021-02-04", spend: 15.96 },
  { label: "2021-02-05", spend: 11 },
  { label: "2021-02-06", spend: 10.07 },
  { label: "2021-02-07", spend: 6.83 },
  { label: "2021-02-08", spend: 4.85 }
];

Now, the number of items is not fixed and can range from 1 to infinity.现在,项目的数量不是固定的,可以从 1 到无穷大。 Regardless of the array size, say I want to cluster every X items into an object that holds, again:不管数组大小如何,假设我想将每个X项聚集到一个 object 中,再次:

  1. label property comprised of the original label of the first and the last group items label属性由第一组和最后一组项目的原始label组成
  2. spent property representing the total of all of the group's items spend properties代表该组所有项目spent属性的总和的spend属性

and to have these objects returned in an array, like so:并将这些对象返回到数组中,如下所示:

myGroupedSpend = [
  ....
  {label: 'date from - date to', spend: 'total spend for these items'},
  {label: 'date from - date to', spend: 'total spend for these items'}
  ...
]

I tried doing the following, but the output is obviously wrong due to:我尝试执行以下操作,但 output 显然是错误的,因为:

  1. The summed amounts of spend vary and do not match the actual total of all items总支出金额各不相同,与所有项目的实际总金额不符
  2. The resulted labels (meaning, 'date from - date to'), are not consistent结果标签(意思是“日期从 - 日期到”)不一致

 myMonthlySpend = [ { label: "2021-02-03", spend: 4.95 }, { label: "2021-02-04", spend: 15.96 }, { label: "2021-02-05", spend: 11 }, { label: "2021-02-06", spend: 10.07 }, { label: "2021-02-07", spend: 6.83 }, { label: "2021-02-08", spend: 4.85 }, { label: "2021-02-09", spend: 5.01 }, { label: "2021-02-10", spend: 5.09 }, { label: "2021-02-11", spend: 9.1 }, { label: "2021-02-12", spend: 10.18 }, { label: "2021-02-13", spend: 10.17 }, { label: "2021-02-14", spend: 10.16 }, { label: "2021-02-15", spend: 10.07 }, { label: "2021-02-16", spend: 9.94 }, { label: "2021-02-17", spend: 9.76 }, { label: "2021-02-18", spend: 10.09 }, { label: "2021-02-19", spend: 10.05 }, { label: "2021-02-20", spend: 9.93 }, { label: "2021-02-21", spend: 9.8 }, { label: "2021-02-22", spend: 10.26 }, { label: "2021-02-23", spend: 10.03 }, { label: "2021-02-24", spend: 10.09 }, { label: "2021-02-25", spend: 10.09 }, { label: "2021-02-26", spend: 9.95 }, { label: "2021-02-27", spend: 9.78 }, { label: "2021-02-28", spend: 9.77 }, { label: "2021-03-01", spend: 10.11 }, { label: "2021-03-02", spend: 10.04 }, { label: "2021-03-03", spend: 10.01 }, { label: "2021-03-04", spend: 5.06 }, { label: "2021-03-05", spend: 4.72 }, { label: "2021-03-06", spend: 5.36 }, { label: "2021-03-07", spend: 4.98 }, { label: "2021-03-08", spend: 1.51 } ]; // What is the actual total of all items.spend let totalSpend = 0; myMonthlySpend.forEach(v => totalSpend += v.spend); // Grouping function function groupItems(rawData, groupEvery) { let allGroups = []; let currentSpend = 0; for (let i = 0, j = 0; i < rawData.length; i++) { currentSpend += rawData[i].spend; if (i >= groupEvery && i % groupEvery === 0) { let currentLabel = rawData[i - groupEvery].label + ' - ' + rawData[i].label; j++; allGroups[j] = allGroups[j] || {}; allGroups[j] = {'label': currentLabel, 'spend': currentSpend}; currentSpend = 0; } else if (i < groupEvery && i % groupEvery === 0) { let currentLabel = rawData[0].label + ' - ' + rawData[groupEvery].label; allGroups[j] = {'label': currentLabel, 'spend': currentSpend}; } } let checkTotal = 0; allGroups.forEach(v => checkTotal += v.spend); console.log('Total spend when grouped by', groupEvery, 'is', checkTotal, '\nwhile the actual total should be', totalSpend); return allGroups; } // Trying with grouping by 5 and by 9 console.log(groupItems(myMonthlySpend, 5)) console.log('\n\n'); console.log(groupItems(myMonthlySpend, 9))
 .as-console-wrapper { max-height: 100%;important: top; 0; }

For example, should the function be invoked with 10 as the groupEvery number, the output would be:例如,如果 function 以 10 作为 groupEvery 数字调用,则 output 将是:

correctOutput = [
  { label: "2021-02-03 - 2021-02-12", spend: 83.03 },
  { label: "2021-02-13 - 2021-02-22", spend: 100.22 },
  { label: "2021-02-23 - 2021-03-04", spend: 94.92 },
  { label: "2021-03-05 - 2021-03-08", spend: 16.57 },
]
// Actual total is 294.77
// Output total is 294.74

Would appreciate a suggestion on the proper way to achieve this.希望能就实现这一目标的正确方法提出建议。 Tnx.肿瘤坏死因子。

One approach is as follows:一种方法如下:

 // your own original data, assigned as a variable using 'const' since I don't // expect it to change during the course of the script: const myMonthlySpend = [{ label: "2021-02-03", spend: 4.95 }, { label: "2021-02-04", spend: 15.96 }, { label: "2021-02-05", spend: 11 }, { label: "2021-02-06", spend: 10.07 }, { label: "2021-02-07", spend: 6.83 }, { label: "2021-02-08", spend: 4.85 }, { label: "2021-02-09", spend: 5.01 }, { label: "2021-02-10", spend: 5.09 }, { label: "2021-02-11", spend: 9.1 }, { label: "2021-02-12", spend: 10.18 }, { label: "2021-02-13", spend: 10.17 }, { label: "2021-02-14", spend: 10.16 }, { label: "2021-02-15", spend: 10.07 }, { label: "2021-02-16", spend: 9.94 }, { label: "2021-02-17", spend: 9.76 }, { label: "2021-02-18", spend: 10.09 }, { label: "2021-02-19", spend: 10.05 }, { label: "2021-02-20", spend: 9.93 }, { label: "2021-02-21", spend: 9.8 }, { label: "2021-02-22", spend: 10.26 }, { label: "2021-02-23", spend: 10.03 }, { label: "2021-02-24", spend: 10.09 }, { label: "2021-02-25", spend: 10.09 }, { label: "2021-02-26", spend: 9.95 }, { label: "2021-02-27", spend: 9.78 }, { label: "2021-02-28", spend: 9.77 }, { label: "2021-03-01", spend: 10.11 }, { label: "2021-03-02", spend: 10.04 }, { label: "2021-03-03", spend: 10.01 }, { label: "2021-03-04", spend: 5.06 }, { label: "2021-03-05", spend: 4.72 }, { label: "2021-03-06", spend: 5.36 }, { label: "2021-03-07", spend: 4.98 }, { label: "2021-03-08", spend: 1.51 } ], // a named function which takes two arguments: // 1. expenses, an Array of Objects representing your expenditures, and // 2. nSize, an Integer to define the size of the 'groups' you wish to // sum. // This function is defined using Arrow syntax since we have no specific // need to use 'this' within the function: expenseGroups = (expenses, nSize = 7) => { // we use an Array literal with spread syntax to make a copy of // the Array of Objects, in order to avoid operating upon the // original Array: let haystack = [...expenses], // initialising an Array: chunks = []; // here, while they haystack has a non-zero length: while (haystack.length) { // we use Array.prototype.splice() to both remove the identified // Array-elements from the Array (each time reducing the length // of the Array), which returns the removed-elements to the calling- // context; it's worth explaining that we take a 'slice' from the // haystack Array, from index 0 (the first Array-element) up until // but not including the index of nSize). Array.prototype.splice() // modifies the original Array, which is why we had to use a copy // and not the original itself. Once we have the 'slice' of the // Array, that slice is then pushed into the 'chunks' array using // Array.prototype.push(): chunks.push(haystack.splice(0, nSize)); } // here use - and return the results of - Array.prototype.map(), // which returns a new Array based on what we do with each // Array-element as we iterate over that Array: return chunks.map( // 'chunk' is the first of three variabls available to // Array.prototype.map(), and represents the current // Array-element of the Array over which we're iterating: (chunk) => { // here we return an Object: return { // the property 'label' holds a value that is formed using a // template-literal (delimited with back-ticks) in which // JavaScript functions/expressions can be interpolated so // long as they're within a sequence beginning with '${' and // ending with '}'; here we take the read the label property-value // from the zeroth (first) element in the chunk Array, and then // we read the 'label' property from the last Array-element of // the chunk Array: 'label': `${chunk[0].label} - ${chunk[chunk.length - 1].label}`, // because we're representing a currency, I chose to use // Intl.NumberFormat(), which should theoretically style // the currency according to the locale in which it's used // (as determined by the browser or OS): 'spend': new Intl.NumberFormat({ // using a currency style: style: 'currency' // applying the formatting to the number that results from: }).format( // here we use Array.prototype.reduce(), which iterates over // an Array, and performs some function upon that Array; // we use two of the arguments available to that function: // 1. 'acc' which represents the 'accumulator' (or the current // value that the function has produced, // 2. 'curr' which represents the current array-element of the // Array over which we're iterating, and upon which we're // we're working: chunk.reduce((acc, curr) => { // here we take the accumulator, and add to it the value held // in the current Array-element's 'spend' property-value: return acc + curr.spend // we initialise the default/starting value of the accumulator // to 0: }, 0) ) } }); }; console.log(expenseGroups(myMonthlySpend, 10)); /* { label: 2021-02-03 - 2021-02-12, spend: 83.04 }, { label: 2021-02-13 - 2021-02-22, spend: 100.23 }, { label: 2021-02-23 - 2021-03-04, spend: 94.93 }, { label: 2021-03-05 - 2021-03-08, spend: 16.57 } */

JS Fiddle demo . JS 小提琴演示

References:参考:

Here is an implementation that does not rely on array data being ascendingly sorted one day after another.这是一个不依赖于数组数据逐日升序排序的实现。

function groupItems(rawData, groupEvery) {
  // Get date boundaries
  let firstDate = new Date(rawData[0].label);
  let earliestTimestamp = rawData.reduce((a, b) => Math.min(a, new Date(b.label)), firstDate);
  let earliestDate = new Date(earliestTimestamp);
  let latestTimestamp = rawData.reduce((a, b) => Math.max(a, new Date(b.label)), firstDate);
  let latestDate = new Date(latestTimestamp);

  // Create an array of objects which contain start and end dates that represent the 
  // values by which we find the corresponding array index.
  let groupItemsArray = [];
  for (let currentStartDate = new Date(earliestDate); 
       currentStartDate <= latestDate; 
       currentStartDate.setDate(currentStartDate.getDate() + groupEvery)) {
    let currentEndDate = new Date(currentStartDate);
    currentEndDate.setDate(currentEndDate.getDate() + groupEvery - 1);
    groupItemsArray.push({
      startDate: new Date(currentStartDate),
      endDate: currentEndDate > latestDate ? new Date(latestDate) : currentEndDate,
      spend: 0
    });
  }

  // For each date append value of spend to an existing item in groupItemsArray 
  // which obeys internal date range for the given date
  for (let {label, spend} of rawData) {
    let labelDate = new Date(label);
    let index = groupItemsArray.findIndex(groupItem => groupItem.startDate <= labelDate 
      && labelDate <= groupItem.endDate);
    groupItemsArray[index].spend += spend;
  }

  // Map results to comply desired output format
  return groupItemsArray.map(item => {
    let startDateFormatted = item.startDate.toISOString().split('T')[0];
    let endDateFormatted = item.endDate.toISOString().split('T')[0];
    return {
      label: startDateFormatted + " - " + endDateFormatted,
      spend: Number(item.spend.toFixed(2))
    }
  });
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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