Reputation: 23
I'm new to Prolog and I'm going through some trouble to understand what's wrong with my code.
I'm trying to make a food recommender. Basically, the user enters whether they want breakfast, lunch or dessert and a list of their tastes. The program then counts the number of matches between their tastes and the characteristics of every food and returns the food with the most matches.
I'm using recursion to go through the list of food and everything works as intended until it reaches the end with the empty list of food and then starts to fill it again, returning to the initial state of the problem. I'm not understanding why is the program doing this and I can't find a solution.
recommend(breakfast, Tastes, Result) :-
findall(Breakfast, breakfast(Breakfast), Breakfasts),
getBestMatch(Breakfasts, Tastes, Result, -1).
recommend(lunch, Tastes, Result) :-
findall(Lunch, lunch(Lunch), Lunches),
getBestMatch(Lunches, Tastes, Result, -1).
recommend(dessert, Tastes, Result) :-
findall(Dessert, dessert(Dessert), Desserts),
getBestMatch(Desserts, Tastes, Result, -1).
% Recursive predicate. Goes through the list of food and calls the matches predicate to count the
% matches and compares it to the current best match. When the food list is empty, it should stop.
% Result should be the name of the best matching food. I think here's where the error is.
getBestMatch([], _, _, _).
getBestMatch([H|T], Tastes, Result, BestMatch) :-
matches(H, Tastes, Matches),
Matches > BestMatch
-> getBestMatch(T, Tastes, H, Matches)
; getBestMatch(T, Tastes, Result, BestMatch).
% Counts the number of matches between the food's characteristics and the tastes.
matches(Food, Tastes, Matches) :-
characteristics(Food, Characteristics),
intersection(Tastes, Characteristics, MatchList),
length(MatchList, Matches).
% These are some examples of how I have declared the food and its characteristics
lunch(lasagna).
lunch(pizza).
dessert(cake).
characteristics(lasagna, [warm, salty, pasta, italian, meat]).
characteristics(pizza, [warm, salty, cheese, italian]).
characteristics(cake, [cold, sweet, vegetarian]).
And here's a fragment of the trace.
Call: (12) getBestMatch([pizza], [italian, cheese], lasagna, 1) ? creep
Call: (13) matches(pizza, [italian, cheese], _4220) ? creep
Call: (14) characteristics(pizza, _4262) ? creep
Exit: (14) characteristics(pizza, [warm, salty, cheese, italian]) ? creep
Call: (14) lists:intersection([italian, cheese], [warm, salty, cheese, italian], _4376) ? creep
Exit: (14) lists:intersection([italian, cheese], [warm, salty, cheese, italian], [italian, cheese]) ? creep
Call: (14) length([italian, cheese], _4474) ? creep
Exit: (14) length([italian, cheese], 2) ? creep
Exit: (13) matches(pizza, [italian, cheese], 2) ? creep
Call: (13) 2>1 ? creep
Exit: (13) 2>1 ? creep
Call: (13) getBestMatch([], [italian, cheese], pizza, 2) ? creep
Exit: (13) getBestMatch([], [italian, cheese], pizza, 2) ? creep
Exit: (12) getBestMatch([pizza], [italian, cheese], lasagna, 1) ? creep
Exit: (11) getBestMatch([lasagna, pizza], [italian, cheese], _2882, -1) ? creep
Exit: (10) recommend(lunch, [italian, cheese], _2882) ? creep
true.
I've tried looking through similar questions but none of them works for me.
PS: Sorry if this question or the code sample is too large, this was my first question here and I couldn't figure out how to simplify it more.
Upvotes: 2
Views: 94
Reputation: 3736
There is something mixed up in your getBestMatch/4
predicate. Let's go through the roles of the atributes:
[H|T]
seems to be a list of dishes to choose from (Input).Tastes
seems to be a list of favored properties (Input).Result
seems to be the favored dish from the List (Output).BestMatch
seems to be the best match value (Input? Better to be an output).You have an if-then-else in this clause which reads as pseudocode:
if matches(H, Tastes, Matches) and if Matches > BestMatch
then getBestMatch(T, Tastes, H, Matches)
else getBestMatch(T, Tastes, Result, BestMatch)
There are some issues here, like connecting values of attributes to each other. Let's try to resolve it. At first when to stop: if your List is empty, return a -1
as BestMatch
getBestMatch([], _, _, -1).
Now before doing anything you need to know what was the BestMatch
value from the Tail List T
is to compare you current element H
with it:
getBestMatch([H|T], Tastes, Result, BestMatch) :-
getBestMatch(T, Tastes, TmpRes, TmpBest),
Assuming the not-yet-programmed code works, you know now the recommendation from the TmpRes
with score TmpBest
. Now you need to know the score of your current dish H
.
matches(H, Tastes, Matches),
And then there follows an if-then-else for tunneling the right result to the "output"
( Matches > TmpBest
-> Result = H,
BestMatch = Matches
; Result = TmpRes,
BestMatch = TmpBest
).
Or as complete code for getBestMatch/4
:
getBestMatch([], _, _, -1).
getBestMatch([H|T], Tastes, Result, BestMatch) :-
getBestMatch(T, Tastes, TmpRes, TmpBest),
matches(H, Tastes, Matches),
( Matches > TmpBest
-> Result = H,
BestMatch = Matches
; Result = TmpRes,
BestMatch = TmpBest
).
Ok, let's test it:
?- getBestMatch([pizza], [italian, cheese], L, _).
L = pizza.
?- getBestMatch([pizza,lasagna,cake], [italian], L, _).
L = lasagna.
Seems to work at first glance, but the problem is that the order of dishes in the list gives a preference to the dishes (more on the right are strictly favored). This can be fixed as well, if wished.
For this altered predicate to work you need to change the call
getBestMatch(Breakfasts, Tastes, Result, -1).
to
getBestMatch(Breakfasts, Tastes, Result, _).
The underscore _
means this is a variable where you are not interested in its value.
Test for recommend/3
:
?- recommend(lunch, [warm, meat],L).
L = lasagna.
?- recommend(lunch, [warm, cheese],L).
L = pizza.
?- recommend(lunch, [sweet], L).
L = pizza.
Upvotes: 2