0

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
     },
    ]
2
  • What do you mean by all similar country ? Commented Nov 20, 2020 at 21:49
  • Thanks for responding. For instance I want to take all objects with "USA" and add the "Units Sold" together. Commented Nov 20, 2020 at 21:51

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks so much, took me a while to grasp what this is doing but it definitely works and gives the exact outcome that I was looking for. I appreciate the step-by-step as well. Thanks again for your time.
No problem. Just take some time to study the code, lots of CDD(Console Driven Development), and just thinking about things as steps. You'll get it down sooner than you think.
2
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.

1 Comment

Thanks, Brenden's answer was a little closer to what I was looking for but I'll definitely look more into approaching the problem in this manner for future reference. Wish I could check both... Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.