Home > Software engineering >  get total price from array of objects per specific user using .reduce in javascript
get total price from array of objects per specific user using .reduce in javascript

Time:05-01

I have this array of objects

const items = [
  {
    id: '121',
    itemDate: '2022-04-28',
    itemName: 'testname1',
    itemCategory: 'Category A',
    itemPrice: { price: '100', currency: 'GBP' },
    createdBy: {
      username: 'user1',
      name: 'Name 1',
      date: '2022-04-28T22:41:59',
    },
  },
  {
    id: '122',
    itemDate: '2022-04-28',
    itemName: 'testname2',
    itemCategory: 'Category B',
    itemPrice: { price: '100', currency: 'GBP' },
    createdBy: {
      username: 'user2',
      name: 'Name 2',
      date: '2022-04-28T22:42:44',
    },
  },
  {
    id: '122',
    itemDate: '2022-04-28',
    itemName: 'testname3',
    itemCategory: 'Category C',
    itemPrice: { price: '200', currency: 'GBP' },
    createdBy: {
      username: 'user2',
      name: 'Name 2',
      date: '2022-04-28T22:43:16',
    },
  },
]

Code I'm using:

items.reduce(function (c, x) {
  if (!c[x.createdBy.username])
    c[x.createdBy.username] = {
      username: x.createdBy.username,
      total: 0,
    }
  c[x.createdBy.username].total  = Number(x.itemPrice.price)
  return c
}, [])

This part gives me the following output:

items :>>  [
  user1: { username: 'user1', total: 100},
  user2: { username: 'user2', total: 300}
]

So I tried this to get rid of the object names:

let output = []
let totalSum = 0

for (const username in items) {
  let temp = {
    username: items[username].username,
    total: items[username].total,
  }
  totalSum = totalSum   items[username].total
  output.push(temp)
}
output.push({ username: 'allUsers', total: totalSum })

return output

And final output is as I want it now:

output :>>  [
  { username: 'user1', total: 100 },
  { username: 'user2', total: 300 },
  { username: 'allUsers', total: 400}
]

My two questions...

Is there a way to update the .reduce part so that I'd get an object without the name at the beggining, without having to use the for loop?

Is there also a way to implement the part that would sum up all the totals?

Thank you

CodePudding user response:

Code Sample (without comments/description)

const groupAndAdd = arr => (
  Object.values(
    arr.reduce(
      (acc, {createdBy : {username}, itemPrice: {price}}) => {
        acc.allUsers ??= { username: 'allUsers', total: 0};
        acc.allUsers.total  =  price;
        if (username in acc) {
          acc[username].total  =  price;
        } else {
          acc[username] = {username, total:  price};
        }
        return acc;
      },
      {}
    )
  )
);

Presented below is a working demo to achieve the desired objective, with notes/comments to help understand.

Code Snippet

// method to group by user and sum prices
const groupAndAdd = arr => (
  // extract the values from the intermediate result-object
  Object.values(
    arr.reduce(             // generate result as object
      (acc, {createdBy : {username}, itemPrice: {price}}) => {
        // above line uses de-structuring to directly access username, price
        // below uses logical nullish assignment to set-up "allUsers"
        acc.allUsers ??= { username: 'allUsers', total: 0};
        
        // accumulate the "price" to the all-users "total"
        acc.allUsers.total  =  price;
        
        // if "acc" (accumulator) has "username", simply add price to total
        if (username in acc) {
          acc[username].total  =  price;
        } else {
          // create an object for the "username" with initial total as "price"
          acc[username] = {username, total:  price};
        }
        
        // always return the "acc" accumulator for ".reduce()"
        return acc;
      },
      {}                // initially set the "acc" to empty object
    )
  )          // if required, use ".sort()" to move the all-users to last position in array
);

const items = [{
    id: '121',
    itemDate: '2022-04-28',
    itemName: 'testname1',
    itemCategory: 'Category A',
    itemPrice: {
      price: '100',
      currency: 'GBP'
    },
    createdBy: {
      username: 'user1',
      name: 'Name 1',
      date: '2022-04-28T22:41:59',
    },
  },
  {
    id: '122',
    itemDate: '2022-04-28',
    itemName: 'testname2',
    itemCategory: 'Category B',
    itemPrice: {
      price: '100',
      currency: 'GBP'
    },
    createdBy: {
      username: 'user2',
      name: 'Name 2',
      date: '2022-04-28T22:42:44',
    },
  },
  {
    id: '122',
    itemDate: '2022-04-28',
    itemName: 'testname3',
    itemCategory: 'Category C',
    itemPrice: {
      price: '200',
      currency: 'GBP'
    },
    createdBy: {
      username: 'user2',
      name: 'Name 2',
      date: '2022-04-28T22:43:16',
    },
  },
];

console.log('group and add prices per user: ', groupAndAdd(items));
.as-console-wrapper { max-height: 100% !important; top: 0 }

Explanation

Inline comments added to the snippet above.

PS: If you'd like to add value to stackoverflow community,

Please consider reading: What to do when my question is answered Thank you !

CodePudding user response:

For your first question, you're initialising correctly as an array, but you're using just object. Two ways you can do this.

First Option

let something = items.reduce(function(c, x) {
  if (!c[x.createdBy.username])
    c[x.createdBy.username] = {
      username: x.createdBy.username,
      total: 0,
    }
  c[x.createdBy.username].total  = Number(x.itemPrice.price)
  return c
}, {});
something = Object.values(something);

Second Option

I was thinking of using just push, but seems it's not possible, so the above is the only option.

Using push is possible, but it'll get too complicated by checking with find and updating the correct array element.


For your second question of summing up all the totals, you can use the simple syntax of:

const sum = arr.reduce((a, c) => a   c, 0);

This is the minimum code you need for array of numbers to be summed.

  • Related