Ilanus
Ilanus

Reputation: 6928

Loop over array of objects that contain additional array of objects. and make certain calculations

Let me explain what I'm talking about. I have the following array:

const days = [
{

    date: '2016-12-13T00:00:00.000Z',
    stats: [
      { name: 'A', sold: 34, },
      { name: 'B', sold: 3, },
      { name: 'C', sold: 26, },
    ],
  },
  {
    date: '2016-12-14T00:00:00.000Z',
    stats: [
      { name: 'D', sold: 34, },
      { name: 'E', sold: 3, },
      { name: 'F', sold: 26, },
    ],
  },
    {
    date: '2016-12-14T00:00:00.000Z',
    stats: [
      { name: 'D', sold: 34, },
      { name: 'E', sold: 3, },
      { name: 'F', sold: 26, },
    ],
  },
];

What I'm trying to do is find the percentage of each stat.name so for example. If we combine all sold values we get: 189 now find the % of stat name B

(3 / 189) * 100

Which will give me: 1.58% this represents the % of sold items for the category.

Ideally the result I'm after would look like this:

const result = [
    { name: 'A', sold: 34, percentage: '17,98%' },
    { name: 'B', sold: 3, percentage: '1,58%', },
    { name: 'C', sold: 26, percentage: '13,75%', },
    { name: 'D', sold: 68, percentage: '35,97%', },
    { name: 'E', sold: 6, percentage: '3,17%' },
    { name: 'F', sold 52, percentage: '27,51%' },
];

What I did so far:

const days = [
{
    date: '2016-12-13T00:00:00.000Z',
    stats: [
      { name: 'A', sold: 34, },
      { name: 'B', sold: 3, },
      { name: 'C', sold: 26, },
    ],
  },
  {
    date: '2016-12-14T00:00:00.000Z',
    stats: [
      { name: 'D', sold: 34, },
      { name: 'E', sold: 3, },
      { name: 'F', sold: 26, },
    ],
  },
    {
    date: '2016-12-14T00:00:00.000Z',
    stats: [
      { name: 'D', sold: 34, },
      { name: 'E', sold: 3, },
      { name: 'F', sold: 26, },
    ],
  },
];

let total = 0;
days.map(record => record.stats.map(category => total += category.sold)); // save the total;

const newStats = days.reduce(function (pastDay, currentDay) {
  const nextStats = currentDay.stats.map(function(stat) {
    const oldSold = pastDay.stats.find((old) => old.name === stat.name); // object that match by name.
    let newSold;
    if (oldSold) { // if matched
    	newSold = stat.sold + oldSold.sold // sum
    } else { // don't sum anything
    	newSold = stat.sold
    }
		stat.sold = newSold;
    stat.percentage = `${(newSold / total * 100).toFixed(2)}%`;
    return stat;
  });
  return {
    stats: nextStats,
  };
});

console.log(newStats);

Which outsputs:

{
  "stats": [
    {
      "name": "D",
      "sold": 68,
      "percentage": "35.98%"
    },
    {
      "name": "E",
      "sold": 6,
      "percentage": "3.17%"
    },
    {
      "name": "F",
      "sold": 52,
      "percentage": "27.51%"
    }
  ]
}

A, B, C. are gone..

Is there a better approach to the whole thing? I don't really like mapping and getting the total first, then working with the rest.. Is there a way to do it better? Thanks...

Upvotes: 2

Views: 81

Answers (3)

Mulan
Mulan

Reputation: 135247

I would highly encourage you to add some generic functions to abstract away some of the complexity here. There are many ways to do that, but I'll leave that as an exercise for you. Maybe I'll update the answer later today if I have more time.

Here's an approach using Array.prototype.reduce

const days = [ { date: '2016-12-13T00:00:00.000Z', stats: [ { name: 'A', sold: 34, }, { name: 'B', sold: 3, }, { name: 'C', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [ { name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [ { name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, } ] } ]

const makeSalesReport = days => {
  let {map,sum} = days
    .reduce((acc, {stats}) => [...acc, ...stats], [])
    .reduce(({map, sum}, {name, sold}) => ({
      map: map.set(name, map.has(name) ? map.get(name) + sold : sold),
      sum: sum + sold
    }), {map: new Map(), sum: 0})

  return Array.from(map, ([name, sold]) =>
    ({name, sold, percentage: sold / sum * 100}))
}
      
console.log(makeSalesReport(days))

Here's another approach nicely bundled up in a function using for-of loops

const days = [ { date: '2016-12-13T00:00:00.000Z', stats: [ { name: 'A', sold: 34, }, { name: 'B', sold: 3, }, { name: 'C', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [ { name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [ { name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, } ] } ]

const makeSalesReport = days => {
  let map = new Map(), sum = 0
  for (let {stats} of days) {
    for (let {name, sold} of stats) {
      map.set(name, map.has(name) ? map.get(name) + sold : sold),
      sum += sold
    }
  }
  return Array.from(map, ([name, sold]) =>
    ({name, sold, percentage: sold / sum * 100}))
}

console.log(makeSalesReport(days))

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386654

You could first collect the sold count and then render the array with the percentage.

var days = [{ date: '2016-12-13T00:00:00.000Z', stats: [{ name: 'A', sold: 34, }, { name: 'B', sold: 3, }, { name: 'C', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [{ name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, }, ], }, { date: '2016-12-14T00:00:00.000Z', stats: [{ name: 'D', sold: 34, }, { name: 'E', sold: 3, }, { name: 'F', sold: 26, }, ], }, ],
    temp = Object.create(null),
    result = [],
    total = 0;

days.forEach(function (day) {
    day.stats.forEach(function (stat) {
        total += stat.sold;
        temp[stat.name] = (temp[stat.name] || 0) + stat.sold;
    });
}, Object.create(null));

result = Object.keys(temp).map(function (k) {
    return { name: k, sold: temp[k], percentage: (temp[k] * 100 / total).toFixed(2) + '%' };
});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 3

Ori Drori
Ori Drori

Reputation: 191986

Use Array#map with Object#assign, and array spread to combine all stats to a single array. Use Array#reduce to get the sum. Map the flatlist, and assign the percentage to each stat using a template literal:

const days = [{"date":"2016-12-13T00:00:00.000Z","stats":[{"name":"A","sold":34},{"name":"B","sold":3},{"name":"C","sold":26}]},{"date":"2016-12-14T00:00:00.000Z","stats":[{"name":"D","sold":34},{"name":"E","sold":3},{"name":"F","sold":26}]},{"date":"2016-12-14T00:00:00.000Z","stats":[{"name":"D","sold":34},{"name":"E","sold":3},{"name":"F","sold":26}]}];
     
// flatten the lists
const flatList = [].concat([], ...days.map(({ stats }) => stats )); 
  
// get the sum
const sum = flatList.reduce(( sum, { sold }) => sum + sold, 0);

// assign the percentage to each
const result = flatList.map((stat) => Object.assign({}, stat, { percentage: `${(stat.sold / sum * 100).toFixed(2)}%` })); 
          
console.log(result);

Upvotes: 1

Related Questions