Home > Software engineering >  How to group array object by properties?
How to group array object by properties?

Time:10-05

How can we group array object by properties. For example below I want to group the sample data by its oldStockBooks.name (see expected result at the bottom)?

Been to these following links, but I can't apply it in my scenario stack-link-1, stack-link-2.

I have tried these code below, but it does not work as intended.

var counter = {};
oldStockBooks.forEach(function(obj) {
    var key = JSON.stringify(obj)
    counter[key] = (counter[key] || 0)   1
});

Sample data:

const oldStockBooks = [
    {
        name: 'english book',
        author: 'cupello',
        version: 1,
        //... more props here
    },
    {
        name: 'biology book',
        author: 'nagazumi',
        version: 4,
    },
    {
        name: 'english book',
        author: 'cupello',
        version: 2,
    },
];

Expected result: display name, author, and total props only. And total props would be the number of duplication by book name.

const output = [
    {
        name: 'english book',
        author: 'cupello',
        total: 2,
    },
    {
        name: 'biology book',
        author: 'nagazumi',
        total: 1,
    },
];

CodePudding user response:

You can use reduce on your oldStockBooks to build a Map object. Since you want to group by name, the keys in the Map object can be the name values from your objects. When building your Map, if you come across a name that is already in your Map, you can grab the total from the object stored at that key, and create a new object with an updated total. Otherwise, if you have not seen the object yet, you can set the total to 0 (done by destructuring with a default: total = 0). Once you have your Map, you can grab the value objects from it and turn that into an array with Array.from():

const oldStockBooks = [{ name: 'english book', author: 'cupello', version: 1, }, { name: 'biology book', author: 'nagazumi', version: 4, }, { name: 'english book', author: 'cupello', version: 2, }, ];

const res = Array.from(oldStockBooks.reduce((acc, obj) => {
  // Grab name, author and total keys from the seen object. If the object hasn't already been seen, use the current object to grab the name and author, and default the total to 0
  const {name, author, total=0} = acc.get(obj.name) || obj;
  return acc.set(obj.name, {name, author, total: total 1}); // update the total
}, new Map).values());

console.log(res);

CodePudding user response:

You can efficiently achieve the result using Map

const oldStockBooks = [
  {
    name: "english book",
    author: "cupello",
    version: 1,
  },
  {
    name: "biology book",
    author: "nagazumi",
    version: 4,
  },
  {
    name: "english book",
    author: "cupello",
    version: 2,
  },
];

const map = new Map();
oldStockBooks.forEach(({ name, author }) =>
  map.has(name)
    ? (map.get(name).total  = 1)
    : map.set(name, { name, author, total: 1 })
);

const result = [...map.values()];
console.log(result);

CodePudding user response:

You're close to the answer:

var response = {}; // here we put the itens mapped by the key (the 'name' field)
oldStockBooks.forEach(function(obj) {
    if( !response[ obj.name ] ) { // if it is the first item of this 'name'
        response[ obj.name ] = {
            name: obj.name,
            author: obj.author,
            total: 1,
        };
    } else { // else, we have one already, so lets only increment the total count
        response[ obj.name ].total  = 1;
    }
});

// if need a list/array
var myBooks = [];
for(var key in response) myBooks.push( response[key] );

CodePudding user response:

Note: this assumes that you don't care if two books have the same title but are by different authors.

const oldStockBooks = [
    {
        name: 'english book',
        author: 'cupello',
        version: 1,
    },
    {
        name: 'biology book',
        author: 'nagazumi',
        version: 4,
    },
    {
        name: 'english book',
        author: 'cupello',
        version: 2,
    },
];

var counter = {}
oldStockBooks.forEach((obj) => {
    var key = obj.name
    if (!counter[key]) {
      counter[key] = {
        ...obj,
        total: 1,
      }
    } else {
      counter[key].total = counter[key].total   1
    }
});

const result = Object.keys(counter).map(key => {
  const {name, author, total} = counter[key]
  return {name, author, total}
})

console.log(result);

Codepen

CodePudding user response:

Here is a simpler solution. It loops through oldStockBooks, pushing objects into counter. If the object is already in counter, it just increments total .

const oldStockBooks = [
    {
        name: 'english book',
        author: 'cupello',
        version: 1,
        //... more props here
    },
    {
        name: 'biology book',
        author: 'nagazumi',
        version: 4,
    },
    {
        name: 'english book',
        author: 'cupello',
        version: 2,
    },
];

var counter = [];
oldStockBooks.forEach(function(obj) {
    var foundIncounter = counter.find(el => el.name === obj.name)
    if(!foundIncounter){
        // first encounter
        // removing 'version' field 
        var versioRemoved = {author: obj.author, name: obj.name, total: 1}
        counter.push(versioRemoved)
    } else {
        // not first encounter
        foundIncounter.total  
    }
});

console.log(counter)

  • Related