Моделирование рецептов и доступных ингредиентов с логикой ограничений
Представьте, что у меня есть несколько рецептов для разных блюд и кладовая с различными ингредиентами на моей кухне. Я хочу построить модель, используя core.logic
что даст мне возможность ответить на следующий вопрос: какие рецепты я могу приготовить для данного набора ингредиентов (т.е. тех, которые в моей кладовой сейчас)?
Рецепты несколько гибки, и мне нужно уметь это моделировать. Позже я хотел бы добавить к ним количества, но давайте пока оставим это для начала.
Я вижу, как смоделировать кладовую:
(db-rel in-larder x)
(def larder (db
[in-larder :carrots]
[in-larder :rice]
[in-larder :garlic]))
Рецепты имеют название и список ингредиентов, которые могут быть необязательными или комбинироваться различными способами. Есть n рецептов. В качестве примера рецепты могут выглядеть (неофициально) так:
Risotto A
=========
(carrots OR peas)
rice
(onions OR garlic)
Risotto B
=========
((carrots AND onions)) OR (rice AND peas))
garlic
Я борюсь с тем, как выразить это в core.logic
, (Обратите внимание, что приведенный выше текст носит исключительно иллюстративный характер и не предназначен для машиночитания.)
Я полагаю, что запрос будет выглядеть примерно так:
(with-dbs [larder recipes] (run* [q] (possible-recipe q)))
который вернул бы следующий результат (учитывая более широкое определение выше):
(:risotto-a :risotto-b)
Мой вопрос заключается в следующем: как я могу смоделировать эти рецепты, чтобы я мог написать запрос по рецептам и кладовке, чтобы перечислить имена возможных рецептов с учетом текущего содержимого моей кладовки?
1 ответ
Вот один из способов моделирования этой проблемы:
(db-rel in-larder i)
(db-rel recipe r)
(db-rel in-recipe r i)
(db-rel compound-ingredient i is)
(def recipes (db
[compound-ingredient :carrots-or-peas [:or :carrots :peas]]
[compound-ingredient :onions-or-garlic [:or :onions :garlic]]
[compound-ingredient :carrots-and-onions [:and :carrots :onions]]
[compound-ingredient :rice-and-peas [:and :rice :peas]]
[compound-ingredient :carrots-onions-or-rice-peas [:or :carrots-and-onions :rice-and-peas]]
[recipe :risotto-a]
[recipe :risotto-b]
[in-recipe :risotto-a [:carrots-or-peas :rice :onions-or-garlic]]
[in-recipe :risotto-b [:garlic :carrots-onions-or-rice-peas]]))
(defn possible-recipe [r]
(recipe r)
(fresh [ingredients]
(in-recipe r ingredients)
(all-ingredients-in-lardero ingredients)))
Есть рецепты и список ингредиентов для каждого рецепта. Каждый ингредиент может быть отдельным или составным, и в этом случае он может иметь необязательные или обязательные ингредиенты.
Нам нужно еще несколько отношений, чтобы это заработало:
(defne any-ingredient-in-lardero [ingredients]
([[?i . ?morei]] (conda [(ingredient-in-lardero ?i)]
[(emptyo ?morei) fail]
[(any-ingredient-in-lardero ?morei)])))
(defne all-ingredients-in-lardero [ingredients]
([[?i . ?morei]]
(ingredient-in-lardero ?i)
(conda [(emptyo ?morei)]
[(all-ingredients-in-lardero ?morei)])))
(defn ingredient-in-lardero [i]
(conde
[(fresh [composition op sub-ingredients]
(compound-ingredient i composition)
(conso op sub-ingredients composition)
(conde
[(== :or op) (any-ingredient-in-lardero sub-ingredients)]
[(== :and op) (all-ingredients-in-lardero sub-ingredients)]))]
[(in-larder i)]))
Теперь мы можем запросить у разных кладовок рецепты:
(def larder-1 (db [in-larder :carrots] [in-larder :rice] [in-larder :garlic]))
(def larder-2 (db [in-larder :peas] [in-larder :rice] [in-larder :garlic]))
(with-dbs [recipes larder-1]
(run* [q]
(possible-recipe q)))
;=> (:risotto-a)
(with-dbs [recipes larder-2]
(run* [q]
(possible-recipe q)))
;=> (:risotto-a :risotto-b)
Полный код в этой сути