繁体   English   中英

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

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

假设以下对象数组,每个对象都包含一个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 }
];

现在,项目的数量不是固定的,可以从 1 到无穷大。 不管数组大小如何,假设我想将每个X项聚集到一个 object 中,再次:

  1. label属性由第一组和最后一组项目的原始label组成
  2. 代表该组所有项目spent属性的总和的spend属性

并将这些对象返回到数组中,如下所示:

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

我尝试执行以下操作,但 output 显然是错误的,因为:

  1. 总支出金额各不相同,与所有项目的实际总金额不符
  2. 结果标签(意思是“日期从 - 日期到”)不一致

 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; }

例如,如果 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

希望能就实现这一目标的正确方法提出建议。 肿瘤坏死因子。

一种方法如下:

 // 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 小提琴演示

参考:

这是一个不依赖于数组数据逐日升序排序的实现。

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