Reputation: 729
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
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
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
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
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
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
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