Bill
Bill

Reputation: 5150

Javascript Collating Data

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

Answers (3)

pilchard
pilchard

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

user4945014
user4945014

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

AdityaParab
AdityaParab

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' }
    ]
  }
]

*/

Working REPL

Upvotes: 0

Related Questions