Моделирование рецептов и доступных ингредиентов с логикой ограничений

Представьте, что у меня есть несколько рецептов различных блюд и кладовая с различными ингредиентами на кухне. Я хочу построить модель, используя 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)

Мой вопрос заключается в следующем: как я могу смоделировать эти рецепты, чтобы я мог написать запрос по рецептам и кладовой, чтобы перечислить имена возможных рецептов с учетом текущего содержимого моей кладовой?


person Richard Padley    schedule 02.02.2015    source источник


Ответы (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)

Полный код приведен в этой сути.

person nberger    schedule 08.07.2015
comment
Отличный ответ! Я думаю, что застрял, пытаясь продумать, как сделать логические выражения в рецептах произвольно сложными. Ответ, который вы дали, аккуратно решает это чистым выразительным способом. - person Richard Padley; 17.07.2015