Seating chart starts to output wrong permutations in Prolog - algorithm

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).

Related

Finding most occurrences in set of prolog rules

I can't seem to wrap my head around how Prolog actually works. I'm very used to other programming languages like Java and Python but Prolog seems to be very different since it is based on a set of logical statements.
If someone can explain to me how I would approach a situation where I am given a set of rules such as
likes(john,mary).
likes(mary,adam).
likes(adam,mary).
likes(jack,destiny).
likes(destiny,adam).
likes(brianna,adam).
and I want to find out how to see who is the most liked person (in this case adam = 3), how would I do this?
Maybe it's easier if you think of Prolog as a special database retrieval language that can morph into functional programming in the same line.
Here we we have a relation likes/2 over which we want to run statistics.
One could use predicates from library(aggregate) or similar, but let's not
Suggestion is to chain three operations:
Create a nicer structure to run stats
Run stats over nicer structure
Find the best
Create nicer structure to run stats
Collect
the vector (in the form or a Prolog list) of persons that occur as second argument in predicate likes/2 (so that we have something to count), and
the set of persons (also in the form of a Prolog list) so that we can iterate over something.
The key are the collection predicates findall/3 and setof/3
findall/3 is used to collect all the Person that appear on second argument position of likes/2,
setof/3 is used to collect the set of all Person that appear on first or second argument position of likes/2.
To make that work, setof/3 needs to be told that the argument on the other position is unimportant by
"existentially quantifying" it with X^.
person_occurrences(PersonVec) :-
findall(Person,likes(_,Person),PersonVec).
person_set(PersonSet) :-
setof(Person,X^(likes(Person,X);likes(X,Person)),PersonSet).
Alternativey for person_set/2, more comprehensible:
person(Person) :- likes(Person,_).
person(Person) :- likes(X,Person).
person_set(PersonSet) :- setof(Person,person(Person),PersonSet).
Trying this on the "Prolog Toplevel" shows we are on the right track:
?- person_occurrences(PersonSet).
PersonSet = [mary, adam, mary, destiny, adam, adam].
?- person_set(PersonSet).
PersonSet = [adam, brianna, destiny, jack, john, mary].
We can easily count how often a person occurs in the vector of persons,
by using findall/3 to create an arbitrary list of x (for example),
one x for each occurrence, then determining the length of that list:
count(Person,PersonVec,Count) :-
findall(x,member(Person,PersonVec),Xs),length(Xs,Count).
Trying this on the "Prolog Toplevel" shows we are on the right track:
?- person_occurrences(PersonVec),count(mary,PersonVec,Count).
PersonVec = [mary, adam, mary, destiny, adam, adam],
Count = 2.
We now have the "nicer structure" that we can use to do stats, namely the
"vector of persons" and the "set of persons".
Run stats over nicer structure
The result here, called Stats shall be a list (it's always lists) of
pairs -(NumberOfOccurrencesOfPersonInPersonVector,Person),
which can be more easily written "infix": Count-Person, for example 2-mary.
This is a recursive definition (or an inductive definition) whereby we "count"
for each person element in PersonSet until the PersonSet is the empty set
(or rather, the empty list), upon which we are done and succeed. The result
is constructed in the third argument:
% stats(PersonVec,PersonSet,Stats)
stats(_,[],[]).
stats(PersonVec,[Person|MorePersons],[Count-Person|MoreStats]) :-
count(Person,PersonVec,Count), % count them
stats(PersonVec,MorePersons,MoreStats). % recursion
Trying this on the "Prolog Toplevel" shows we are on the right track:
?- person_occurrences(PersonVec),stats(PersonVec,[mary],Stats).
PersonVec = [mary, adam, mary, destiny, adam, adam],
Stats = [2-mary] ; % Maybe more solutions?
false. % Nope.
New we can build the whole of the stats list:
stats(Stats) :-
person_occurrences(PersonVec),
person_set(PersonSet),
stats(PersonVec,PersonSet,Stats).
Trying this on the "Prolog Toplevel" shows we are on the right track:
?- stats(Stats).
Stats = [3-adam, 0-brianna, 1-destiny, 0-jack, 0-john, 2-mary] ;
false.
Find the best
Given Stats, we can find a BestPerson by maximizing over the list of pairs.
This can be done directly by selecting the pair which is "largest"
according to "the standard order of term": the numeric count comes first
so a term with a larger numeric count is "larger" than one with a
smaller numeric count, which is what we want. The predicate
max_member/2
does what we want:
best(Stats,BestPerson,BestCount) :-
max_member(BestCount-BestPerson,Statss).
Alternatively, we can program-out the max_member/2 (and keep
it to numeric comparison of the first argument, AND get several
answers in case there are several persons with the same "likes"
count), like so:
% start the maximization over Stats with a dummy "(-1)-nobody"
best(Stats,BestPerson,BestCount) :-
best2(Stats, (-1)-nobody, BestCount-BestPerson).
% best2(Stats,BestCountSoFar-BestPersonSoFar,Result).
best2([],BestCountSoFar-BestPersonSoFar,BestCountSoFar-BestPersonSoFar).
best2([Count-_|MoreStats],BestCountSoFar-BestPersonSoFar,Result) :-
Count < BestCountSoFar,
best2(MoreStats,BestCountSoFar-BestPersonSoFar,Result). % keep best
best2([Count-_|MoreStats],BestCountSoFar-BestPersonSoFar,Result) :-
Count == BestCountSoFar,
best2(MoreStats,BestCountSoFar-BestPersonSoFar,Result). % keep best (2nd possibility below)
best2([Count-Person|MoreStats],BestCountSoFar-_,Result) :-
Count >= BestCountSoFar,
best2(MoreStats,Count-Person,Result). % take new, better, pair
Conclude
We run it together:
?- stats(Stats),best(Stats,BestPerson,BestCount).
Stats = [3-adam, 0-brianna, 1-destiny, 0-jack, 0-john, 2-mary],
BestPerson = adam, BestCount = 3 ; % maybe more solutions?
false. % no
Complete code
likes(john,mary).
likes(mary,adam).
likes(adam,mary).
likes(jack,destiny).
likes(destiny,adam).
likes(brianna,adam).
person_occurrences(PersonVec) :-
findall(Person,likes(_,Person),PersonVec).
person_set(PersonSet) :-
setof(Person,X^(likes(Person,X);likes(X,Person)),PersonSet).
count(Person,PersonVec,Count) :-
findall(x,member(Person,PersonVec),Xs),length(Xs,Count).
% stats(PersonVec,PersonSet,Stats)
stats(_,[],[]).
stats(PersonVec,[Person|MorePersons],[Count-Person|MoreStats]) :-
count(Person,PersonVec,Count), % count them
stats(PersonVec,MorePersons,MoreStats). % recursion
stats(Stats) :-
person_occurrences(PersonVec),
person_set(PersonSet),
stats(PersonVec,PersonSet,Stats).
% start the maximization over Stats with a dummy "(-1)-nobody"
best(Stats,BestPerson,BestCount) :-
best2(Stats, (-1)-nobody, BestCount-BestPerson).
% best2(Stats,BestCountSoFar-BestPersonSoFar,Result).
best2([],BestCountSoFar-BestPersonSoFar,BestCountSoFar-BestPersonSoFar).
best2([Count-_|MoreStats],BestCountSoFar-BestPersonSoFar,Result) :-
Count < BestCountSoFar,
best2(MoreStats,BestCountSoFar-BestPersonSoFar,Result). % keep best
best2([Count-_|MoreStats],BestCountSoFar-BestPersonSoFar,Result) :-
Count == BestCountSoFar,
best2(MoreStats,BestCountSoFar-BestPersonSoFar,Result). % keep best (2nd possibility below)
best2([Count-Person|MoreStats],BestCountSoFar-_,Result) :-
Count >= BestCountSoFar,
best2(MoreStats,Count-Person,Result). % take new, better, pair
Consider the set of facts:
likes(john,mary).
likes(mary,adam).
likes(adam,mary).
likes(jack,destiny).
likes(destiny,adam).
likes(brianna,adam).
Another possible solution is as follows:
You can use setof/3 to get the list of persons that like someone:
?- setof(Person, likes(Person,Someone), ListOfPersons).
Someone = adam,
ListOfPersons = [brianna, destiny, mary] ;
Someone = destiny,
ListOfPersons = [jack] ;
Someone = mary,
ListOfPersons = [adam, john].
Then, you can combine setof/3 with findall/3 to get a list of pairs of the form Someone-ListOfPersons:
?- findall(Someone-ListOfPersons, setof(Person, likes(Person,Someone), ListOfPersons), Pairs).
Pairs = [adam-[brianna, destiny, mary], destiny-[jack], mary-[adam, john]].
After that, you can use maplist/3 to map pairs of the form Someone-ListOfPersons into corresponding pairs of the form Someone-NumberOfPersons:
?- findall(Someone-ListOfPersons, setof(Person, likes(Person,Someone), ListOfPersons), Pairs),
maplist([Someone-ListOfPersons, Someone-NumberOfPersons]>>length(ListOfPersons,NumberOfPersons), Pairs, NewPairs).
Pairs = [adam-[brianna, destiny, mary], destiny-[jack], mary-[adam, john]],
NewPairs = [adam-3, destiny-1, mary-2].
Finally, you can use sort/4 to get the most liked person:
?- findall(Someone-ListOfPersons, setof(Person, likes(Person,Someone), ListOfPersons), Pairs),
maplist([Someone-ListOfPersons, Someone-NumberOfPersons]>>length(ListOfPersons,NumberOfPersons), Pairs, NewPairs),
sort(2,>=,NewPairs, SortedPairs).
Pairs = [adam-[brianna, destiny, mary], destiny-[jack], mary-[adam, john]],
NewPairs = [adam-3, destiny-1, mary-2],
SortedPairs = [adam-3, mary-2, destiny-1].
Thus, the final solution is:
most_liked(Person) :-
findall(Someone-ListOfPersons,
setof(Person, likes(Person,Someone), ListOfPersons),
Pairs),
maplist([Someone-ListOfPersons, Someone-NumberOfPersons]>>length(ListOfPersons, NumberOfPersons),
Pairs,
NewPairs),
sort(2, >=, NewPairs, [Person-_|_]).
Running example:
?- most_liked(Person).
Person = adam.
Another solution where we don't care about the admonition to "do things only once" and "let Prolog work for us" instead is simply this:
Determine how much an arbitrary person is "liked"
person_liked_count(Person,Count) :-
likes(_,Person), % Grab a Person
findall(x, % Create a list of 'x'
likes(_,Person), % one 'x' for each like of the Person
Xs), % and this will be list 'Xs'.
length(Xs,Count). % The number of likes is the length of the list
We now get multiple solutions for any person, but we don't care:
?- person_liked_count(Person,Count).
Person = mary, Count = 2 ;
Person = adam, Count = 3 ;
Person = mary, Count = 2 ;
Person = destiny, Count = 1 ;
Person = adam, Count = 3 ;
Person = adam, Count = 3.
Maximize by doing exactly what is demanded
Person with "likes count" Count is what we want if we have person_liked_count(Person,Count) and there is no other person that has higher count (there is no need to even check that _PersonOther is different from Person inside the negation-as-failure-marked-subgoal, although we can):
most_liked(Person,Count) :-
person_liked_count(Person,Count), % grab a Person and a Count
\+ (person_liked_count(_P,CountOther), % "where not exists" a person _P
CountOther > Count). % with a higher count
We now get several answers, but that is not a problem as they are all the same:
?- most_liked(Person,Count).
Person = adam, Count = 3 ;
Person = adam, Count = 3 ;
Person = adam, Count = 3.
We can always force determinism with once/1
?- once(most_liked(Person,Count)).
Person = adam, Count = 3.
Everything in one block
likes(john,mary).
likes(mary,adam).
likes(adam,mary).
likes(jack,destiny).
likes(destiny,adam).
likes(brianna,adam).
person_liked_count(Person,Count) :-
likes(_,Person), % Grab a Person
findall(x, % Create a list of 'x'
likes(_,Person), % one 'x' for each like of the Person
Xs), % and this will be list 'Xs'.
length(Xs,Count). % The number of likes is the length of the list
most_liked(Person,Count) :-
person_liked_count(Person,Count), % grab a Person and a Count
\+ (person_liked_count(_P,CountOther), % "where not exists" a person _P
CountOther > Count). % with a higher count
solution(Person,Count) :- once(most_liked(Person,Count)).

Prolog endless loop with Intersect predicate.(Scheduling)

I have a base file which i include which contains many attends/2 predicates like the following.
attends(student_ID,course_iD). (Example: attends(2222,CSD101).)
The predicate schedule_e(W,IN,E) :
-W is a List that contains 3 courses and represents a week of examination. For instance W = [CSD101,CSD102,CSD103].
What it basically does is that for a student takes all the courses that he has and intersects them with the W list and then Returns this List IN of the intersected along with the number of courses this IN list contains. IN list basically contains all the courses that the student will be examined on and E is the number of the courses this student has on the exams.
The predicate count_num just returns how many numbers in a list are greater than 2.
The last predicate:
W1,W2,W3 are week1 week2 week3 exactly like the above one.
Each findall finds all the courses for each student and for each week and then intersects them with the week itself so we can know for each student how many courses he has on the examination for each week. Then count_num counts how many students have more than 2 courses for per week and returns it.
If i run ?- schedule_errors([CSD101,CSD102,CSD103],[CSD104,CSD105,CSD106],[CSD107,CSD108],E). everything's great.
But if i run schedule_errors(W1,W2,W3,0) (so that it will give the schedule or else the 3 weeks for which the number of students that have more that 2 courses to examined on is 0), in this case it says out of global stack for which i raised the global stack and it seems that it enters in an endless recursion. It also notes that this happens in Intersect. How else can i implement this? What exactly is the problem.
(The first 2 weeks have always 3 courses each and the 3rd has 2 courses.)
intersect([],_,[]).
intersect([H1|T1],L2,[H1|Res]):-
member(H1,L2),
intersect(T1,L2,Res).
intersect([_|T1],L2,Res):-
intersect(T1,L2,Res).
%1
schedule_e(W1,IN,E):-
setof(C,attends(Y,C), L),intersect(L,W1,IN),length(IN,E).
%2
count_num(_,[],0).
count_num(A,[H|L],N):-A<H,count_num(A,L,N1),N is N1+1.
count_num(A,[H|L],N):-A>=H,count_num(A,L,N).
%3
schedule_errors(W1,W2,W3,E):-
findall(E1, schedule_e(W1, _, E1), Esa),
findall(E2, schedule_e(W2, _, E2), Esb),
findall(E3, schedule_e(W3, _, E3), Esc),
append(Esa,Esb,L),append(L,Esc,L1),count_num(2,L1,E).

Querying a Prolog knowledge base

% A quiz team structure takes the form:
% team(Captain, Vice_captain, Regular_team_members).
% Captain and Vice_captain are player structures;
% Regular_team_members is a list of player structures.
% player structures take the form:
% player(First_name, Surname, details(Speciality,Recent_score)).
I've been given the following Prolog database:
team(player(niall,elliott,details(history,11)),
player(michelle,cartwright,details(fashion,19)),
[player(peter,lawlor,details(science,12)),
player(louise,boyle,details(current_affairs,17))
]
).
What would be the code needed to get the firstname and the recent score of all players whose recent score is above 15?
I've tried using exists but it keeps giving me errors.
Second question:
I need to get the surname of any vice-captain whose team includes a captain or a regular team member whose speciality is science.
I can get the surname of the vice-captains by using the first line below, but the second part is more tricky.
part_two(Surname):-
team(_,player(_,Surname,_),_),
Regular_player = team(_,_,player(_,_,details(science,_))),
Captain = team(player(_,_,details(science,_),_,_)).
A more detailed description of what you tried and how it didn't work would be better because (a) some people are reluctant to do your homework for you, and (b) we can better clear up your misunderstandings if we know what those misunderstandings are.
Anyway, Prolog programming is all about decomposing problems.
The first problem is to find out which players exist at all. A player is a team captain or a team vice captain or a regular team member. This definition has three parts separated by "or", which suggests that we need a predicate composed of three clauses:
player(Captain) :-
team(Captain, _, _).
player(Vice_captain) :-
team(_, Vice_captain, _).
player(Regular_player) :-
team(_, _, Regular_members),
member(Regular_player, Regular_members).
We can test this:
?- player(P).
P = player(niall, elliott, details(history, 11)) ;
P = player(michelle, cartwright, details(fashion, 19)) ;
P = player(peter, lawlor, details(science, 12)) ;
P = player(louise, boyle, details(current_affairs, 17)).
Now we want to identify "good players". You wrote that you have "tried using exists". There is no exists in Prolog, and it isn't needed. In order to express something like "there exists a player P such that ...", we just define a predicate containing the goal player(P) and some other goals expressing the property we are interested in. This leads to a definition like this:
good_player(First_name, Recent_score) :-
player(P),
P = player(First_name, _, details(_, Recent_score)),
Recent_score > 15.
You can read this as "there is a player P with first name First_name and recent score Recent_score such that the recent score is greater than 15".
?- good_player(F, S).
F = michelle,
S = 19 ;
F = louise,
S = 17.

Prolog find flights

I'm having some issues with the findall/3 in Prolog.
Facts:
%
country(dublin,ireland).
country(cork,ireland).
country(london,uk).
country(rome,italy).
country(moscow,russia).
country(hongkong,china).
country(amsterdam,holland).
country(berlin,germany).
country(paris,france).
country(newyork,usa).
country(chicago,usa).
country(sao_paulo,brazil).
country(rio,brazil).
I need to write predicate to show the connections from city X to city Y (one by one). X and Y are the two inputs (city) and T is the output. Each solution T is a list of all the cities connecting X to Y (X and Y included).
Example:
| ?- trip(rome,dublin,T).
T=[rome,london,dublin] ;
//first solution T=[rome,paris,dublin];
//second solution
my try is
path(X,Y,[X|Y]):- edge(X,Y).
path(X,Y,[]):- edge(X,Z),not(member(Z,V)),path(Z,Y,[Z|V]).
Any ideas would be greatly appreciated.
Cheers,
I think your question is: "how can I display all possible trip between two country ?". If so I advice you to do something like that:
trip(X,Y,T) :- X \= Y,
path(X, Y, [X], T).
path(Departure, Arrival, Visited, go(Departure, Arrival)) :- direct(Departure, Arrival) , !.
path(Departure, Arrival, Visited, go(Departure, Intermediate, GO)) :-
direct(Departure, Intermediate),
Intermediate \= Departure,
\+ member(Intermediate,Visited),
path(Intermediate, Arrival, [Intermediate|Visited], GO).
So I also advice you to modify the facts in your knowledge base like:
direct(rome, moskow).
direct(moskow, paris).
direct(paris, chicago).
direct(sau_paolo, rio).
direct(rio, honkong).
and so on.
What you did is logically uncorrect. How can you know if you can travel from a city to another only knowing where they are? There should be some facts that state that is possible travel from a city to another, e.g. direct(rome, milan) (or travel(rome, milan), you choose the functor).
I hope to have help you.
If you have some dubt, just ask ;)
by use prolog
Timetable of air routes
Each air route can be described by a structure:
country of departure, country of landing, date of departure, time of departure, duration, tariffs.
Tariff can described by a structure:
price for business class, price for economy class.
Implement the following rules:
• Find all routes from the given country
• Find all routes to the given country with duration less then than the given number
• Find all routes with the cheapest price for economy class
• Find all prices for flying from one given country to another given country

Comparing list element structures to each other in Prolog

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 ]).

Resources