Matt
Matt

Reputation: 115

Consolidate array of objects with similar key values and create new array with less objects

I have an array of objects (over 5000 entries) with Units Sold data from several countries. I am trying to take all similar countries, add the amounts, and then return a new array of objects with only one occurrence of each country and its total amount.

I think the code below is on the right track but I'm sure I'm taking the inefficient route and positive that it's flat out wrong. Im sure I need to apply the reduce() method and probably use something else besides forEach() but I'm not sure how.

Been on this for a few days now and would love/appreciate some help. Thanks!

    const data = [
     {
      "Country" : "USA",
      "Units Sold" : 1245.56
     },
     {
      "Country" : "Spain",
      "Units Sold" : 7843.50
     },
     {
      "Country" : "USA",
      "Units Sold" : 1435.99
     },
     {
      "Country" : "Uruguay",
      "Units Sold" : 594.20
     },
    ]

    let result = [];
    data.forEach((block) => {
      if (result.length === 0) {
        result.push({
          Country: block.Country,
          "Units Sold": block["Units Sold"],
        });
      } else {
        data.forEach((e) => {
          if (e.Country === block.Country) {
            console.log("add values");
            e["Units Sold"] += block["Units Sold"];
            console.log(e);
          }
        });
      }
    });

Desired outcome:

    const newArray = [
     {
      "Country" : "USA",
      "Units Sold" : 2681.55
     },
     {
      "Country" : "Spain",
      "Units Sold" : 7843.50
     },
     {
      "Country" : "Uruguay",
      "Units Sold" : 594.20
     },
    ]

Upvotes: 0

Views: 90

Answers (2)

Lyes
Lyes

Reputation: 477

const data = [
        {
  "Country" : "USA",
  "sold" : 15.56
 },
 {
  "Country" : "USA",
  "sold" : 1245.56
 },
 {
  "Country" : "Spain",
  "sold" : 7843.50
 },
 {
  "Country" : "USA",
  "sold" : 1435.99
 },
 {
  "Country" : "Uruguay",
  "sold" : 594.20
 },
]

const usaEntries= data.filter(item => item.Country === 'USA')
const sum = pays.reduce((a, n) => (a + Number(n.sold)), 0);

The first line retrieves all the usa entries, the second one sum the sold of all the items, it takes two parameters, a for accumulator, and n fot the current value, the 0 is to initialize the accumulator to zero.

You can also do it in one line

 const sum = data.filter(item => item.Country === 'USA').reduce((a, n) => (a + Number(n.sold)), 0);

You can also group all the countries in one array

    let group = data.reduce((a, n) => {

        a[n.Country] = [...a[n.Country] || [], n];
        return a;
    }, {});

Then you can apply the sum function using reduce on every item to get the sold of every country.

Upvotes: 2

Brenden
Brenden

Reputation: 2097

instead of trying to do everything in one operation, sometimes it makes more sense to do things step by step. For example your problem can be broken down into two steps.

  1. Group by country
  2. Sum discrete groups

Now all we have to do is create some building blocks to help you achieve this results.

Group By

The group by operator is simple you are already doing this. But we can make it a bit faster by using a map. This allows us to immediately tell if a country group exists without having to search our array.

const groupBy = (array, selector) => {
  return array.reduce((groups, value) => {
    const key = selector(value);
    const group = groups.get(key);
    
    if (group == null) {
        groups.set(key, [value]);
    } else {
        group.push(value);
    }
    
    return groups;
  }, new Map());
};

const groups = groupBy(data, (d) => d["Country"]);

We are using array.reduce here to reduce the collection into a single value.

Sum

We will next use a summing operator to go through each group and sum up whatever is returned by the selector.

const sum = (array, selector) => {
  return array.reduce((s, value) => {
    return s + selector(value);
  }, 0);
};

This should look pretty similar to groupBy. We are reducing multiple values into a single sum.

Results

Now since we have our two building blocks we just need to click them together to get the results we want.

const groups = groupBy(data, (v) => v["Country"])
const unitsSoldByCountry = Array.from(groups.entries())
  .map(([country, sales]) => {
    return {
      "Country": country,
      "Units Sold": sum(sales, (s) => s["Units Sold"])
    };
  });

We groupBy the country. Then for every group(country) we sum up their units of sales. We use a array.map operator here because we expect that if there are N countries that we return N summed records.

const data = [
  {
    "Country": "USA",
    "Units Sold": 1245.56
  },
  {
    "Country": "Spain",
    "Units Sold": 7843.50
  },
  {
    "Country": "USA",
    "Units Sold": 1435.99
  },
  {
    "Country": "Uruguay",
    "Units Sold": 594.20
  }
];

const groupBy = (array, selector) => {
  return array.reduce((groups, value) => {
    const key = selector(value);
    const group = groups.get(key);
    
    if (group == null) {
        groups.set(key, [value]);
    } else {
        group.push(value);
    }
    
    return groups;
  }, new Map());
};

const sum = (array, selector) => {
  return array.reduce((s, value) => {
    return s + selector(value);
  }, 0);
};

const groups = groupBy(data, (v) => v['Country'])
const unitsSoldByCountry = Array.from(groups.entries())
  .map(([country, sales]) => {
    return {
      "Country": country,
      "Units Sold": sum(sales, (s) => s["Units Sold"])
    };
  });

console.log(unitsSoldByCountry);

There are definitely ways of doing this with fewer lines of code. But I prefer reusable components that allow for more declarative programming making your code easier to read and change.

Upvotes: 2

Related Questions