1

I have an array like this:

const state = {
  products: [
    { name: "Potato", amount: "3"},
    { name: "Butter", amount: "1000" },
    { name: "Salt", amount: "2000" },
    //{name: "Egg", amount: "10"},
    { name: "Tomato",  amount: "5"},
    { name: "Sour Milk", amount: "5"}
  ],

  recipes: [
    {
      name: "Mashed potatoes",
      ingredients: [
        { name: "Potato", amount: "5"},
        { name: "Butter", amount: "30"},
        { name: "Salt", amount: "15"}
      ],
      instructions: "Some Text"
    },
    {
      name: "Tomato Omelette",
      ingredients: [
        { name: "Tomato", amount: "1" },
        { name: "Egg", amount: "1" },
        { name: "Salt", amount: "10" },
        { name: "Butter", amount: "40" }
      ],
      instructions: "Some text"
    }
  ]
};

I want to filter my recipes array by recipes that I can cook with my products (in this case I can't cook "Tomato omelette" because I don't have any eggs and I can't cook "Mashed Potatoes" because I don't have enough potatoes).

So far I tried different approaches but I didn't come up with a whole solution.

My closest solution was this:

const filterRecipes = (filter, products, recipes) => {

  if(filter === 'available products') {
      //Getting all product names for future tests
      const productsNames = products.map(item => item.name);

      //Here we will filter all our recipes by available products 
      const result = recipes.filter(recipe => {
          //Getting all ingredient names of the current recipe
          const ingredientNames = recipe.ingredients.map(item => item.name);

          //If we have all products for our recipe
          //we will add it to our filtered array
          if (ingredientNames.every((name) => productsNames.includes(name))){
              return true;
          }
      })

      console.log(result);
  }
};

This one works only for the names of the products but not for its amount. When I'm trying to check for the amount it just brokes.

Here is the whole code:

const state = {
  products: [
    { name: "Potato", amount: "5"},
    { name: "Butter", amount: "1000" },
    { name: "Salt", amount: "2000" },
    //{name: "Egg", amount: "10"},
    { name: "Tomato",  amount: "5"},
    { name: "Sour Milk", amount: "5"}
  ],
  recipes: [
    {
      name: "Mashed potatoes",
      ingredients: [
        { name: "Potato", amount: "5"},
        { name: "Butter", amount: "30"},
        { name: "Salt", amount: "15"}
      ],
      instructions: "Some Text"
    },
    {
      name: "Tomato Omelette",
      ingredients: [
        { name: "Tomato", amount: "1" },
        { name: "Egg", amount: "1" },
        { name: "Salt", amount: "10" },
        { name: "Butter", amount: "40" }
      ],
      instructions: "Some text"
    }
  ]
};

const filterRecipes = (filter, products, recipes) => {

  if(filter === 'available products') {
      //Getting all product names for future tests
      const productsNames = products.map(item => item.name);

      //Here we will filter all our recipes by available products 
      const result = recipes.filter(recipe => {
          //Getting all ingredient names of the current recipe
          const ingredientNames = recipe.ingredients.map(item => item.name);
        
          //If we have all products for our recipe
          //we will add it to our filtered array
          if (ingredientNames.every((name) => productsNames.includes(name))){
              return true;
          }
      })

      console.log(result);
  }
};

filterRecipes("available products", state.products, state.recipes);

1
  • What if, say you have 20 units of salt and after cooking "Mashed potatoes" there left only 5 units of salt. But for "Tomato Omelette" you need 10 units of salt. What would be the output of this situation? Commented Apr 21, 2020 at 10:46

3 Answers 3

2

We can do it like this:

  • Set up a productsObj with reduce to allow fast lookups
  • filter the recipes array
  • inside each callback of the recipes filter function, and loop over ingredients of each recipe
  • for each ingredient, check it exists in productsObj and the amount is larger than or equal to the item in the recipe ingredients.
  • if it is present and with large enough qty keep checking the rest of the ingredients
  • if not, return false - i.e. filter out of array.

const state = {
  products: [
    { name: "Potato", amount: "5" },
    { name: "Butter", amount: "1000" },
    { name: "Salt", amount: "2000" },
    { name: "Egg", amount: "10" },
    { name: "Tomato", amount: "5" },
    { name: "Sour Milk", amount: "5" }
  ],
  recipes: [
    {
      name: "Mashed potatoes",
      ingredients: [
        { name: "Potato", amount: "5" },
        { name: "Butter", amount: "30" },
        { name: "Salt", amount: "15" }
      ],
      instructions: "Some Text"
    },
    {
      name: "Tomato Omelette",
      ingredients: [
        { name: "Tomato", amount: "1" },
        { name: "Egg", amount: "1" },
        { name: "Salt", amount: "10" },
        { name: "Butter", amount: "40" }
      ],
      instructions: "Some text"
    }
  ]
};

const filterRecipes = (filter, products, recipes) => {
  if (filter === "available products") {
    //Getting all product names in an object for fast look-up
    const productsObj = products.reduce((aggObj, item) => {
      aggObj[item.name] = item;
      return aggObj;
    }, {});
    //console.log("o", productsObj);

    //Here we will filter all our recipes by available products
    const result = recipes.filter((recipe) => {
      let valid = true; //true until proven false

      //Loop over ingredients of each recipe
      for (let i = 0; i < recipe.ingredients.length; i++) {
        const item = recipe.ingredients[i];
        const lookup = productsObj[item.name] || false;
        const quantityEnough = lookup
          ? parseInt(lookup.amount) >= parseInt(item.amount)
          : false;
        if (!quantityEnough) {
          valid = false;
          break;
        }
      }
      return valid;
    });
    console.log(result);
  }
};

filterRecipes("available products", state.products, state.recipes);
.as-console-wrapper { max-height: 100% !important; top: 0; }

For example if you change your product quantitiess to:

const state = {
  products: [
    { name: "Potato", amount: "4" },
    { name: "Butter", amount: "1000" },
    { name: "Salt", amount: "2" },
    { name: "Egg", amount: "10" },
    { name: "Tomato", amount: "5" },
    { name: "Sour Milk", amount: "5" }
  ],

You get no results as salt and potatoes quantities are not large enough for either recipe.

Sign up to request clarification or add additional context in comments.

Comments

1

You could take an object for faster access, with amount as numbers for better comparability

{
    Potato: 5,
    Butter: 1000,
    Salt: 2000,
    Tomato: 5,
    "Sour Milk": 5
}

and loop only products and recieps once.

This approach uses destructuring assignment, where properties are taken out of objects.

The it uses the value of the object and checks the available amount for all ingredients.

const
    filter = ({ products, recipes }) => {
        const avalilable = products.reduce((r, { name, amount }) => (r[name] = +amount, r), {});
console.log(avalilable )
        return recipes.filter(({ ingredients }) =>
            ingredients.every(({ name, amount }) => avalilable[name] >= +amount));
    },
    state = { products: [{ name: "Potato", amount: "5" }, { name: "Butter", amount: "1000" }, { name: "Salt", amount: "2000" }, { name: "Tomato", amount: "5" }, { name: "Sour Milk", amount: "5" }], recipes: [{ name: "Mashed potatoes", ingredients: [{ name: "Potato", amount: "5" }, { name: "Butter", amount: "30" }, { name: "Salt", amount: "15" }], instructions: "Some Text" }, { name: "Tomato Omelette", ingredients: [{ name: "Tomato", amount: "1" }, { name: "Egg", amount: "1" }, { name: "Salt", amount: "10" }, { name: "Butter", amount: "40" }], instructions: "Some text" }] },
    recipes = filter(state);

console.log(recipes);

Comments

0

You can try this solution.

Here I add a solution to a situation say, you have 20 units of "salt" and the first recipe takes 15 units of them for cooking.

Now assume, the second recipe needs 10 units of salt but you have 5 units left at your store. In this situation, you cannot take the second recipe for cooking.

const state = {
  products: [
    { name: "Potato", amount: "5"},
    { name: "Butter", amount: "1000" },
    { name: "Salt", amount: "20" },
    { name: "Egg", amount: "1"},
    { name: "Tomato",  amount: "5"},
    { name: "Sour Milk", amount: "5"}
  ],
  recipes: [
    {
      name: "Mashed potatoes",
      ingredients: [
        { name: "Potato", amount: "5"},
        { name: "Butter", amount: "30"},
        { name: "Salt", amount: "15"}
      ],
      instructions: "Some Text"
    },
    {
      name: "Tomato Omelette",
      ingredients: [
        { name: "Tomato", amount: "1" },
        { name: "Egg", amount: "1" },
        { name: "Salt", amount: "10" },
        { name: "Butter", amount: "40" }
      ],
      instructions: "Some text"
    }
  ]
};


const filterRecipes = (filter, products, recipes) => {
	if (filter === 'available products') {
		
		/**
		* Restructure the products from array to object.
		* like {Potato: "20", "Salt": "200"}
		*/
		const store = products.reduce((a, {name, amount}) => {
			return {...a, [name]: amount};
		}, {});
		
		
		const canCook = recipes.filter(recipe => {
			/**
		    * Convert ingredient from array to object like products
		    *
		    */
			const ingredients = recipe.ingredients.reduce((a, {name, amount}) => {
				return {...a, [name]: amount};
			}, {});
			
			/**
			* Check if every ingredients are available at the store
			*/
			const allow = Object.entries(ingredients).every(([name, amount]) => {
				return (store[name] !== undefined && (+store[name]) >= (+amount));
			});

			/**
			* This is for reducing the amount of ingredient from the store
			* if the recipe is taken for cooking.
			* You can omit it if you don't need to measure this factor.
			*/
			if (allow) {
				Object.entries(ingredients).forEach(([name, amount]) => {
					store[name] -= amount;
				});
			}
			
			return allow;
		});
		
		console.log(canCook);
	}
}

filterRecipes("available products", state.products, state.recipes);
.as-console-wrapper {min-height: 100% !important; top: 0;}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.