Comparing list element structures to each other in Prolog - 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 ]).

Related

How to read from a list in GNU prolog?

So I have an assignment where I am to produce compatible meeting times between 3 different people. In the prolog file where I define predicates, there is a line given that has the name of the three people I am supposed to compare that reads as follows:
people([ann,bob,carla]).
Where we are supposed to match these names from a data file that defines facts, where a fact holds the following format:
free(ann,slot(time(7,0,am),time(9,0,am))).
My question is, how do I read through 'people' so that I can match names against each other?
My text book doesn't really explain prolog too well, and I am confused on what 'people' actually is (when I say what it actually is, I mean is 'people' a list? an array?) so I am having troubles even searching for a solution as to how to read through each name so I can compare them.
people([ann,bob,carla]). is a fact. The predicate people/1 holds a list of people names. In prolog you have different ways to get elements from a list.
The most "dirtiest" version is just to write the list with a fixed number of elements:
?- people([P1,P2,P3]).
P1 = ann,
P2 = bob,
P3 = carla ;
false.
You should not do this, because it works only for sets of 3 people and you would have to alter your code everytime a person leaves/enters.
Normally you go through a prolog list where you just get the first element Head and the rest of a list Tail:
?- people([Head|Tail]).
Head = ann,
Tail = [bob, carla] ;
false.
By redoing this you can traverse through the whole list until the list has only one element left. To do this you need a help predicate, which I named person. person takes as first element a List and as second a variable (or a name for test). It unificates the variable with one element from the list:
person([H|_], H).
person([_|T], P):-
person(T, P).
?- people(L), person(L,P).
L = [ann, bob, carla],
P = ann ;
L = [ann, bob, carla],
P = bob ;
L = [ann, bob, carla],
P = carla ;
false.
It works as follows: you have a list and imagine you see the first element from it only. You have two choices here: first you are ok with just taking the head element as an output, so the second attribute should be the exact same as the head element: person([H|_], H).
Or second: you ignore the head element and try to find something in the rest of the list by just calling the predicate again with a smaller list: person([_|T], P):- person(T, P).
When a variable starts with an underscore _ you are not interested in its content.
Also worth knowing: there are (most likely) inbuild helper predicates such as member/2 which give you back any member of a list:
?- people(L), member(P,L).
will give you any person in L.
To access a single timeslot for a choosen person you simply ask for the predicate free with your person from the list:
?- people(L), member(P,L), free(P,S).
If you want to find a timeslot where all persons in the list have to participate you need to define a helper predicate. I named it hastime
hastime([],_).
hastime([H|L], S):-
free(H,S),
hastime(L,S).
The output of ?- people(L), free(_,S), hastime(L,S). will give you a timeslot S where everone has time. Before calling hastime/2 you guess a Timeslot S. hastime/2 will look if all of the people have time on S: if there are no people (empty list []) you can accept any timeslot (_). If there are at least one person H in your list: ask if H has time on timeslot S and try if the other people from the list have this timeslot S free as well by calling the predicate for the tail list.
If prolog choose a slot where not all of them have time, it will go back to the point where it choosed the timeslot S and will look for a different value and try again. If there are no such timeslots it will return false.
Also hastime/2 can be used to find a timeslot by itself, but using it as a "generator" and test at the same time is a bit confusing.

How to make a list of pairs from facts in SWI-Prolog?

Assuming I have some facts like the following
person(jessica,19,usa).
person(james,18,uk).
person(eric,34,italy).
person(jake,24,france).
how can I create a predicate that creates a large list of pairs of all the names and their corresponding country like so:
?-filter(L).
L=[(jessica,usa),(james,uk),(eric,italy),(jake,france)]
The best solution is this one:
?- bagof((P,C), Age^person(P,Age,C), People).
People = [(jessica, usa), (james, uk), (eric, italy), (jake, france)].
This gives you the same result as findall/3, because findall/3 implicitly assumes existential quantification on all variables not present in the template ((P,C) is the template). In your case you only have one, the age variable. Notice what happens if you don't include that:
?- bagof((P,C), person(P,_,C), People).
People = [(james, uk)] ;
People = [(jessica, usa)] ;
People = [(jake, france)] ;
People = [(eric, italy)].
What happened here? The value of the second parameter was the same across each solution because we didn't inform bagof/3 that we didn't care what it was bound to or even if it was bound to just one thing. This property of bagof/3 and setof/3 (but not findall/3) sometimes turns out to be surprisingly useful, so I tend to prefer using bagof/3 over findall/3 if I only need to mark a variable or two.
It's more obvious if we add another person the same age to the database:
person(janet,18,australia).
?- bagof((P,C), person(P,Age,C), People).
Age = 18,
People = [(james, uk), (janet, australia)] .
?- bagof((P,C), person(P,_,C), People).
People = [(james, uk), (janet, australia)] ;
Assuming person/3 is ground and terminates, you can implement it without setof as:
notin(_, []).
notin(X, [Y|Ys]) :-
dif(X,Y),
notin(X,Ys).
lt_list(_, []).
lt_list(X, [Y|Ys]) :-
X #< Y,
lt_list(X,Ys).
f( [ Name-Location | Rest], Acc) :-
person(Name, _, Location),
lt_list( Name-Location, Acc ),
f(Rest, [Name-Location | Acc]).
f( [], Acc) :-
\+ (person(Name,_,Location), notin(Name-Location,Acc)).
When we query f, we get our solutions:
?- f(Xs,[]).
Xs = [jessica-usa, james-uk, jake-france, eric-italy] ;
false.
I used X-Y instead of (X,Y) for better readability. The predicate notin describes an element that is not contained in a list and lt_list describes an element that is smaller than anything in the list by the standard term order.
The idea is that the first rule generates persons that I have not seen yet. Using the term order makes sure that we don't generate all permutations of the list (try replacing lt_list by notin to see what happens). The second rule makes sure we only terminate if there are no more solutions to generate. Be aware that the rule contains negation, which can have some unwanted side-effects. Most of them are filtered out by only looking at ground terms, but I have not thought well, how bad the impact is in this solution.

How to attach properties to terms?

I want to attach properties to my terms, from a set of about 50 different properties. Usually only a small subset of them are used for a given term. There are many ways to represent these properties, but I am not satisfied with any of them.
For the sake of discussion, here is a set of properties and their possible values:
hair: bald, blonde, brune, red
eyes: blue, green, brown
first_name: John, Dick, Harry
There are many ways to represent these properties, for example with a list of pairs:
[eyes-blue, hair-blonde]
The only representation that seems to work is to use a very long list, where each index is used for a specific property:
?- T1=[blonde,_,_], T2=[_,blue,_], T1=T2.
T1 = T2, T2 = [blonde, blue, _1266]
?- T1=[X,_,_], X=blue.
T1 = [blue, _1230, _1236],
X = blue
But it's unreadable with 50 properties, and very bugprone (in my case, a whole set of predicates is dedicated to each property, and sometimes to each value of a property).
The way I would use such a feature would be by having conditions like "Terms T1 and T2 have the same value for property X", or "Terms T1 and T2 are the same", where T1 and T2 have attributes which can be set elsewhere, or can be left unset.
Using dicts desn't work, because unset keys are considered non-existent:
?- T1 = _{eyes:blue, hair:blonde}, T2 = _{eyes:blue}, T1 = T2.
false.
For this to work, I would need to initialize each term with the 50 (mostly irrelevant) properties with free variables, on the off-chance that some of them will be used.
What other options do I have? I am open to using a different logic programming language if there is something closer to my needs than prolog.
With the "very long list", you have indeed found one possible representation that lets you directly use Prolog's built-in unification to perform the task for you.
As you note, this comes at a price though: It's unreadable, error-prone, wasteful etc.
There are many possible ways to solve the task, and I would like to give you two pointers that I hope you find relevant for your task.
Option 1: Use lists of pairs
This is in fact already mentioned in your post. Pairs of the form hair-blonde etc. are a natural way to represent the available data. By convention, (-)/2 is frequently used to denote pairs in Prolog.
All that is missing is precisely describing what "merging" such pairs means. You call it "unification", so let us use this terminology although it is of course different from syntactic unification that is available with (=)/2. One way to define the relation we want is:
unify_pairs([], APs, APs).
unify_pairs([A1-P1|APs1], APs2, APs) :-
if_(selectd_t(A1-P1, APs2, APs2Rest),
APs=[A1-P1|Rest],
if_(attr_exists_t(A1, APs2),
false,
APs = [A1-P1|Rest])),
unify_pairs(APs1, APs2Rest, Rest).
attr_exists_t(A, APs, T) :-
pairs_keys(APs, As),
memberd_t(A, As, T).
selectd_t(E, Xs0, Xs, T) :-
i_selectd_t(Xs0, Xs, E, T).
i_selectd_t([], [], _, false).
i_selectd_t([X|Xs], Rest, E, T) :-
if_(X=E, (T=true,Rest=Xs), (Rest = [X|Rs],i_selectd_t(Xs, Rs, E, T))).
This uses library(reif) and two auxiliary predicates to distinguish the different cases.
Your test cases work as required. For example:
?- unify_pairs([hair-blonde], [eyes-blue], Ps).
Ps = [hair-blonde, eyes-blue].
?- unify_pairs([eyes-blue], [eyes-brown], Ps).
false.
Importantly, we can use it in all directions, and so we can also post significantly more general queries. For example:
?- unify_pairs([T1-P1], [T2-P2], TPs).
T1 = T2,
P1 = P2,
TPs = [T2-P2] ;
TPs = [T1-P1, T2-P2],
dif(T2, T1),
dif(f(T2, P2), f(T1, P1)).
Such answers help us to obtain a better understanding of the relation, and to test it more exhaustively.
Option 2: Use lists of pairs again
The second pointer I would like to include is found in library(ordsets) and similar libraries that ship with several Prolog systems.
This again lets you use lists, even lists of pairs. Importantly, lists are available in all Prolog systems. Various operations are quite efficient due to the way these libraries represent sets as ordered lists.
However, the price you may pay in such cases is the generality explained in the first approach. I suggest you first try the more general approach (i.e., Option 1), and then, only if necessary, resort to lower-level approaches that are more error-prone and less general.
You maybe say "unification" but you mean something different from what unification normally means in Prolog which is why your question might be mistaken for a different question. You could do some things with SWI-Prolog dicts:
?- _{hair:blonde, eyes:blue} >:< _{eyes:blue}.
true.
?- _{hair:blonde, eyes:blue} >:< _{eyes:blue, hair:Color}.
Color = blonde.
?- _{hair:blonde, eyes:blue} >:< _{eyes:blue, hair:bald}.
false.
but you cannot directly do what you need, because if you "put" into a dict you add or replace which is not what you want.
?- R =_{eyes:blue}.put(_{hair:blonde}).
R = _7436{eyes:blue, hair:blonde}.
(this one was OK)
?- R =_{eyes:blue}.put(_{eyes:brown}).
R = _7436{eyes:brown}.
(this is not what you want, is it?)
what you want I don't know what to call in words but it is some form of finding union on keys in key-value pairs. But you can just do it with dicts I think if you first do P1 >:< P2 and then put_dict(P1, P2, Result)?
?- P1 = _{eyes:blue},
P2 = _{hair:blonde,eyes:brown},
P1 >:< P2, put_dict(P1, P2, Result).
false.
?- P1 = _{eyes:blue},
P2 = _{hair:blonde},
P1 >:< P2, put_dict(P1, P2, Result).
Result = _10044{eyes:blue, hair:blonde}.
?- P1 = _{eyes:blue},
P2 = _{hair:blonde,eyes:blue},
P1 >:< P2, put_dict(P1, P2, Result).
Result = _10046{eyes:blue, hair:blonde}.
Please respond if this is what you were asking because I am really not sure? But what is even more important actually is that you think a bit more carefully about the real problem you are trying to model because maybe? (just maybe?) you are thinking of it in terms of solution that is not as good as another solution that will make the problem be a lesser problem or a problem with already existing better solutions. Maybe it will help if you provide even more context about your problem in your question, because now there is enough context about how you tried to solve it but I don't know what you are really solving.
You could make the attributes one-arity terms, like this:
hair(bald)
hair(blonde)
eyes(blue)
eyes(green)
...
That would rule out unifications like
hair(blonde) = hair(red)
and you could quite easily write your own predicate for combining two lists, which could also block/filter out multiple instances of the same attribute.
In languages with strong typing this is a nice representation, but I'm not sure it's so useful in Prolog. Anyway it is a possibility.
I think I understand your question but I don't think I understand your difficulty. You could achieve what you want with dicts, with assocs, with lists of pairs.... You say:
Terms T1 and T2 have the same value for property X
Here it is with dicts, like the answer by #User9213:
?- _{a:1, foo:2, bar:3}.a = _{a:2, foo:22, baz:33}.a.
false.
?- _{a:1, foo:2, bar:3}.a = _{a:1, foo:22, baz:33}.a.
true.
In other words, to compare a "property" of two dicts, you just say Dict1.X = Dict2.X. Note that this also works with X a variable:
?- X = a, _{a:1, b:2}.X = _{a:1, b:432432}.X.
X = a.
The same would work with any other option already mentioned: with library(assoc) (just get the values for that key and compare), or even for lists of pairs (just do member(Key-Value, List) and compare values).
Then, you also say,
Terms T1 and T2 are the same
Now you really can just compare dicts. For assocs, I am not certain if two assocs are always the same if they have the same contents, but you can make lists and compare those. And if you keep your lists of pairs sorted on keys, you can just compare, as with dicts.
Finally, you say:
where T1 and T2 have attributes which can be set elsewhere, or can be left unset.
This is ambiguous. If an attribute is unset, just leave it out of the dict/assoc/list. "Set elsewhere" I really don't get.
You need to write some code down and get a feel for how things could be done. Showing your difficulties with a code example will help you get specific and useful answers.

Prolog - Using Bagof

I've been stuck on a past paper question while studying for my exams.
The question is:
https://gyazo.com/ee2fcd88d67068e8cf7d478a98f486a0
I figured I've got to use findall/bagof/setof because I need to collect a set of solutions. Furthermore, setof seems appropriate because the list needs to be presented in descending order.
My solution so far is:
teams(List) :-
setof((Team, A),
(Team^team(Team, _, Wins, Draws, _), A is Wins*3 + Draws*1),
List).
However the problem is I don't quite get the answers all in one list. I'm very likely using Team^ incorrectly. I'd really appreciate pointers on how I can get a list of ordered tuples in terms of points. The output it gives me is:
X = [(queenspark,43)] ? ;
X = [(stirling,26)] ? ;
X = [(clyde,25)] ? ;
X = [(peterhead,35)] ? ;
X = [(rangers,63)] ? ;
Also, it's not really apparent what kind of order, if any it's in, so I'm also lost as to how setof is ordering.
Whats the best way to approach this question using setof?
Thanks.
Firstly, I would suggest to change (Team,A) to a pair representation A-Team with the A being in front since this is the total score of the team and thus the key you want to use for sorting. Then you would like to prefix the variables that should not be in the list with a ^ in front of the query you want to aggregate. See the following example:
?- setof(A-Team, P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1), List).
List = [25-clyde,26-stirling,35-peterhead,43-queenspark,63-rangers]
Since you asked, consider the following query with the pair ordering flipped to Team-A for comparison reasons:
?- setof(Team-A,P^Wins^Draws^L^(team(Team,P,Wins,Draws,L), A is Wins*3 + Draws*1),List).
List = [clyde-25,peterhead-35,queenspark-43,rangers-63,stirling-26]
Now the resulting list is sorted with respect to the teamnames. So A-Team is the opportune choice. You could then use the predicate lists:reverse/2 to reverse the order to a descending list and then define an auxilary predicate pair_second/2 that you can use with apply:maplist/3 to get rid of the leading scores in the pairs:
:- use_module(library(lists)).
:- use_module(library(apply)).
% team(+Name, +Played, +Won, +Drawn, +Lost)
team(clyde,26,7,4,15).
team(peterhead,26,9,8,9).
team(queenspark,24,12,7,5).
team(rangers,26,19,6,1).
team(stirling,25,7,5,13).
pair_second(A-B,B). % 2nd argument is 2nd element of pair
teams(Results) :-
setof(A-Team,
P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1),
List),
reverse(List,RList),
maplist(pair_second,RList,Results). % apply pair_second/2 to RList
If you query the predicate now you get the desired results:
?- teams(T).
T = [rangers,queenspark,peterhead,stirling,clyde]
Concerning your question in the comments: Yes, of course that is possible. You can write a predicate that describes a relation between a list of pairs and a list than only consists of the second element of the pairs. Let's call it pairlist_namelist/2:
pairlist_namelist([],[]).
pairlist_namelist([S-N|SNs],[N|Ns]) :-
pairlist_namelist(SNs,Ns).
Then you can define teams/1 like so:
teams(Results) :-
setof(A-Team,
P^Wins^Draws^L^(team(Team, P, Wins, Draws, L), A is Wins*3 + Draws*1),
List),
reverse(List,RList),
pairlist_namelist(RList,Results).
In this case, besides maplist/3, you don't need pair_second/2 either. Also you don't need to include :- use_module(library(apply)). The example query above yields the same result with this version.

Prolog, finding largest value from a setOf list

I have a predicate which purpose is to print out which country that has the biggest area(one with biggest border = biggest area). This is how my predicate looks like:
/* If I write get_country(X, 'Europe'). then all the countries in Europe
that isn't bordering a sea gets printed out.
However as you can see I am creating a list
with all of the countries and then I want to
take the largest country from all of these
and print that one out. But instead
all of the countries gets printed out
with their length, ex: X = hungary ; 359 (length) ... */
get_country(Country, Region):-
encompasses(Country,Region,_),
not(geo_sea(_,Country,_)),
setof(Length, country_circumference(Country,Length), Cs),
largest(Cs, X),
write(X).
The predicates used within that predicate follows:
country_circumference(Country, X):-
setof(Length, get_border_length(Country, Length), Cs),
sum(Cs, X).
largest([X],X).
largest([X|Xs],R) :-
largest(Xs,Y),
R is max(X,Y).
Can anyone tell me what I am doing wrong here? How do I simply get all of my countries into the list and then traverse through the list to find the one with the biggest border instead of just printing them out one after one as I put them into the list? Thanks in advance.
Prolog defines a natural order of terms. For example, the following are true:
foo(3, z) #< foo(10, x)
bar(2, 9) #< foo(3, 1)
Note the use of the term comparison operator #< versus the numeric comparison <. The predicate, setof/3, will do term comparison.
If you want to find the country that has the longest border, then you can do so by taking advantage of the term comparison and collect like terms in setof/3 that have the item you want to sort by as the first argument. In this case, we'd want the circumference first. In addition, if I'm understanding the intended meaning of your get_country predicate correctly, you need to include the queries that define the countries you want to consider as part of the query in the setof/3:
get_country(Country, Region):-
setof(L-C-R, X^Y^Z^( encompasses(C, R, X),
\+ geo_sea(Y, C, Z),
country_circumference(C, L) ), Cs),
reverse(Cs, HighToLowAreas),
member(_-Country-Region, HighToLowAreas), !.
The member/2 at the end of the predicate clause will find the first element in the list HighToLowAreas that matches _-Country-Region, which will be the first element if Country and Region are initially uninstantiated.
The existential quantifiers X^Y^Z^ are needed to exclude these from being selectors in the query. Using _ won't do that in the context of setof/3. Here, we're using the term form, -(-(X,Y),Z) since it's conveniently written, X-Y-Z. But you could just as well use, foo(X, Y, Z) here. The reverse/2 puts the list Cs in descending order, and we just pick off the Country and Region from the head of that list with, [_-Country-Region].

Resources