Search code examples
javascriptdata-structuresmergegroupingreduce

Reducing object array into smaller array


I'm trying to reduce the an array of Expenses:

interface Expense { price: number, date: number, category: { name: string } }

So that I get an array of objects where each date is unique and the properties are added on if a new one is found.

So basically I want to turn:

[{date: 1, price: 300, category: 'Bills'}, {date: 1, price: 20, category: 'Entertainment'}] 

into:

[ {date: 1, Bills: 300, Entertainment: 20 } ]

Here's what I have, except it's creating a new object if the date already exists, instead of adding to the current object:

const newData = months.reduce(
    (acc, curr) => {
      for (let i = 0; i < acc.length; i++) {
        if (curr.date === acc[i].date) {
          return [
            ...acc,
            {
              ...acc[i],
              date: curr.date,
              [curr.category.name]: curr.price,
            },
          ];
        }
      }
      return [
        ...acc,
        {
          date: curr.date,
          [curr.category.name]: curr.price,
        },
      ];
    },
    [{ date: 0 }]
  );

So my output is this instead:

  Array(10) [
    { date: 0 }, { date: 0, Entertainment: 100 }, { date: 1, Entertainment: 30 }, { date: 1, Entertainment: 30, School: 80 },
    { date: 2, Health: 40 }, { date: 3, Food: 200 }, { date: 4, Transportation: 70 }, { date: 5, Bills: 900 }, { date: 6, Food: 5 },
    { date: 6, Food: 5, Health: 50 }
  ]

I'm super close, but I'm not sure how to fix


Solution

  • A possible approach would programmatically create/aggregate an object where same date values would be utilized as grouping keys for data item's which are related by date. The single reduce task which gets used on that behalf either does access and aggregate a new condensed data-structure or it does create and assign and aggregate it. Creating and/or accessing gets achieved via the nullish coalescing assignment operator / ??= whereas the aggregation is taken care of by Object.assign. From the intermediate result of an index of key-value pairs only the values (the new condensed data-structures) are of interest. Thus one has to pass the result of the reduce task to Object.values.

    const sampleData = [{
      date: 1, price: 300, category: 'Bills',
    }, {
      date: 1, price: 20, category: 'Entertainment',
    }, {
      date: 2, price: 200, category: 'Bills',
    }, {
      date: 2, price: 30, category: 'Entertainment',
    }];
    
    console.log(
      Object
        .values(
          sampleData
            .reduce((index, { date, price: value, category: key }) => {
    
              Object.assign(
                // - create and assign or access object.
                (index[date] ??= { date }),
                // - aggregate the `date` specific object.
                { [ key ]: value },
              );
              return index;
    
            }, {})
        )
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }