I can solve this problem using a language I already know, but I was hoping to do it using prolog (and maybe clpfd) because I want to learn those technologies. I see some references to similar problems using SQL, but that's not what I'm interested in.
I have a collection of ingredients, each of which has properties such as calorie count, amount of protein, amount of fat, etc. There will be no more than a couple of dozen ingredients, and probably no more than half a dozen properties. In C I would model this as an array of structs.
I then want to be able to generate recipes that use combinations of ingredients subject to constraints. For example, total calories < 1000 and fat less than 30% and more than 25g protein. I might also want to say "exclude white rice" e.g. because I'm out of white rice at the moment.
An answer might be a list of items like "125g chicken, 25g carrots, 100g steamed rice".
Am I on the right track to do this with prolog/cplfd? Are there particular difficulties that would make it unsuitable for a beginner (although I'm an experienced programmer in other languages)?
How would I model this database? I see prolog has lists and tuples.. would I express it as a list of tuples?
Would I be able to express mathematical constraints like "Total calories < 1000 and calories from fat < 30% of total calories and protein > 25g" ?
Is there a name for this class of problem that I could search for?
Are there existing prolog examples I could use as a guide?
This is going to be a really neat program and I hope you will implement the whole thing and write about it. Please let me know what you come up with!
I can easily see how to handle your example constraint. The code will look something like this:
% these operator declarations are just guesses!
:- op(500, xfy, and).
:- op(600, xf, grams).
:- op(600, yfx, of).
test(Recipe, X < Y) :-
evaluate(Recipe, X, XV),
evaluate(Recipe, Y, YV),
XV < YV.
test(Recipe, X and Y) :-
test(Recipe, X), test(Recipe, Y).
evaluate(Recipe, total_calories, X) :- total_calories(Recipe, X).
evaluate(Recipe, Const, Const) :- number(Const).
total_calories(Recipe, Total) :-
maplist(calories, Recipe, Calories),
sum(Calories, Total).
calories(X grams of Y, Calories) :-
caloric_content(N grams of Y, C),
Calories is X/N * C.
caloric_content(100 grams of chicken, 195).
I think that's a decent sketch of how I'd proceed with testing the recipes. Generating them is going to be very interesting. I think you're going to want to constrain your recipe writing. The best thing to figure out how to constrain it is probably to write it naively and then introduce corrections case-by-case. My best guess would be something like this:
meat(chicken).
vegetable(carrots).
recipe([MeatAmount grams of Meat, VegAmount grams of Vegetable]) :-
meat(Meat),
vegetable(Vegetable),
and then you're going to want to augment that with some way of guessing the amount. Another idea to explore would be to combine the two aspects so that each ingredient you add is handling some constraint. That way you're keeping the search directed rather than aimlessly throwing things together. I hope this gives you some ideas!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With