SWI Prolog - Using a predicate with another predicate - prolog

I'm relatively new to prolog, and I'll try and explain this the best I can. Say that I have a small knowledge base of restaurants, where it has the name, cuisine, and price.
restaurant(spaghetti, italian, 20).
restaurant('naan bread', indian, 30).
...
And I have some people that like certain restaurants, such as:
likes(adam, restaurant, italian).
Where Adam likes italian restaurants. The main issue I'm having is that if I do a query such as:
likes(adam, spaghetti).
or
likes(adam, _, spaghetti).
It only comes up with false, no matter what I put. I have done a lot of research, but can't seem to get it to work - since I'm newish at prolog I don't really understand it. I have looked at books like 'Programming in Prolog' by Clocksin and Mellish and various websites, but I can't seem to find an answer or one I understand.

Based on your comment
It's only a knowledge base of restaurants. So there aren't any other rules.
we'll take it from there and start with the given rules.
restaurant(spaghetti, italian, 20).
restaurant('naan bread', indian, 30).
Next is the query you are trying
likes(adam, spaghetti).
Which is valid, but as we note in the comments some facts are missing.
The simplest fact to make the query correct would be
likes(adam, spaghetti).
but you have other queries like
likes(adam, restaurant, italian).
and facts like
restaurant('naan bread', indian, 30).
which suggest that you are aware that there are relationships between the four entities, adam, itialian and spaghetti, 20 (price).
There are endless ways to make relationships but for this example we will keep it more on the simple side.
person(adam).
person(mary).
food_nationality(spaghetti, italian).
food_nationality(hamburger, americian).
food_nationality('naan bread', indian).
food_price(spaghetti, 20).
food_price(hamburger, 30).
food_price('naan bread', 30).
likes(adam,italian).
likes(mary,american).
Now that we have some facts if you wanted to know what it cost for adam to eat a food he likes we start with the facts and see what we can conclude.
We see the fact
person(adam).
but that only tells us adam is a person and doesn't lead to any more information for our question.
We also see the fact
likes(adam,italian).
which tells us that adam likes italian but doesn't give us a specific food.
We also see
food_nationality(spaghetti, italian).
So we know adam likes italian and italian has spagehetti but we still need a price.
We also see
food_price(spaghetti, 20).
So we know adam likes italian and italian has spagehetti and spaghetti cost 20. So the answer is that for adam to eat something he likes it will cost 20.
As a Prolog predicate it would be
cost_to_eat(Person,Price) :-
likes(Person,Nationality),
food_nationality(Food, Nationality),
food_price(Food, Price).
and running that for adam
?- cost_to_eat(adam,Price).
Price = 20.
It also works for mary
?- cost_to_eat(mary,Price).
Price = 30.
and it also works if you give just a price
?- cost_to_eat(Person,20).
Person = adam ;
false.
and it also works it you ask
?- cost_to_eat(Person,Cost).
Person = adam,
Cost = 20 ;
Person = mary,
Cost = 30.

So let's just say you have the following facts, as suggested in your question, with no extra and no changes:
restaurant(spaghetti, italian, 20).
restaurant('naan bread', indian, 30).
likes(adam, restaurant, italian).
Now you'd like to ask the question "What dish does Adam like?"
We can add this simple rule:
likes(Person, Dish) :-
likes(Person, restaurant, Type),
restaurant(Dish, Type, _).
Now you can go ahead and ask the following:
?- likes(adam, Dish), write(Dish).
This spits out the answer spaghetti.

Related

Variation of the zebra puzzle in prolog, and I can't figure out where I'm going wrong

I know the zebra puzzle is asked often here, but this is a bit different: We were given a variation of the zebra puzzle in Prolog to write. I'm super new to this, but I even tried getting help from some people last year who had themselves a different variation, and they weren't sure what was going on with my code.
I'll post the whole thing, I hope it's not overwhelming or bad practice.
register_renderer/2
:- use_rendering(table,
[header(dorm('Major', 'Car', 'Team', 'Music', 'Drink'))]).
csMusic(Music) :-
dorms(Dorms),
member(dorm(computerscience,_,_,Music,_),Dorms).
engDrink(Drink) :-
dorms(Dorms),
member(dorm(english,_,_,_,Drink),Dorms).
dorms(Dorms) :-
length(Dorms, 5),
% 1. The computer science student lives in the middle of the corridor.
Dorms = [_,_,(dorm(computerscience,_,_,_,_)),_,_],
% 2. The history major is a jazz fan.
member(dorm(history,_,_,jazz,_),Dorms),
% 3. The Yankees fan drives a Toyota.
member(dorm(_,toyota,yankees,_,_),Dorms),
% 4. The accounting major drinks Coke.
member(dorm(accounting,_,_,_,coke),Dorms),
% 5. The engineering major drinks coffee.
member(dorm(engineering,_,_,_,coffee),Dorms),
% 6. The computer science student and history student are neighbors.
adjacent((dorm(computerscience,_,_,_,_)),(dorm(history,_,_,_,_)),Dorms),
% 7. The student at the far end of the hall likes classical music.
Dorms = [_,_,_,_,(dorm(_,_,_,classical,_))],
% 8. The tea drinker drives a Tesla.
member(dorm(_,_,_,_,_),Dorms),
% 9. The classical music fan lives next to the jazz listener.
adjacent((dorm(_,_,_,classical,_)),(dorm(_,_,_,jazz,_)),Dorms),
% 10. The English major does not live in either of the first two rooms.
member(dorm(english,_,_,_,_),Dorms),
not(Dorms = [dorm(english,_,_,_,_)]),
not(Dorms = [_,dorm(english,_,_,_,_),_,_,_]),
% 11. The Royals fan drives a Tesla.
member(dorm(_,tesla,royals,_,_),Dorms),
% 12. The Cubs fan listens to jazz.
member(dorm(_,_,cubs,jazz,_),Dorms),
% 13. The engineering major follows the Chiefs
member(dorm(engineering,_,chiefs,_,_),Dorms),
% 14. The first room is the home of the Broncos fan
Dorms = [dorms(_,_,broncos,_,_),_,_,_,_],
% 15. The Coke drinker drives a Nissan.
member(dorm(_,nissan,_,_,coke),Dorms),
% 16. The country music fan and the techno fan are neighbors.
adjacent((dorm(_,_,_,country,_)),(dorm(_,_,_,techno,_)),Dorms),
% 17. The accounting major lives in the first room.
Dorms = [dorms(accounting,_,_,_,_),_,_,_,_],
% 18. The fans of the 2 Kansas City teams (Chiefs and Royals) are neighbors
adjacent((dorm(_,_,chiefs,_,_)),(dorm(_,_,royals,_,_)),Dorms),
% 19. The accounting major listens to rock music
member(dorm(accounting,_,_,rock,_),Dorms),
% 20. The Yankees fan drinks milk.
member(dorm(_,_,yankees,_,milk),Dorms),
% 21. The Chevy driver listens to country music.
member(dorm(_,chevy,_,country,_),Dorms),
% 22. The jazz fan drives a Ford.
member(dorm(_,ford,_,jazz,_),Dorms),
% 23. Water isnt used.
member(dorm(_,_,_,_,water),Dorms).
next(A, B, Ls) :- append(_, [A,B|_], Ls).
next(A, B, Ls) :- append(_, [B,A|_], Ls).
adjacent(A,B,List) :- next(A,B,List); next(B,A,List).
My database loads just fine, but results in false when I try to run anything. If I run dorms(Dorms) to print out all the results, I am returned false. When I run csMusic (trying to find the type of music the CS major listens to) or engDrink (trying to find what kind of drink the English major drinks), I am returned false.
I know quite little of Prolog, but I did my best to follow along on the swipl website when going over this problem. Is there perhaps something easy I'm missing that anyone could point out? An inconsistency in naming, perhaps?
Help is much appreciated, thanks!
If dorms(Dorms) fails, then certainly csMusic(Music) will fail because it requires dorms(Dorms) to succeed.
One way to find the problem is to try commenting out some clues and then run the query dorms(Dorms). You'll find that you get multiple solutions. Eventually you'll find one clue that causes failure. Of course, that might not be the bad clue.
Another thing that might be wrong is the use of "not". For example, if you run this query:
?- length(Dorms, 5), not(Dorms = [dorm(english,_,_,_,_)]). % 10.
Dorms = [_9200, _9206, _9212, _9218, _9224].
is that result what you expect? You might want to review the "closed world assumption" that Prolog uses for negation and consider an alternative way of writing this clue.
Minor point: there's no need to put parentheses around dorm(...). For example, the first constraint can be written Dorms = [_,_,dorm(computerscience,_,_,_,_),_,_].
In clause 8, I didn't actually mention tea or Tesla.
In clause 10, my first use of not had the incorrect number of items in the array.
In clauses 14 and 17, I misspelled dorm as 'dorms'.
Fixing these, I was able to receive the correct and complete answer.

How to use the cut operator?

I've been learning and I came across a problem. I understood the cut operator from the tutorials but I'm trying to solve a problem and I cannot understand the solution.
Problem:
If the car color is red, made in Italy, then it's a Ferrari. If it's red but made in Germany (or any other country. could be more than one), it's Benz. If it's not red and is big, it's ford. If it's not red and not big, it's Toyota.
That is:
red & Italy: Ferrari
red & Germany (or not Italy): Benz
not red & big: ford
not red & not big: Toyota
Given some facts for a particular car object:
color(cx, red).
speed(cx, 220).
make(cx, italy).
type(cx, sport).
I want to write a predicate brand(X, name) that will return the brand of the particular car objects, like:
brand(X, ferrari):-
color(X,red), make(X,T), T=italy.
brand(X, benz) :-
color(X,red), not(make(X,italy)).
brand(X, ford) :-
not(color(X,red)), size(X,big).
brand(X, toyota) :-
not(color(X,red)), not(size(X,big)).
Question is how (and where) do I use the cut operator here so that it doesn't check the same property (eg: here "make") twice? I can't seem to wrap my head around this.
If I check for red and then for make, if the make turns out not italy, how do I write the brand(X, brand_name) for a set of facts for car object "ck" such that it doesn't check the make again? It appears impossible to me.
The short answer: Don't.
Almost invariably, using !/0 will lead to loss of valid solutions.
Always keep in mind that Prolog programs are typically much more general than programs in other languages. In particular, keep in mind that users can always post the most general query, which in your case is:
?- brand(X, Y).
If you have !/0 in this definition, then yes, you will likely "check only once", but on the other hand, you will fail to generate all valid answers.
One good way out is to use if_/3 from library(reif). For example, in your case, you can write the facts as:
color(cx, red, true).
speed(cx, 220, true).
make(cx, italy, true).
type(cx, sport, true).
And now you can write:
car_type(C, Type) :-
if_(color(C,red),
if_(make(C, italy), Type=ferrari, Type=bentz),
if_(type(C, big), Type=ford, Type=toyota)).
Importantly, the most general query will still work even if there are multiple solutions:
?- car_type(C, Type).
C = cx,
Type = ferrari.

Prolog: deduction given that items can be in exactly one of two sets, with set sizes known

I have 5 people in a room. I'll be writing rules to determine whether the people are happy or sad. However, before I even start with that, I have the overlying knowledge that - of the 5 - exactly 3 are happy and 2 are sad (and none can be both). It should therefore be possible to make deductions based on this: if - by any means - I know who the three happy people are, then I can deduce the two sad people, and vice versa.
What I've got so far is as follows:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(X) :-
person(X),
\+ sad(X),
findall(Y, sad(Y), YS),
length(YS, 2).
When asked happy(X), Prolog will give me Roy, Steve and Jack, because it already knows who the two sad people are. Problem: I'm unable to define a sad/1 rule in the same manner, because of the mutual recursion with happy/1. I want to be able to add in rules such that the outcome in the above example remains the same, yet the following initialisation would list Bob and Tim as sad:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
happy(steve).
happy(roy).
happy(jack).
Is there a better way I should be thinking about this? It's important that I'll be able to go on to later write more rules for sad/1 and happy/1, adding additional logic beyond the fact that deduction should be possible based on the knowledge that the 5 are split into 3 happy and 2 sad.
How about using clpb?
:- use_module(library(clpb)).
Sample query:
?- Hs = [Bob,Tim,Steve,Roy,Jack],
sat(card([3],Hs)), % exactly three are happy.
(
Who = sad, sat(~H_bob * ~H_tim) % specify the sad ones ...
; Who = happy, sat(H_jack * H_roy * H_steve) % ... OR the happy ones?
),
labeling(Hs).
Who = sad, Bob = 0, Tim = 0, Jack = 1, Roy = 1, Steve = 1, Hs = [0,0,1,1,1]
; Who = happy, Bob = 0, Tim = 0, Jack = 1, Roy = 1, Steve = 1, Hs = [0,0,1,1,1].
Sorting the logic out is a matter of consistency and avoiding conflicting meanings of a given predicate or fact.
Your definition of sad/1 is currently a fact that results in one result for each backtrack to the query, sad(X). But your definition of happy/1 generates a list. That leaves you with how you want to define sad/1 to generate a list, which would be in conflict with your current definition of sad/1 as a query that is true if the argument is a sad person.
A more consistent approach would be define happy/1 to behave the way sad/1 behaves:
happy(X) :-
person(X),
\+ sad(X).
Then you can define your list versions:
happy_all(A) :-
findall(X, happy(X), A).
sad_all(A) :-
findall(X, sad(X), A).
Now the above assumes you have explicit facts for person/1 which defines the universe of all valid people, and sad/1 which defines who the sad ones are. It also assumes that if a person isn't sad, then they must be happy.
You could flip this around and explicitly define happy people with happy/1 facts, then define sad/1 in terms of people not being happy assuming that a person must be happy if they aren't sad:
sad(X) :-
person(X),
\+ sad(X).
And the happy_all/1 and sad_all/1 predicates will still apply.
If you want to mix your facts with happy/1 and sad/1, this can create a consistency issue: (1) cases where a person isn't defined as happy or sad... then what are they? and (2) what if a person is defined as both happy and sad?
Semantically, you may want to define both sad/1 and happy/1 explicitly if you are also allowing for someone not being either happy or sad. You can do this:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(steve).
happy(roy).
happy_all(A) :-
findall(X, happy(X), A).
sad_all(A) :-
findall(X, sad(X), A).
But not define predicates for happy/1 or sad/1 since they are already facts. That keeps things simple. We just don't know if jack is happy or sad.
But what if we want to say that if a person isn't happy or sad, then they must be happy and add that rule back in. To avoid the looping you mentioned, we don't to be mixing rule names with fact names. In that case:
person(bob).
person(tim).
person(steve).
person(roy).
person(jack).
sad(bob).
sad(tim).
happy(steve).
happy(roy).
% A person is happy if they are, in fact, happy
happy_person(X) :-
happy(X),
% A person is happy if they are neither happy or sad
happy_person(X) :-
person(X),
\+ happy(X),
\+ sad(X).
% A person is sad if they are, in fact, sad
sad_person(X) :-
sad(X).
% Who are all the happy people?
happy_all(A) :-
findall(X, happy_person(X), A).
% Who are all the sad people?
sad_all(A) :-
findall(X, sad_person(X), A).

Prolog: Stating more facts in less statements

I have a problem in Prolog and I really really need help with it.
There are people (male and female) and they have propterties:
Name
Gender
Age
Weight
Hair color
and
favourite song
favourite sport
favourite book
I need state these facts about a person, but I am not supposed to state more than 3 facts!
could someone help me?
Here's an easy way:
person(PersonName, Gender, Age, Weight, HairColor).
favorite(PersonName, Song, Sport, Book).
There's no reason you have to limit yourself to arity-1 or -2 facts. You can design your Prolog fact database the same way you would design a relational database. Prolog will index the first term of the fact automatically anyway, so make that a candidate key and observe as your program performs well despite the apparent searching. :)
Edit: To illustrate a few queries:
person(maria, female, 28, 61, blond).
Is there a person named Maria?
?- person(maria, _, _, _, _).
yes.
What is Maria's favorite sport?
?- favorite(maria, _, Sport, _).
Sport = tennis ;
no.
etc. For more details, see #mbratch's comment below.

Solving Caliban problems with prolog

I'm working on solving a logic puzzle using prolog for school. Here's the clues:
Brown, Clark, Jones and Smith are 4 substantial citizens who serve their
community as achitect, banker, doctor and lawyer, though not necessarily
respectively.
Brown, who is more conservative than Jones but more liberal than Smith,
is a better golfer than the men who are younger than he is and has a
larger income than the men who are older than Clark.
The banker, who earns more than the architect, is neither the youngest
nor the oldest.
The doctor, who is a poorer golfer than the lawyer, is less conservative
than the architect.
As might be expected, the oldest man is the most conservative and has the
largest income, and the youngest man is the best golfer.
What is each man's profession?
hint: to rank people for weath, ability, relative age, etc
use the numbers 1,2,3,4 Be careful to state whether 1 represents,
e.g., youngest or oldest. Doing this makes comparisons easy to code.
To code (as follows) interprets all the relationships, given by the clues, as a list of lists, wherein each list defines the
%[profession,surname,politics,relative_age, relative_salary, golf_ability]:
profession(L) :- L = [[_,'Brown',_,_,_,_],[_,'Jones',_,_,_,_],[_,'Clark',_,_,_,_],
[_,'Smith',_,_,_,_]],
member([_,'Brown',P1,A6,M3,G3],L),
member([_,'Jones',P2,_,_,_],L),
member([_,'Clark',_,A3,_,_],L),
member([_,'Smith',P3,_,_,_],L),
moreconservative(P1,P2),
moreliberal(P1,P3),
bettergolfer(G3,younger(_,A6)),
richer(M3,older(_,A3)),
member(['banker',_,_,A1,M1,_],L),
member(['architect',_,P5,_,M2,_],L),
richer(M1,M2),
(A1 = 2;A1 = 3),
member(['doctor',_,P4,_,_,G1],L),
member(['lawyer',_,_,_,_,G2],L),
worsegolfer(G1,G2),
moreliberal(P4,P5),
member([_,_,4,4,4,_],L),
member([_,_,_,1,_,4],L).
I define the relative_politics,relative_salary,relative_age, and golf_ability relationships like so
EG:
richer(4,1).
moreconservative(4,1).
poorer(1,4).
poorer(1,3).
And it goes on for all relationships.
I think I have faithfully translated all of the clues to prolog but it just says fail when I query the database. EG:
?- profession(L).
fail.
I am using NU Prolog. I'm wondering if I made an error in my translation of the clues or I omitted a fact that is needed for the database to satisfy all the conditions of the list L.
bettergolfer(G3,younger(_,A6)) ... it doesn't work this way, in Prolog. Instead, have this
( member( X,L), age(X,AX), golf(X,GX),
( younger(AX,A6) -> better_golfer(G3,GX) ; true )),
.....
age( [_,_,_,A,_,_],A).
golf([_,_,_,_,_,G],G).
.....
this means, all the persons (including none) that are younger than Brown, must be poorer golfers than he is.
There is a catch here, too. Since we're told about the men younger than Brown, it means there must exist at least one such man (unlike in the mathematical definition of implication). We have to code this too. For example,
( member(X,L), age(X,AX), younger(AX,A6) -> true ),
.....
(using unique names for the new logvars of course). You'll have to make the same transformation for your richer(M3,older(_,A3)).
Great idea BTW, to have the comparison predicates defined in a generative fashion:
poorer(1,2).
poorer(1,3).
poorer(1,4).
poorer(2,3).
poorer(2,4).
poorer(3,4).
richer(A,B):- poorer(B,A)
If you were to define them as arithmetic comparisons, poorer(A,B):- A<B., you could potentially run into problems with uninstantiated variables (as recently discussed here).

Resources