Reputation: 5150
I have an array of objects taking this shape...
type allRecipes = {
rows: [
{
category: string;
id: number;
owner: string;
recipes_uri: string;
recipes_name: string;
}
];
};
Many recipes have the same recipes_name
and the same category
but with different id
, owner
and recipes_uri
.
I need to collate these into this new shape that will remove some of the duplications and make the data easier to handle.
type recipesCollated = [
{
category: string;
recipes_name: string;
recipes_collection: [
{
id: number;
owner: string;
recipes_uri: string;
}
];
}
];
So I'm trying to loop over allRecipes.rows
then should I use .reduce
I've stubbed out some sudo code in the comments...
const recipesCollated = [];
for (let i = 0; i < allRecipes.rows.length; i++) {
// is allRecipes.rows[i].recipes_name in the recipesCollated array??;
// if its not push a new record in with one item in the recipes_collection array
// if it is, loop over recipesCollated.recipes_collection and check to see if the current id is in the array
// if it is, our job is done, if its not insert it into recipesCollated.recipes_collection array
}
Upvotes: 0
Views: 175
Reputation: 12918
This can be handled with a single Array#reduce()
call using a composite key of the relevant properties to group by.
With a Map
const allRecipes = { rows: [{ category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 23', }], };
const collatedRecipes = [...allRecipes.rows.reduce((acc, { category, recipes_name, ...rest }) => {
const key = `${category}_${recipes_name}`;
if (acc.has(key)) {
acc.get(key).recipes_collection.push(rest);
} else {
acc.set(key, { category, recipes_name, recipes_collection: [rest] })
}
return acc;
}, new Map)
.values()]
console.log(collatedRecipes)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or an object
const allRecipes = { rows: [{ category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 23', }], };
const collatedRecipes = Object
.values(allRecipes.rows
.reduce((acc, { category, recipes_name, ...rest }) => (
(acc[`${category}_${recipes_name}`] ??= { category, recipes_name, recipes_collection: [] })
.recipes_collection.push(rest), acc)
, {})
);
console.log(collatedRecipes)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Or a Map
and a for...of
loop
const allRecipes = { rows: [{ category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 1', }, { category: 'cat 2', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 5', }, { category: 'cat 1', id: 1, owner: 'Bill', recipes_uri: 'uri2', recipes_name: 'recipe 23', }], };
const collatedMap = new Map;
for (const { category, recipes_name, ...rest } of allRecipes.rows) {
const key = `${category}_${recipes_name}`;
if (collatedMap.has(key)) {
collatedMap.get(key).recipes_collection.push(rest);
} else {
collatedMap.set(key, { category, recipes_name, recipes_collection: [rest] });
}
}
const collatedArray = [...collatedMap.values()];
console.log(collatedArray);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Upvotes: 1
Reputation:
Rather than checking for inclusion in an array (which is O(n^2)), it's better to use maps associating category and name to an array of entries. You can always convert it to an array if necessary. For example:
const recipesCollated_ = new Map();
for (const recipe of recipes.rows) {
let category_map = recipesCollated_.get(recipe.category);
if (typeof category_map === 'undefined') {
const new_map = new Map();
recipesCollated_.set(recipe.category, new_map);
category_map = new_map;
}
let recipe_array = category_map.get(recipe.recipes_name);
if (typeof recipe_array === 'undefined') {
const new_arr = [];
category_map.set(recipe.recipes_name, new_arr);
recipe_array = new_arr;
}
recipe_array.push({ recipes_uri: recipe.recipes_uri, owner: recipe.owner, id: recipe.id, });
}
const recipesCollated = [];
for (const [ category, recipes_map, ] of recipesCollated_) {
for (const [ recipe_name, recipes, ] of recipes_map) {
recipesCollated.push({ recipes_name: recipe_name, category: category, recipes_collection: recipes, });
}
}
Upvotes: 2
Reputation: 7100
Simply running a reduce method on your original array and checking if name and category exist. If so, push the current recipe in existing object, else add a new object in the accumulated result.
const recipes = {
rows: [
{
category: 'c1',
id: 1,
owner: 'o1',
recipes_uri: 'some url',
recipes_name: 'rn-1',
},
{
category: 'c1',
id: 2,
owner: 'o2',
recipes_uri: 'some url-2',
recipes_name: 'rn-1'
}
]
};
const result = recipes.rows.reduce((acc, cur) => {
const {recipes_name, category, ...recipe} = cur;
const recipeNameExist = acc.find(rec => rec.recipes_name === recipes_name);
const recipeCatExist = acc.find(rec => rec.category === category);
if(!(recipeCatExist && recipeNameExist)) {
acc.push({
recipes_name,
category,
recipes_collection: [recipe]
})
} else {
recipeCatExist.recipes_collection.push(recipe)
}
return acc;
}, []);
console.dir(result);
/*
[
{
recipes_name: 'rn-1',
category: 'c1',
recipes_collection: [
{ id: 1, owner: 'o1', recipes_uri: 'some url' },
{ id: 2, owner: 'o2', recipes_uri: 'some url-2' }
]
}
]
*/
Upvotes: 0