Related
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.
Program should check if there is a direct route between two given cities. Alternatively, it can list all the connected cities for a given city.
My Solution is:
I keep a list of cities I visit. If the next city is not the city I came from and the name of the next city is not on the list, I let you press the screen.
My code is:
% knowledge base
path(newyork,losangeles).
path(losangeles,newyork).
path(losangeles,chicago).
path(chicago,losangeles).
path(chicago,houston).
path(houston,chicago).
path(houston,newyork).
path(newyork,houston).
% rules
route(X,Y):-myroute(X,X,Y,[]).
myroute(X,Y,Z,_L):- path(Y,Z), not(X = Z).
myroute(X,Y,A,L):- path(Y,A), not(X = A) , not(member(A,L)),
append(X,L,T) , myroute(Y,A,_Q,T).
Output:
?- route(newyork,Y).
Y = losangeles ;
Y = houston ;
false
Expected Output:
?- route(newyork,Y).
Y = losangeles ;
Y = chicago ;
Y = houston ;
false
My code is check if there is a direct route between two given cities. It can't list all the connected cities for a given city. Where am I making mistakes?
Let's follow the logic of your code:
?- route(newyork, Y).
Prolog tries to unify this with what it knows from the database. It finds this definition:
route(X,Y):-myroute(X,X,Y,[]).
Therefore it unifies X with newyork and proceeds to check if
myroute(newyork, newyork, Y, []) is true, as per the definition of route.
Prolog finds a match for that term in it database, with the first definition of the myroute predicate, and proceeds to unify X, Y and _L in that definition as newyork, newyork and [], i.e. it checks to see if myroute(newyork, newyork, Y, []) can be satisfied given the definition for myroute.
According to the first definition of myroute, prolog checks to see if it can find a fact in its database that will satisfy path(newyork, Z), such that Z is not newyork. There are two facts that can satisfy this, for Z=losangeles and for Z=houston. Note that there is no path(newyork, chicago) fact in the database, therefore this does not qualify as an answer given your current definitions.
Note also that if the first myroute definition fails, then so will the second one, since it checks for exactly the same things first! Therefore there will be no other solutions found via backtracking in this instance.
What you should be doing instead
I won't solve your exercise for you (especially since you didn't ask me to :p ) but in general, your myroute predicate is probably expected to work as follows:
myroute(StartPoint, EndPoint, RouteSoFar) :- % your definition here
and called from route like so:
route(X, Y) :- myroute(X, Y, []).
RouteSoFar is an accumulator; in the first call we call it with [] because we haven't visited any nodes, but in subsequent calls to myroute, it will be a list that you add to before calling the predicate again recursively. Somewhere in your predicate definition you'll have to check that the node you're about to visit isn't in the list of visited nodes already.
The logic of myroute is this: "There exists a myroute from X to Z if there is a simple path from X to Z (base case), OR if there is a path from X to an intermediate UNVISITED node Y, and then a myroute from that Y to Z (recursive case)".
I have a homework assignment where I must write a predicate seatingChart(X):- which will have 8 seats. The rules are:
Adjacent seating partners must be of the opposite gender.
Adjacent seating partners must share at least one of the same hobby.
I thought I wrote the code below to create the correct case.
person(jim,m).
person(tom,m).
person(joe,m).
person(bob,m).
person(fay,f).
person(beth,f).
person(sue,f).
person(cami,f).
% Database of hobbies
% hobbies(name,hobby). -> People can have multiple hobbies)
hobbies(jim, sup).
hobbies(jim, fish).
hobbies(jim, kayak).
hobbies(tom, hike).
hobbies(tom, fish).
hobbies(tom, ski).
hobbies(joe, gamer).
hobbies(joe, chess).
hobbies(joe, climb).
hobbies(bob, paint).
hobbies(bob, yoga).
hobbies(bob, run).
hobbies(fay, sup).
hobbies(fay, dance).
hobbies(fay, run).
hobbies(beth, climb).
hobbies(beth, cycle).
hobbies(beth, fish).
hobbies(sue, yoga).
hobbies(sue, skate).
hobbies(sue, ski).
hobbies(cami, run).
hobbies(cami, kayak).
hobbies(cami, gamer).
%% ANSWER %%
% return a pair of opposite gender people
gender(PersonX, PersonY):-
person(PersonX,GenderX),
person(PersonY,GenderY),
GenderX \= GenderY.
% return the pair of similar interests.
similarHobbies(PersonX, PersonY):-
hobbies(PersonX, HobbyX),
hobbies(PersonY, HobbyY),
HobbyX == HobbyY.
% Create the rules for our seating chart list
seatingRules([P1,P2,P3,P4,P5,P6,P7,P8|_]):-
% Have each adjacent person be of the opposite gender
gender(P1,P2),
gender(P3,P4),
gender(P5,P6),
gender(P7,P8),
gender(P8,P1),
% Have each adjacent person have at least one of the same hobby
similarHobbies(P1,P2),
similarHobbies(P3,P4),
similarHobbies(P5,P6),
similarHobbies(P7,P8).
% Generate a list of all the names from person(...)
people(P):-
findall(X, person(X,_), P).
% Generate a list of permutations of people
permPeople([P1,P2,P3,P4,P5,P6,P7,P8]):-
permutation([P1,P2,P3,P4,P5,P6,P7,P8],
[jim,tom,joe,bob,fay,beth,sue,cami]),
\+error([P1,P2,P3,P4,P5,P6,P7,P8]).
error([P1,P2,P3,P4,P5,P6,P7,P8]):-
\+seatingRules([P1,P2,P3,P4,P5,P6,P7,P8]).
seatingChart(X):-
permPeople(X).
When I run this using seatingChart(X). in SWI-Prolog I get the following answer first:
X = [jim, fay, tom, beth, joe, cami, bob, sue] ;
However, my subsequent permutations seem to be flat out wrong.. after hitting ; a few more times this says it's a valid answer:
X = [jim, beth, sue, tom, joe, cami, bob, fay] .
What am I doing wrong? Or what is causing my permutations to start not following the seating chart rules?
Shouldn't the seating rule predicate contain all pairs?
% Create the rules for our seating chart list
seatingRules([P1,P2,P3,P4,P5,P6,P7,P8|_]):-
% Have each adjacent person be of the opposite gender
gender(P1,P2),
gender(P2,P3),
gender(P3,P4),
gender(P4,P5),
gender(P5,P6),
gender(P6,P7),
gender(P7,P8),
gender(P8,P1),
% Have each adjacent person have at least one of the same hobby
similarHobbies(P1,P2),
similarHobbies(P2,P3),
similarHobbies(P3,P4),
similarHobbies(P4,P5),
similarHobbies(P5,P6),
similarHobbies(P6,P7),
similarHobbies(P7,P8),
similarHobbies(P8,P1).
I am working through sample questions while studying, using SWI-Prolog. I have reached the last section of this question, where I have to recursively (I hope) compare elements of a list containing 'researcher' structures to determine whether or not the researchers have the same surname, and, if they do, return the Forename and Surname of the group leader for that list.
There is only one list that meets this criteria and it has four members, all with the same surname. However, the correct answer is returned FOUR times. I feel my solution is inelegant and is lacking. Here is the question:
The following Prolog database represents subject teaching teams.
% A research group structure takes the form
% group(Crew, Leader, Assistant_leader).
%
% Crew is a list of researcher structures,
% but excludes the researcher structures for Leader
% and Assistant_leader.
%
% researcher structures take the form
% researcher(Surname, First_name, expertise(Level, Area)).
group([researcher(giles,will,expertise(3,engineering)),
researcher(ford,bertha,expertise(2,computing))],
researcher(mcelvey,bob,expertise(5,biology)),
researcher(pike,michelle,expertise(4,physics))).
group([researcher(davis,owen,expertise(4,mathematics)),
researcher(raleigh,sophie,expertise(4,physics))],
researcher(beattie,katy,expertise(5,engineering)),
researcher(deane,fergus,expertise(4,chemistry))).
group([researcher(hardy,dan,expertise(4,biology))],
researcher(mellon,paul,expertise(4,computing)),
researcher(halls,antonia,expertise(3,physics))).
group([researcher(doone,pat,expertise(2,computing)),
researcher(doone,burt,expertise(5,computing)),
researcher(doone,celia,expertise(4,computing)),
researcher(doone,norma,expertise(2,computing))],
researcher(maine,jack,expertise(3,biology)),
researcher(havilland,olive,expertise(5,chemistry))).
Given this information, write Prolog rules (and any additional predicates required) that can be used to return the following:
the first name and surname of any leader whose crew members number more than one and who all have the same surname. [4 marks]
This is the solution I presently have using recursion, though it's unnecessarily inefficient as for every member of the list, it compares that member to every other member. So, as the correct list is four members long, it returns 'jack maine' four times.
surname(researcher(S,_,_),S).
checkSurname([],Surname):-
Surname==Surname. % base case
checkSurname([Researcher|List],Surname):-
surname(Researcher,SameSurname),
Surname == SameSurname,
checkSurname(List,SameSurname).
q4(Forename,Surname):-
group(Crew,researcher(Surname,Forename,_),_),
length(Crew,Length),
Length > 1,
member(researcher(SameSurname,_,_),Crew),
checkSurname(Crew,SameSurname).
How could I do this without the duplicate results and without redundantly comparing each member to every other member each time? For every approach I've taken I am snagged each time with 'SameSurname' being left as a singleton, hence having to force use of it twice in the q4 predicate.
Current output
13 ?- q4(X,Y).
X = jack,
Y = maine ; x4
A compact and efficient solution:
q4(F, S) :-
group([researcher(First,_,_), researcher(Second,_,_)| Crew], researcher(S, F, _), _),
\+ (member(researcher(Surname, _, _), [researcher(Second,_,_)| Crew]), First \== Surname).
Example call (resulting in a single solution):
?- q4(X,Y).
X = jack,
Y = maine.
You are doing it more complicated than it has to be. Your q4/2 could be even simpler:
q4(First_name, Surname) :-
group(Crew, researcher(Surname, First_name, _E), _A),
length(Crew, Len), Len > 1,
all_same_surname(Crew).
Now you only need to define all_same_surname/1. The idea is simple: take the surname of the first crew member and compare it to the surnames of the rest:
all_same_surname([researcher(Surname, _FN, _E)|Rest]) :-
rest_same_surname(Rest, Surname).
rest_same_surname([], _Surname).
rest_same_surname([researcher(Surname, _FN, _E)|Rest), Surname) :-
rest_same_surname(Rest, Surname).
(Obviously, all_same_surname/1 fails immediately if there are no members of the crew)
This should be it, unless I misunderstood the problem statement.
?- q4(F, S).
F = jack,
S = maine.
How about that?
Note: The solution just takes the most straight-forward approach to answering the question and being easy to write and read. There is a lot of stuff that could be done otherwise. Since there is no reason not to, I used pattern matching and unification in the heads of the predicates, and not comparison in the body or extra predicates for extracting arguments from the compound terms.
P.S. Think about what member/2 does (look up its definition in the library, even), and you will see where all the extra choice points in your solution are coming from.
Boris did answer this question already, but I want to show the most concise solution I could come with. It's just for the educational purposes (promoting findall/3 and maplist/2):
q4(F, S) :-
group(Crew, researcher(S, F, _), _),
findall(Surname, member(researcher(Surname, _, _), Crew), Surnames),
Surnames = [ First, Second | Rest ],
maplist(=(First), [ Second | Rest ]).
I'm working on a simple program that has a database of people. When given a year it should state the "King" of that year, where the king is the eldest living man.
For simplicity, all people in the database are eligible as long as they are alive at the given year and I'm assuming there are no twins.
My problem is picking the "oldest" person alive during a given year. I can't seem to figure out how to ask Prolog to examine all possible kings and pick the oldest.
male(jack).
male(roy).
male(ele).
born(jack,2000).
born(dave,1999).
born(roy,1980).
born(ele,1990).
died(jack, 2100).
died(dave, 2099).
died(roy, 1990).
died(ele, 1999).
% compare X against all other possibleSuccessors and make sure he was born 1st.
eldest(X,Year):-
born(X,T1),
born((possibleSuccessor(Year,_)),T2),
T1 < T2.
% must be male and have been born before or during the given year and must not be dead.
possibleSuccessor(Year, X):-
male(X),
born(X,B),
died(X,D),
(B =< Year),
(D >= Year).
successor(Year):-
possibleSuccessor(Year,X),
eldest(X,Year),
write(X).
Any help on comparing all possible answers vs one another would be appreciated. I attempted to use findall before but was unsuccessful.
Prolog offers a restricted form of negation (negation by failure) that solve your problem:
eldest(X,Year):-
born(X,Year),
\+((born(_,T), T<Year)).
this says that X is eldest if we can't find any other born before him.
alternatively, setof/3 can be used:
eldest(X,Year):-
setof((Y,K), born(K,Y), [(Year,X)|_]).
this works sorting all pairs (Y,K), then we can pick just the head of the result.
edit this should solve the problem, but I've introduced a service predicate
eldest(X, Year):-
alive(X, Year, B),
\+((alive(_, Year, T), T<B)).
alive(X, Year, B) :- born(X, B), B =< Year, \+ (died(X, D), D < Year).
% must be male and have been born before or durring the given year and must not be dead.
possibleSuccessor(Year,X):-
male(X),
alive(X, Year, _).
successor(Year):-
possibleSuccessor(Year,X),
eldest(X,Year),
write(X).