I recently started learning Prolog for fun. I found the following murder mystery puzzle. Since I don't know much about Prolog except the basics, I cannot really evaluate the solution provided in the link, however, it didn't seem particularly nice to me. My solution is not enough to generate the correct answers so I'm looking for some pointers as to how to get there or if it's at all possible to get there with my approach. Here's the puzzle just in case the link goes down:
To discover who killed Mr. Boddy, you need to learn where each person
was, and what weapon was in the room. Clues are scattered throughout
the quiz (you cannot solve question 1 until all 10 are read).
To begin, you need to know the suspects. There are three men (George,
John, Robert) and three women (Barbara, Christine, Yolanda). Each
person was in a different room (Bathroom, Dining Room, Kitchen, Living
Room, Pantry, Study). A suspected weapon was found in each room (Bag,
Firearm, Gas, Knife, Poison, Rope). Who was found in the kitchen?
Clue 1: The man in the kitchen was not found with the rope, knife, or
bag. Which weapon, then, which was not the firearm, was found in the
kitchen?
Clue 2: Barbara was either in the study or the bathroom; Yolanda was
in the other. Which room was Barbara found in?
Clue 3: The person with the bag, who was not Barbara nor George, was
not in the bathroom nor the dining room. Who had the bag in the room
with them?
Clue 4: The woman with the rope was found in the study. Who had the
rope?
Clue 5: The weapon in the living room was found with either John or
George. What weapon was in the living room?
Clue 6: The knife was not in the dining room. So where was the knife?
Clue 7: Yolanda was not with the weapon found in the study nor the
pantry. What weapon was found with Yolanda?
Clue 8: The firearm was in the room with George. In which room was the
firearm found?
It was discovered that Mr. Boddy was gassed in the pantry. The suspect
found in that room was the murderer. Who, then, do you point the
finger towards?
Here's the link to the author's solution.
Here's my attempted solution:
male(george).
male(john).
male(robert).
female(barbara).
female(christine).
female(yolanda).
person(X) :- male(X).
person(X) :- female(X).
room(kitchen).
room(bathroom).
room(diningroom).
room(livingroom).
room(pantry).
room(study).
weapon(bag).
weapon(firearm).
weapon(gas).
weapon(knife).
weapon(poison).
weapon(rope).
/*
Clue 1: The man in the kitchen was not found with
the rope, knife, or bag.
Which weapon, then, which was not the firearm,
was found in the kitchen?
*/
/* X is Weapon, Y is Room, Z is Person */
killer(X, Y, Z) :-
room(Y) = room(kitchen),
male(Z),
dif(weapon(X), weapon(rope)),
dif(weapon(X), weapon(knife)),
dif(weapon(X), weapon(bag)),
dif(weapon(X), weapon(firearm)).
/*
Clue 2: Barbara was either in the study or the bathroom;
Yolanda was in the other.
Which room was Barbara found in?
*/
/* It was easy to deduce the following from other data */
killer(X, Y, Z) :-
female(Z) = female(barbara),
room(study) = room(Y).
killer(X, Y, Z) :-
female(Z) = female(yolanda),
room(bathroom) = room(Y).
/*
Clue 3: The person with the bag, who was not Barbara nor
George, was not in the bathroom nor the dining room.
Who had the bag in the room with them?
*/
killer(X, Y, Z) :-
weapon(bag) = weapon(X),
dif(room(Y), room(bathroom)),
dif(room(Y), room(diningroom)),
dif(person(Z), male(george)),
dif(person(Z), female(barbara)).
/*
Clue 4: The woman with the rope was found in the study.
Who had the rope?
*/
killer(X, Y, Z) :-
weapon(rope) = weapon(X),
room(study) = room(Y),
female(Z).
/*
Clue 5: The weapon in the living room was found with either
John or George. What weapon was in the living room?
*/
killer(X, Y, Z) :-
room(Y) = room(livingroom),
dif(male(Z), male(robert)).
/*
Clue 6: The knife was not in the dining room.
So where was the knife?
*/
killer(X, Y, Z) :-
weapon(knife) = weapon(X),
room(Y) \= room(diningroom).
/*
Clue 7: Yolanda was not with the weapon found
in the study nor the pantry.
What weapon was found with Yolanda?
*/
killer(X, Y, Z) :-
female(yolanda) = female(Z),
dif(room(study), room(Y)),
dif(room(pantry), room(Y)).
/*
Clue 8: The firearm was in the room with George.
In which room was the firearm found?
*/
killer(X, Y, Z) :-
weapon(firearm) = weapon(X),
male(george) = male(Z).
/*
It was discovered that Mr. Boddy was gassed in the pantry.
The suspect found in that room was the murderer.
Who, then, do you point the finger towards?
*/
killer(X, Y, Z) :-
room(Y) = room(pantry),
weapon(X) = weapon(gas).
I took a more positive approach to this problem. Rather than trying any form of negation I went with just plain unification.
Key is this predicate pair:
members([],_).
members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).
This is a basic permutation predicate. It will take a list of the first argument and try to unify against all permutations of second list.
Now a lot of the rules became quite easy to express:
For example, clue 1:
clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).
So this meant that the rope, knife, bag and firearm were all members of the house, but in different rooms than the kitchen. Prolog would keep backtracking util it found a fit for these items.
Here's my full solution:
man(george).
man(john).
man(robert).
woman(barbara).
woman(christine).
woman(yolanda).
members([],_).
members([M|Ms],Xs) :- select(M,Xs,Ys),members(Ms,Ys).
clue1(House) :- members([[P,kitchen,_],[_,_,rope],[_,_,knife],[_,_,bag],[_,_,firearm]],House),man(P).
clue2(House) :- member([barbara,study,_],House), member([yolanda,bathroom,_],House).
clue2(House) :- member([barbara,bathroom,_],House), member([yolanda,study,_],House).
clue3(House) :- members([[_,_,bag],[barbara,_,_],[george,_,_]],House),members([[_,_,bag],[_,bathroom,_],[_,dining_room,_]],House).
clue4(House) :- members([[P,study,rope]],House),woman(P).
clue5(House) :- members([[john,living_room,_]],House).
clue5(House) :- members([[george,living_room,_]],House).
clue6(House) :- members([[_,_,knife],[_,dining_room,_]],House).
clue7(House) :- members([[yolanda,_,_],[_,study,_],[_,pantry,_]],House).
clue8(House) :- member([george,_,firearm],House).
clue9(House,P) :- members([[P,pantry,gas]],House).
solve(X) :-
House = [[_,bathroom,_],[_,dining_room,_],[_,kitchen,_],[_,living_room,_],[_,pantry,_],[_,study,_]],
clue1(House),
clue2(House),
clue3(House),
clue4(House),
clue5(House),
clue6(House),
clue7(House),
clue8(House),
clue9(House,X),
members([[george,_,_],[john,_,_],[robert,_,_],[barbara,_,_],[christine,_,_],[yolanda,_,_]],House),
members([[_,_,bag],[_,_,firearm],[_,_,gas],[_,_,knife],[_,_,poison],[_,_,rope]],House),
write(House),
true.
That gave me:
?- solve(X).
[[yolanda,bathroom,knife],[george,dining_room,firearm],[robert,kitchen,poison],[john,living_room,bag],[christine,pantry,gas],[barbara,study,rope]]
X = christine .
Edit: See an improved version of the reference solution at https://swish.swi-prolog.org/p/crime_constraints.pl.
I agree that the solution you linked to is ugly, but it does use the right approach. Yours isn't quite going in the right direction. Some remarks:
/* X is Weapon, Y is Room, Z is Person */
Why not use the variable names Weapon, Room, and Person then? It makes your program much easier to read.
weapon(rope) = weapon(X)
This is exactly equivalent to just writing X = rope or rope = X.
But apart from these there are other two big problems with the way you are approaching this puzzle:
First, you are not modeling relationships between your objects as data. For example, for "The woman with the rope was found in the study." you have this clause:
killer(X, Y, Z) :-
weapon(rope) = weapon(X),
room(study) = room(Y),
female(Z).
This does indeed have three solutions that you can interpret as "a relation killer(rope, study, barbara), killer(rope, study, christine), or killer(rope, study, yolanda)", but your program doesn't know how to interpret it that way. You don't actually construct data that expresses this relationship. This is what the solution you linked to does correctly: It models rooms and weapons as variables which can be bound to atoms representing persons. Thus it can express this clue as woman(Rope) ("the person with the Rope is a woman") and Rope = Study ("the rope and the study are associated with the same person").
The second big problem is that you are modeling all clues as different clauses of the same predicate. This is wrong because in Prolog the different clauses of a predicate express a choice: Something holds if the first clause holds or the second clause holds or the third clause holds, etc. But you want to express that the first clue holds and the second clue holds and the third clue holds, etc. And "and" is expressed by combining the different conditions with , in the body of one clause. This is why the linked solution has different predicates clue1, clue2, etc., all of which are called from the body of one big predicate.
Derive Rules from the clues in sequence
Each person was in a different room (Bathroom, Dining Room, Kitchen,
Living Room, Pantry, Study). A suspected weapon was found in each room
(Bag, Firearm, Gas, Knife, Poison, Rope).
unique(A,B,C,D,E,F) :-
A \= B, A \= C, A \= D, A \= E, A \= F,
B \= C, B \= D, B \= E, B \= F,
C \= D, C \= E, C \= F,
D \= E, D \= F,
E \= F.
suspicious(pwr(george,WA,RA), pwr(john,WB,RB), pwr(robert,WC,RC), pwr(barbara,WD,RD), pwr(christine,WE,RE), pwr(yolanda,WF,RF)) :-
weapon(WA), weapon(WB), weapon(WC), weapon(WD), weapon(WE), weapon(WF),
unique(WA,WB,WC,WD,WE,WF),
room(RA), room(RB), room(RC), room(RD), room(RE), room(RF),
unique(RA,RB,RC,RD,RE,RF).
Now let us examine
Clue 1: The man in the kitchen was not found with the rope, knife, or
bag. Which weapon, then, which was not the firearm, was found in the
kitchen?
clue1(L) :-
oneof(pwr(P,W,kitchen),L),
male(P),
weapon(W),
W \= rope, W \= knife, W \= bag, W \= firearm.
We do this for each of the 8 clues and finally
It was discovered that Mr. Boddy was gassed in the pantry. The suspect
found in that room was the murderer. Who, then, do you point the
finger towards?
killer(X, L) :- member(pwr(X,gas,pantry),L).
resolved(X) :-
suspicious(A,B,C,D,E,F),
L = [A,B,C,D,E,F],
clue1(L),
clue2(L),
clue3(L),
clue4(L),
clue5(L),
clue6(L),
clue7(L),
clue8(L),
killer(X, L).
The full program could be found and run. The inference is rather slow (but faster than the authors solution).
Why consider it a better design to use relations instead of Variable bindings?
I understand a prolog program as a ruleset to derive knowledge. That means:
Each relation in prolog should describe a relation in the domain
Adding entities (Weapons, Persons, Rooms) to the world should not make the ruleset obsolete. The problem has not changed (we only extended the world) so the rules and queries need not to be touched.
Extending the problem (e.g. by adding a seventh location) should have minimal impact
Not every aspect is optimal in the referenced solution, some may be better expressed if one is more familiar with prolog.
Why do I think that a ruleset should be robust to world changes?
I used datalog in program analysis. That means that each relation in source code (or byte code) was modeled as facts and the rules inferred types, security vulnerabilities, design patterns etc. There were multiple millions of facts and multiple thousands of ruleset code. Adding an entity (e.g. a source code line, a type annotation) should not drive me to reimplement the ruleset code (which was quite hard to write it correctly).
Why do I think that using implicit relations is bad code?
Consider this code from the reference solution, it is totally misleading:
clue1(Bathroom, Dining, Kitchen, Livingroom, Pantry, Study, Bag, Firearm, Gas, Knife, Poison, Rope) :-
man(Kitchen), // a man is a kitchen?
\+Kitchen=Rope, // a kitchen is not a rope?
\+Kitchen=Knife, // a kitchen is not a knife?
\+Kitchen=Bag, // a kitchen is not a bag
\+Kitchen=Firearm. // a kitchen is not a firearm
Ok the variable names are ugly, better readable would be
clue1(InBathroom, InDiningroom, InKitchen, InLivingroom, InPantry, InStudy, WithBag, WithFirearm, WithGas, WithKnife, WithPoison, WithRope) :-
man(InKitchen), // (person) in the kitchen is a man - ok
\+Kitchen=Rope, // (person) in the kitchen is not
(person) with a rope - better than above
\+Kitchen=Knife, // ...
\+Kitchen=Bag, // ...
\+Kitchen=Firearm. // ...
But we misuse the equal relation for an explicit one. There is a clear indicator: Variables containing predicates in their names are probably implicit relations. "personInKitchen" is a (logical) predicate "in" connecting two substantives "person" and "kitchen".
As comparison a model with lists and function symbols (suspect/3 is the relational function that connects persons to weapons and rooms, Suspects is the list of suspects):
clue1(Suspects) :-
member(suspect(Person,Weapon,Room),Suspects),
male(Person), // The man (Person)
Room = kitchen, // in the Kitchen (Room)
Weapon \= rope, // was not found with the (Weapon) rope
Weapon \= knife, // (Weapon) knife
Weapon \= bag, // (Weapon) bag
Weapon \= firearm.// (Weapon) firearm
Summary
So if you use prolog for private purpose, I do not mind "misusing" Variables to come to a quick solution. But if your ruleset and your data grows it seems to me quite essential to model all relations explicitly.
I am a complete beginner in Mercury language, although I have learned Prolog before. One of the new aspects of Mercury is dererminism. The main function has to be deterministic. In order to make it so, I have to check if a variable is unified/bound to a value, but I cannot find how to do that. Particularly see the code:
main(!IO) :-
mother(X,"john"),
( if bound(X) <-----this is my failed try; how to check if X is unified?
then
io.format("%s\n", [s(X)], !IO)
else
io.write_string("Not available\n",!IO)
).
Such main could not fail, i.e. it would (I guess) satisfy the deterministic constraint. So the question is how to check if a variable is bound.
I've translated the family tree from a Prolog example found on this side for comparison.
I have specified all facts (persons) and their relationship with each other and a few helper predicates.
Note that this typed version did actually find the error that you see in the top answer: female(jane).
The main predicate does not have to be deterministic, it can also be cc_multi, which means Mercury will commit (not try other) choices it has;
you can verify this by replacing mother with parent.
You also do not have to check for boundness of your variables, instead you just use any not deterministic term in the if clause, and on succeeding the then part will have guaranteed bound variables, or unbound in the else part.
If you want this example to be more dynamic, you will have to use the lexer or term module to parse input into a person atom.
If you want all solutions you should check the solution module.
%-------------------------------%
% vim: ft=mercury ff=unix ts=4 sw=4 et
%-------------------------------%
% File: relationship.m
%-------------------------------%
% Classical example of family relationship representation,
% based on: https://stackoverflow.com/questions/679728/prolog-family-tree
%-------------------------------%
:- module relationship.
:- interface.
:- import_module io.
%-------------------------------%
:- pred main(io::di, io::uo) is cc_multi.
%-------------------------------%
%-------------------------------%
:- implementation.
:- type person
---> john
; bob
; bill
; ron
; jeff
; mary
; sue
; nancy
; jane
.
:- pred person(person::out) is multi.
person(Person) :- male(Person).
person(Person) :- female(Person).
:- pred male(person).
:- mode male(in) is semidet.
:- mode male(out) is multi.
male(john).
male(bob).
male(bill).
male(ron).
male(jeff).
:- pred female(person).
:- mode female(in) is semidet.
:- mode female(out) is multi.
female(mary).
female(sue).
female(nancy).
female(jane).
:- pred parent(person, person).
:- mode parent(in, in) is semidet.
:- mode parent(in, out) is nondet.
:- mode parent(out, in) is nondet.
:- mode parent(out, out) is multi.
parent(mary, sue).
parent(mary, bill).
parent(sue, nancy).
parent(sue, jeff).
parent(jane, ron).
parent(john, bob).
parent(john, bill).
parent(bob, nancy).
parent(bob, jeff).
parent(bill, ron).
:- pred mother(person, person).
:- mode mother(in, in) is semidet.
:- mode mother(in, out) is nondet.
:- mode mother(out, in) is nondet.
:- mode mother(out, out) is nondet.
mother(Mother, Child) :-
female(Mother),
parent(Mother, Child).
:- pred father(person, person).
:- mode father(in, in) is semidet.
:- mode father(in, out) is nondet.
:- mode father(out, in) is nondet.
:- mode father(out, out) is nondet.
father(Father, Child) :-
male(Father),
parent(Father, Child).
%-------------------------------%
main(!IO) :-
Child = john, % try sue or whatever for the first answer
( if mother(Mother, Child) then
io.write(Mother, !IO),
io.print(" is ", !IO),
io.write(Child, !IO),
io.print_line("'s mother", !IO)
else
io.write(Child, !IO),
io.print_line("'s mother is unknown", !IO)
).
%-------------------------------%
:- end_module relationship.
%-------------------------------%
You do not need to check variables for their instantiation state. I have never done it in almost 10 years of using Mercury on a daily basis. For each predicate and predicate's mode, Mercury knows statically the instantiation of each variable at each point in the program. So using your example:
% I'm making some assumptions here.
:- pred mother(string::out, string::in) is det.
main(!IO) :-
mother(X,"john"),
io.format("%s\n", [s(X)], !IO).
The declaration for mother says that its first argument is an output argument, this means that after the call to mother its value will be ground. and so it can be printed. If you did use a var predicate (and there is one in the standard library) it would always fail.
To do this Mercury places other requirements on the programmer. For example.
(
X = a,
Y = b
;
X = c
)
io.write(Y, !IO)
The above code is illegal. Because Y is produced in the first case, but not in the second so it's groundness is not well defined. The compiler also knows that the disjunction is a switch (when X is already ground) because only one disjunct can possibly be true. so there it produces only one answer.
Where this static groundness can become tricky is when you have a multi-moded predicate. Mercury may need to re-order your conjunctions to make the program mode correct: eg to put a use of a variable after it's production. Nevertheless the uses and instantiation states of variables will always be statically known.
I understand this is really quite different from Prolog, and may require a fair amount of unlearning.
Hope this helps, all the best.
main(!IO) :-
mother(X,"john"),
( if bound(X) <-----this is my failed try; how to check if X is unified?
then
io.format("%s\n", [s(X)], !IO)
else
io.write_string("Not available\n",!IO)
).
This code doesn't make a lot of sense in Mercury. If X is a standard output variable from mother, it will never succeed with X unbound.
If mother is deterministic (det) in this mode, it will always give you a single value for X. In that case there would be no need to check anything; mother(X, "john") gives you John's mother, end of story, so there's no need for the "Not available case".
Since you're trying to write cases to handle both when mother gives you something and when it doesn't, I'm assuming mother is not deterministic. If it's semidet then there are two possibilities; it succeeds with X bound to something, or it fails. It will not succeed with X unbound.
Your code doesn't say anything about how main (which is required to always succeed) can succeed if mother fails. Remember that the comma is logical conjunction (and). Your code says "main succeeds if mother(X, "john") succeeds AND (if .. then ... else ...) succeeds". But if mother fails, then what? The compiler would reject your code for this reason, even if the rest of your code was valid.
But the if ... then ... else ... construct is exactly designed to allow you to check a goal that may succeed or fail, and specify a case for when the goal succeeds and a case for when it fails, such that the whole if/then/else always succeeds. So all you need to do is put mother(X, "john") in the condition of the if/then/else.
In Mercury you don't switch on variables that are either bound or unbound. Instead just check whether the goal that might have bound the variable succeeded or failed.
First, sorry for posting the whole program, but as I don't know were the problem is I don't know which parts are irrelevant. These are two slightly different implementations of the same logic puzzle in SWI-Prolog, the first one succeeds the second one fails and I can't find the reason for the failure.
The puzzle:
4 persons are having a diner:
Donna, Doreen, David, Danny
the woman (Donna,Doreen) are sitting vis-a-vis.
the men (David,Danny) are sitting vis-a-vis.
Each of them picked a unique meal and beverage.
1) Doreen sits next to the person that ordered risotto.
2) the salad came with a coke.
3) the person with the lasagna sits vis-a-vis the person with the milk.
4) david never drinks coffee.
5) donna only drinks water.
6) danny had no appetite for risotto.
who ordered the pizza?
I choose the following approach
table with positions:
1
4 O 2
3
domain: positions{1,2,3,4}
variables: persons, meals, beverages
First the inefficient succeeding implementation:
solution(Pizza, Doreen, Donna, David, Danny) :-
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk),
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
next_to(Doreen,Risotto),
Salad = Coke,
vis_a_vis(Lasagna,Milk),
\+ David = Coffee,
Donna = Water,
\+ Danny = Risotto.
unique(X1,X2,X3,X4) :-
pos(X1),
pos(X2),
\+ X1 = X2,
pos(X3),
\+ X1 = X3, \+ X2 = X3,
pos(X4),
\+ X1 = X4, \+ X2 = X4, \+ X3 = X4.
right(1,2).
right(2,3).
right(3,4).
right(4,1).
vis_a_vis(1,3).
vis_a_vis(3,1).
vis_a_vis(2,4).
vis_a_vis(4,2).
next_to(X,Y) :- right(X,Y).
next_to(X,Y) :- right(Y,X).
pos(1).
pos(2).
pos(3).
pos(4).
This works and gives the right result. But when I try to reorder the clauses of the solution procedure to be more efficient (this is the second implementation)
solution(Pizza, Doreen, Donna, David, Danny) :-
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
Salad = Coke,
vis_a_vis(Lasagna,Milk),
\+ David = Coffee,
Donna = Water,
\+ Danny = Risotto,
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk).
%% all other predicates are like the ones in the first implementation
I get a unassigned variable warning when trying to load the file:
Warning: /home/pizza.pl:28:
Singleton variable in \+: Coffee
and the computation returns false. But shouldn't it return the same result?
I see no reason for the difference...
the warning is due to the fact that Coffe and Risotto are unbound when the negation is executed. If you replace \+ David = Coffee, by David \= Coffee, you will avoid the warning, but the solution cannot will not be computed. Should be clear indeed that since Coffee is unbound, David \= Coffee will always fail. You can use dif/2, the solution will work and will be more efficient. I've named solution1/2 your first snippet, and solution2/5 this one (using dif/2):
solution2(Pizza, Doreen, Donna, David, Danny) :-
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
next_to(Doreen,Risotto), % note: you forgot this one
Salad = Coke,
vis_a_vis(Lasagna,Milk),
dif(David, Coffee),
Donna = Water,
dif(Danny, Risotto),
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk).
a small test:
?- time(aggregate_all(count,solution1(P,A,B,C,D),N)).
% 380,475 inferences, 0.058 CPU in 0.058 seconds (100% CPU, 6564298 Lips)
N = 8.
?- time(aggregate_all(count,solution2(P,A,B,C,D),N)).
% 10,626 inferences, 0.002 CPU in 0.002 seconds (100% CPU, 4738996 Lips)
N = 8.
I am trying to create a prolog rule which will generate all the people in a social network using S number degrees of separation.
This is the rule that i have made but it is only printing empty lists. Can somebody please help me into helping me understand why this is happening and me where i am going wrong?:
socialN(_,N):- N<1,!.
socialN(_,N,_,_):- N<1,!.
socialN(P1,Separation,S1,S):-
(message(P1,P2,_); message(P2,P1,_)),
D is Separation-1,
\+(member(P2,S1)),
append(P2,S1,S2),socialN(P1,D,S2,S),!.
socialN(P2,Separation,S,S).
These are the facts:
message(allan, steve, 2013-09-03).
message(nayna, jane, 2013-09-03).
message(steve, jane, 2013-09-04).
message(steve, allan, 2013-09-04).
message(mark, martin, 2013-09-04).
message(martin, steve, 2013-09-04).
message(allan, martin, 2013-09-05).
E.g. Mark’s network includes just Martin for 1 degree of separation; it includes Martin, Steve and Allan for 2 degrees of separation; and Martin, Steve, Allan and Jane for 3.
I see you are using append and member, so I suppose you are trying to build up a list of people. I was a bit surprised that you were not using findall. Like this:
allDirectLinks(P1, L) :- findall(P2, directlyLinked(P1, P2), L).
directlyLinked(P1, P1).
directlyLinked(P1, P2) :- message(P1, P2, _).
directlyLinked(P1, P2) :- message(P2, P1, _).
From there, you can write a recursive function to find the indirect links:
socialN(0, P, [P]) :- !.
socialN(N, P1, L3) :-
N>0, !,
N1 is N-1,
socialN(N1, P1, L1)
maplist(allDirectLinks, L1, L2),
append(L2, L3).
For example, this yields in Y a list of people separated 2 steps or less from Mark:
socialN(2, mark, X), list_to_set(X, Y).
Please note, Mark himself is included in the resulting list (being a 'level 0' link); I suppose it cannot be too hard to filter that out afterwards.
I hope this makes sense; I am a bit rusty, haven't done any Prolog in 25 years.
EDIT: explanation of the rules I defined:
directlyLinked: true if there is a message between two persons (regardless of the direction of the message)
allDirectLinks: accumulates into list L all persons directly linked to a given person P1; just read the manual about findall
socialN: builds up a list of people connected to a given person (P) at a distance less than or equal to a given distance (N)
socialN(0, ...): at distance 0, every person is linked to himself
socialN(N, ...): makes a recursive call to get a list of connections at distance N-1, then uses maplist to apply allDirectLinks to every connection found, and finally uses append to concatenate the results together.
I'm having trouble wrapping my head around how I would return a list of everyone related to a certain person. So, if I say relatives(A,B), A would be a person and B is a list of all of the people related to that person. I can write any additional rules needed to assist in doing this. Here is what I have so far.
man(joe).
man(tim).
man(milan).
man(matt).
man(eugene).
woman(mary).
woman(emily).
woman(lily).
woman(rosie).
woman(chris).
parent(milan, mary).
parent(tim, milan).
parent(mary, lily).
parent(mary, joe).
parent(mary, matt).
parent(chris, rosie).
parent(eugene, mary).
parent(eugene, chris).
cousins(A, B) :- parent(C, A), parent(D, B), parent(E, C), parent(E, D), not(parent(C, B)), not(parent(D, A)), A \=B.
paternalgrandfather(A, C) :- man(A), man(B), parent(B, C), parent(A, B).
sibling(A, B) :- parent(C, A), parent(C, B), A \= B.
Can someone guide me as to how I would go about doing this? Thanks.
I think that you should concentrate on the 'true' relation, i.e. parent(Old,Jung), other predicates are irrelevant here. The obvious assumption it's that atoms occurring in parent/2 are identifiers (i.e. names are unique). From this picture seems that all persons here are relatives:
Then your problem should be equivalent to find all connected vertices in parent relation. You can implement a depth first visit, passing down the list of visited nodes to avoid loops (note that you need to go back to parents and down to children!), something like
relatives(Person, Relatives) :-
relatives([], Person, [Person|Relatives]).
relatives(Visited, Person, [Person|Relatives]) :-
findall(Relative, immediate(Person, Visited, R), Immediates),
... find relatives of immediates and append all in relatives.
immediate(Person, Visited, R) :-
(parent(Person, R) ; parent(R, Person)),
\+ member(R, Visited).
See if you can complete this snippet. Note the order of arguments in relatives/3 is choosen to easy maplist/3.
If you are willing to study more advanced code, SWI-Prolog library(ugraph) offers a reachable(+Vertex, +Graph, -Vertices) predicate that does it on a list based graph representation.
Here the SWI-Prolog snippet to get the image (a file to be feed to dot):
graph(Fact2) :-
format('digraph ~s {~n', [Fact2]),
forall(call(Fact2, From, To), format(' ~s -> ~s;~n', [From, To])),
format('}\n').
you can call in this way:
?- tell('/tmp/parent.gv'),graph(parent),told.
and then issue on command line dot -Tjpg /tmp/parent.gv | display
I think you should use builtin predicate findall/3 and maybe sort/2 to avoid duplicates
It would go along these lines:
relatives(Person, Relatives):-
findall(Relative, is_relative(Person, Relative), LRelatives),
sort(LRelatives, Relatives).
is_relative(Person, Relative):-
(cousins(Person, Relative) ; paternalgrandfather(Person, Relative) ; sibling(Person, Relative)).
You might want to add more clauses to is_relative to get more relationships.