Rob Fyffe
Rob Fyffe

Reputation: 729

How can I aggregate/group an Array of Objects by property value?

I have a small problem that I am having some difficulty in identifying the best method of transforming the array. I have an array of objects like so:

[{
    genre: 'fiction',
    books: ['book1', 'book2'],
},
{
    genre: 'fiction',
    books: ['book6', 'book51'],
},
{
    genre: 'non-fiction',
    books: ['book23', 'book34'],
},
{
    genre: 'fantasy',
    books: ['book241', 'book49'],
},
{
    genre: 'thriller',
    books: ['book67', 'book32'],
},
{
    genre: 'fantasy',
    books: ['book21', 'book99'],
}];

Using vanilla JS how best can I return a new array with items grouped by the "genre' property so I get the following result:

[{
    genre: 'fiction',
    books: ['book1', 'book2', 'book6', 'book51'],
},
{
    genre: 'non-fiction',
    books: ['book23', 'book34'],
},
{
    genre: 'fantasy',
    books: ['book241', 'book49', 'book21', 'book99'],
},
{
    genre: 'thriller',
    books: ['book67', 'book32'],
}];

I have been playing around with this in various for loops but I can't quite figure out a good solution.

Upvotes: 2

Views: 1551

Answers (6)

גלעד ברקן
גלעד ברקן

Reputation: 23955

If we wanted to keep our changes to the array space itself, we could sort and iterate.

function merge(A, prop, group){
  A.sort((a, b) => a[prop] > b[prop] ? 1 : -1)
  let key = A[0][prop]
  let pointer = 1
  for (let i=1; i<A.length; i++){
    if (A[i][prop] != key){
      A[++pointer - 1] = A[i]
      key = A[i][prop]
    } else {
      A[pointer - 1][group].push(...A[i][group])
    }
  }
  A.length = pointer
  return A
}

var A = [{genre: 'fiction', books: ['book1', 'book2']}, {genre: 'fiction', books: ['book6', 'book51']}, {genre: 'non-fiction', books: ['book23', 'book34']}, {genre: 'fantasy', books: ['book241', 'book49']}, {genre: 'thriller', books: ['book67', 'book32']}, {genre: 'fantasy', books: ['book21', 'book99']}]

merge(A, 'genre', 'books')
console.log(JSON.stringify(A))

Upvotes: 0

StackSlave
StackSlave

Reputation: 10627

If you want to group them, the following code should work:

var json = [{
    genre: 'fiction',
    books: ['book1', 'book2'],
},
{
    genre: 'fiction',
    books: ['book6', 'book51'],
},
{
    genre: 'non-fiction',
    books: ['book23', 'book34'],
},
{
    genre: 'fantasy',
    books: ['book241', 'book49'],
},
{
    genre: 'thriller',
    books: ['book67', 'book32'],
},
{
    genre: 'fantasy',
    books: ['book21', 'book99'],
}];
function groupGenres(arrayOfObjects){
  var genres = [], g;
  arrayOfObjects.forEach(function(j){
    g = false;
    genres.forEach(function(o, i){
      if(o.genre === j.genre)g = i;
    });
    if(g === false){
      genres.push(j);
    }
    else{
      genres[g].books = genres[g].books.concat(j.books);
    }
  });
  return genres;
}
var group = groupGenres(json);
console.log(group);

Upvotes: 1

Tom O.
Tom O.

Reputation: 5941

You can take advantage of Array.prototype.reduce to create a map where genre is the key and an array of books as the value. Then you can loop through the entries and map the results to a new array with object elements:

const arr = [{
    genre: 'fiction',
    books: ['book1', 'book2'],
  },
  {
    genre: 'fiction',
    books: ['book6', 'book51'],
  },
  {
    genre: 'non-fiction',
    books: ['book23', 'book34'],
  },
  {
    genre: 'fantasy',
    books: ['book241', 'book49'],
  },
  {
    genre: 'thriller',
    books: ['book67', 'book32'],
  },
  {
    genre: 'fantasy',
    books: ['book21', 'book99'],
  }
];

const agg = Object.entries(arr.reduce((accum, el) => {
  accum[el.genre] = accum[el.genre] ? [...accum[el.genre], ...el.books] : [...el.books]
  return accum;
}, {})).map(entry => {
  const [genre, books] = entry;
  return {
    [genre]: books
  };
})

console.log(agg);

Upvotes: 1

wakakak
wakakak

Reputation: 842

var originalArray = [{
    genre: 'fiction',
    books: ['book1', 'book2'],
},
{
    genre: 'fiction',
    books: ['book6', 'book51'],
},
{
    genre: 'non-fiction',
    books: ['book23', 'book34'],
},
{
    genre: 'fantasy',
    books: ['book241', 'book49'],
},
{
    genre: 'thriller',
    books: ['book67', 'book32'],
},
{
    genre: 'fantasy',
    books: ['book21', 'book99'],
}];

var newArray = [];

for(var i = 0; i< originalArray.length; i++){
  var idx = newArray.findIndex(x => x.genre === originalArray[i].genre);
  if(idx < 0){
    newArray.push(originalArray[i]);
  } else {
    var newBooks = newArray[idx].books.concat(originalArray[i].books);
    newArray[idx].books = newBooks;
  }
}

console.log(newArray);

Upvotes: 1

bryan60
bryan60

Reputation: 29325

This is not terribly generic but it would work:

var originalList = [{
    genre: 'fiction',
    books: ['book1', 'book2'],
},
{
    genre: 'fiction',
    books: ['book6', 'book51'],
},
{
    genre: 'non-fiction',
    books: ['book23', 'book34'],
},
{
    genre: 'fantasy',
    books: ['book241', 'book49'],
},
{
    genre: 'thriller',
    books: ['book67', 'book32'],
},
{
    genre: 'fantasy',
    books: ['book21', 'book99'],
}];

var newList = originalList.reduce((acc, val) => {
  let item = acc.find(i => i.genre === val.genre); // check if the genre is in the list
  if (item) {
    item.books = item.books.concat(val.books); // if so add the books and move on
    return acc;
  }
  val.books = val.books.slice(); // avoid mutation
  return acc.concat([Object.assign({}, val)]); // otherwise add the current item to the list and avoid mutation
},[]);

console.log(newList);

Upvotes: 1

Jaromanda X
Jaromanda X

Reputation: 1

One line of code answer:

const input = [{
    genre: 'fiction',
    books: ['book1', 'book2'],
  },
  {
    genre: 'fiction',
    books: ['book6', 'book51'],
  },
  {
    genre: 'non-fiction',
    books: ['book23', 'book34'],
  },
  {
    genre: 'fantasy',
    books: ['book241', 'book49'],
  },
  {
    genre: 'thriller',
    books: ['book67', 'book32'],
  },
  {
    genre: 'fantasy',
    books: ['book21', 'book99'],
  }
]

// the code
const output = Object.entries(input.reduce((a, {genre, books}) => (a[genre] = (a[genre] || []).concat(books), a), {})).map(([genre, books]) => ({genre, books}));
// end of code
console.log(output);

Upvotes: 2

Related Questions