How to show only one result in prolog? - prolog

Data:
%flight(FID, Start, Destination, Company, Seats).
%------------------------------------------------------
flight(1, 'Paris', 'Berlin', 'Lufthansa', 210).
flight(2, 'Frankfurt', 'Dubai', 'Lufthansa', 400).
flight(3, 'Rome', 'Barcelona', 'Eurowings', 350).
I want to know all companies that, that have flights with over 200 seats. But each company should be returned only once.
I tried:
q1(Company) :- flight(_, _, _, Company, S), S > 200.
But this returns lufthansa twice. I tried:
q1(Company) :- flight(_, _, _, Company, S), S > 200,!.
But this exited after first return of Lufthansa. I think I have to wrap the condition in a sub query:
q1(Company) :- flight(_, _, _, Company, S), q12(S).
q12(S) :- flight(_, _, _, _, S), S > 200,!.
But this exited after the first return, too. Any idea how I can return a company that matches only once using cut?
FID is the primary key and I am only allowed to use . , + < > <= >=

As I said in the comments, one way to do this is to use setof/3 to obtain a sorted list of results without duplicates, for example:
?- setof(C, q1(C), Cs).
This is a recommended way to remove redundant solutions. You can enumerate such solutions for example with:
?- setof(C, q1(C), Cs), member(C, Cs).
Viewer discretion is advised for the remainder...
There are also several ways to solve this subject to brain-damaged conditions that an unskilled instructor may impose on you. For example, here is an inefficient and highly non-idiomatic way to obtain all companies that occur in your database without using setof/3:
companies(Cs) :-
companies_([], Cs).
companies_(Cs0, Cs) :-
( flight(_, _, _, C, _),
\+ memberchk(C, Cs0) ->
companies_([C|Cs0], Cs)
; Cs0 = Cs
).
I don't have the stomach to carry this further, so I just end with the hint for you: You only need to insert one goal to solve your task. I hope your teacher is happy with this "solution".

The key here are the unique IDs. Your original question is:
Which companies have flights with over 200 seats?
The query is obvious:
?- flight(_,_,_,C,S), S > 200.
C = 'Lufthansa',
S = 210 ;
C = 'Lufthansa',
S = 400 ;
C = 'Eurowings',
S = 350.
Now, because the IDs are unique, there is going to be a flight within the group with the same company and seats > 200 which has the highest (or lowest) ID. So, you could re-formulate your question as:
Which flights have over 200 seats and the highest ID within the group of flights from the same company?
or, to make if a bit more close to the way that we can pose the query in Prolog,
Given a flight with an ID, Company, and Seats, the Seats must be more than 200, and there must be no other flight from the same company with a higher ID.
?- flight(ID,_,_,C,S), S > 200, \+ ( flight(IDX,_,_,C,_), IDX > ID ).
ID = 2,
C = 'Lufthansa',
S = 400 ;
ID = 3,
C = 'Eurowings',
S = 350.
If you put this query in a predicate you can avoid reporting the ID and the actual number of seats.
By the way, this approach is a courtesy of this answer to a somewhat related question (shameless self-promotion). I really can't remember where I got the idea: I am certain I didn't come up with it myself. If anyone can find a good reference here on Stackoverflow or elsewhere please comment.

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

How can I show the max number

I know my question will sound silly for you. It's just I can't get this language since it's very different from the languages I know. I need to take the max number of the rating predict but I didn't know how to do it. Whenever I do researches, what appears is lists while the rating isn't a list. Here is my code:
movie(name(thedarknight),director(christopher),
[starring(magijelnar,10000),starring(aroneikhart,30000)],
rating(9)).
movie(name(shazam),director(david),
[starring(markstrong,1000),starring(jackdaniel,3000)],
rating(7)).
& something else, am I doing the matrix right?
This predicate will obtain the name of the movie and its rating from the structure:
movierating(Name, Rating) :-
movie(name(Name), _, _, rating(Rating)).
If you want to get the maximum rating, the most straightforward and least efficient way to do it is to say, I have obtained rating X and there is no rating Y that is greater than X. Thus X is the maximum. In code, this looks like this:
highest_rating(Rating) :-
movie(_, _, _, rating(Rating)),
\+ (movie(_, _, _, rating(OtherRating)),
OtherRating > Rating).
Probably you will want to know the name of the movie too, so you can just add that into your initial query:
highest_rated(Movie, Rating) :-
movie(name(Movie), _, _, rating(Rating)),
\+ (movie(_, _, _, rating(OtherRating)),
OtherRating > Rating).
This is going to be O(N^2), which might be too inefficient for your liking, in which case you should probably use findall/3 to get all the ratings and then ask for the maximum. That would look like this:
?- findall(Rating, movie(_, _, _, rating(Rating)), Ratings),
max_list(Ratings, Rating).
Rating = 9,
Ratings = [9, 7].
This is also possible with library(aggregate):
?- aggregate(max(Rating, Movie),
Movie^D^A^movie(name(Movie), D, A, rating(Rating)),
max(Rating, Movie)).
Rating = 9,
Movie = thedarknight.
library(solution_sequences) has been recently introduced in SWI-Prolog:
?- order_by([desc(X)],movie(name(N),_,_,rating(X))).
X = 9,
N = thedarknight ;
X = 7,
N = shazam.

How to represent situation calculus in Prolog?

I am trying to represent a situation in situation calculus with swi-prolog. The situation is the sale of an item, where:
"Person A sells item I to Person B for $10. The item I had a value of $20 before the sale."
What I have so far is the base facts:
person(a).
person(b).
item(i).
owns(a,i,s0).
value(i,20,s0).
What I think I have to do is define the sell predicate. What I have tried so far is:
sell(Seller, Buyer, Item, Price, S0, S1):-
(
person(Seller), person(Buyer), item(Item), owns(Seller,Item,S0)
-> not(owns(Seller,Item,S1)),
owns(Buyer,Item,S1)
).
What I would like to do is to say sell(a,b,i,10,s0,s1) and then check owns(b,i,s1) which should return true. The issue is that I don't know how to set the owns(Buyer,Item,S1), since it doesn't seem to be setting there.
A possible solution would be to declare predicates such as owns/3 and value/3 as dynamic predicates:
:- dynamic([owns/3, value/3]).
And then rewrite your rule as something like:
sell(Seller, Buyer, Item, Price, S0, S1):-
( person(Seller),
person(Buyer),
item(Item),
owns(Seller,Item,S0) ->
assertz(owns(Buyer, Item, S1)),
assertz(value(Item, Price, S1))
).
Using dynamic predicates in Prolog is something that should be avoided when possible but this case may warrant their use.
Sample calls:
?- sell(a, b, i, 10, s0, s1).
true.
?- owns(Who, Item, Time).
Who = a,
Item = i,
Time = s0 ;
Who = b,
Item = i,
Time = s1.

Checking a combination (of a list) while creating it in Prolog

In our homework we are given a number of people and who knows who in the form of a database,
like this:
person(person1).
person(person2).
person(person3).
person(person4).
person(person5).
knows(person1,person2).
knows(person1,person3).
knows(person2,person4).
knows(person3,person5).
The database can contain any number of people with any set of connections.
We have a predicate (findall) that generate a list of the people one person (X) knows:
i.e.
findall(Y, knows(Y, person1);knows(person1, Y), AllPeople).
Y = [person2, person3] .
Then have a predicate that generate a combination of these people.
comb2(_,[]).
comb2([X|T],[X|Comb]):-comb2(T,Comb).
comb2([_|T],[X|Comb]):-comb2(T,[X|Comb]).
Disclaimer: we got this code snippet from:
http://ktiml.mff.cuni.cz/~bartak/prolog/combinatorics.html
i.e.
comb2([person2, person3], X).
X = [] ;
X = [person2] ;
X = [person2, person3] ;
X = [person3] ;
false.
The problem is that our code does a check on every combination (to see that no one in the group know anyone else in the group), and some of the test-cases have people with up to 30 friends (meaning astronomical amounts of combinations).
What we need is some way to test if anyone in the combination we are building know anyone else in the combination, while we are building it.
If I've correctly understood what you meant, an answer is to use findall/3 again with AllPeople to do just that.
% Assume that your code is here..
knows(person2,person3).
knows(person3,person2).
group(G) :-
G = [_,_|_],
forall(
( member(A, G)
, member(B, G)
, A \== B), knows(A, B)).
Corresponding query is:
?- One = person1,
findall(E
, knows(E, One)
; knows(One, E), AllPeople),
findall(G, (comb2(AllPeople, G), group(G)), Groups).
// ==> Groups = [[person2,person3]]

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