2

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...

1
  • I would just use a good old fashioned loop to calculate all the sums. Then loop over the result to do the percentages. A loop over an array is O(n). Doing it twice is still just O(n). Commented Dec 14, 2016 at 15:02

3 Answers 3

3

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

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

1 Comment

Thank you Nina! That would be the most understandable approach :)
1

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

Comments

1

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))

Comments

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.