Фильтрация вложенного массива с объектами путем проверки другого вложенного массива с объектом - JavaScript - PullRequest
1 голос
/ 21 апреля 2020

У меня есть такой массив:

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"
    }
  ]
};

Я хочу отфильтровать массив моих рецептов по рецептам, которые я могу приготовить из моих продуктов (в этом случае я не могу готовить "Томатный омлет «потому что у меня нет яиц, и я не могу готовить« Картофельное пюре », потому что мне не хватает картошки) .

До сих пор я пробовал разные подходы, но я не пришел с целым решением.

Моим самым близким решением было следующее:

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);
  }
};

Это работает только для названий продуктов, но не для их количества. Когда я пытаюсь проверить сумму, которую он только что сломал.

Вот весь код:

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);

Ответы [ 3 ]

2 голосов
/ 21 апреля 2020

Мы можем сделать это следующим образом:

  • Установить productsObj с функцией Reduce для быстрого поиска
  • отфильтровать массив рецептов
  • внутри каждого обратного вызова функции фильтра рецептов и l oop над ингредиентами каждого рецепта
  • для каждого ингредиента, проверьте, что он существует в productsObj и количество больше или равно элементу в ингредиентах рецепта.
  • , если он присутствует и с достаточно большим количеством, продолжайте проверять остальные ингредиенты
  • , если нет, вернуть false - т.е. отфильтровать из массива.

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; }

Например, если вы измените количество продукта на:

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" }
  ],

Вы не получите результатов, так как количество соли и картофеля недостаточно велико для любого рецепта.

1 голос
/ 21 апреля 2020

Вы можете взять объект для более быстрого доступа, с amount в качестве чисел для лучшей сопоставимости

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

и l oop только продукты и повторные платежи только один раз.

Этот подход использует деструктурирующее присвоение , где свойства снимаются с объектов.

Используется значение объекта и проверяется доступное количество для всех ингредиентов.

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);
0 голосов
/ 21 апреля 2020

Вы можете попробовать это решение.

Здесь я добавляю решение к ситуации, скажем, у вас есть 20 единиц "соли" и первое Рецепт берет 15 единиц из них для приготовления.

Теперь предположим, что второму рецепту нужно 10 единиц соли, но в вашем магазине осталось 5 единиц. В этой ситуации вы не можете воспользоваться вторым рецептом приготовления.

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;}
...