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)).
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.
This is the knowledge base that is being used.
localLib('AHorowitz', 'Stolen Gods', 2011, 'Scorpia Rising', 448, 4).
localLib('AHorowitz', 'Night Visitors', 2000, 'Stormbreaker', 240, 2).
localLib('AHorowitz', 'Matador', 2003, 'Eagle Strike', 340, 6).
localLib('AJohnston', 'Night Visitors', 2000, 'Stormbreaker', 240, 2).
localLib('AJohnston'’, 'Matador', 2003, 'Eagle Strike', 340, 6).
localLib('RMuchamore', 'Basic Training', 2007, 'The Recruit', 342, 3).
localLib('MHaddon', '11.', 2004, 'The Curious Incident Of The Dog In The Night Time', 226, 5).
The key for the KB is as follows:
localLib(w, e, y, t, n, c) where
w = writer’s name
e = excerpt in text
y = year of text
t = title of text
n = no. of pages in excerpt
c = no. of acknowledgements
I have to write a rule to find what is the number of pages for a single longest excerpt that a given writer has published?
Though I understand the question and what the final output should look like I am having difficulty translating it into Prolog language.
The code I have written below is the start of the rule as I am stuck:
longestexcerpt(W, E, N):- localLib(W,E,_,_,N,_), sort(N,X).
Effectively I understand that we have to make Prolog check each no of pages in excerpt against one another therefore it is like a sorting algorithm however the sort function we have learned in lectures so far only involve sorting number in lists. How would I make Prolog check each excerpt length by the same author, say 'AHorowitz', then make it display the highest one, in this case n=448 (as Stolen Gods is the longest number of pages out of all Horowitz texts).
Help and guidance how to approach these kinds of problems would be really useful!
The other solution is also fine, but you can also do it like this:
% The longest excerpt for an author
longest_excerpt(W, E, N) :-
localLib(W, E, _, _, N, _),
\+ (localLib(W, _, _, _, N1, _),
N < N1).
This reads as follows: "There is a writer W with and excerpt E with length N, and there is not an excerpt from the same writer with a greater length".
\+ here is the negation: read it as, "succeeds when the goal fails." Here, the goal is a conjunction.
From the top level:
?- longest_excerpt(W, E, N).
W = 'AHorowitz',
E = 'Stolen Gods',
N = 448 ;
W = 'AJohnston',
E = 'Matador',
N = 340 ;
W = 'RMuchamore',
E = 'Basic Training',
N = 342 ;
W = 'MHaddon',
E = '11.',
N = 226.
There is nothing wrong with using setof/3, of course.
As for the solution from #lurker, it seems better, even if a bit less "declarative". I would have written it as:
longest_excerpt_1(W, E, N) :-
setof(N0-E0, Y^T^C^localLib(W, E0, _, _, N0, _), R),
last(N-E, R).
As you may have already discovered, here's a predicate which simply will be true for any association of an writer, excerpt, and page count:
longestexcerpt(W, E, N) :- localLib(W,E,_,_,N,_).
If you wanted to collect all of the solutions in a list for a given writer, you could do this:
writer_excerpts(Writer, ExcerptList) :-
setof( E-N, Y^T^C^localLib(Writer, E, Y, T, N, C), ExcerptList ).
The existential quantifiers, Y^T^C^ indicate that we don't want these values in the results. OK, that's great. Now we have the entire list of excerpts (in ExcerptList) for Writer, and setof/3 will sort each element, E-N, in a "natural order" (which will be collated by the term E). That is, ExcerptList will be a list of elements that look like 'Stolen Gods'-448, etc. A sample output looks like this:
| ?- writer_excerpts('AHorowitz', E).
E = ['Matador'-340,'Night Visitors'-240,'Stolen Gods'-448]
yes
Since you want the largest number of pages, you really want them ordered by decreasing page count. So you can swap this around as N-E for the list elements, which gives the order by increasing page count, and then reverse the list:
writer_excerpts(Writer, ExcerptList) :-
setof( N-E, Y^T^C^localLib(Writer, E, Y, T, N, C), EList),
reverse(EList, ExcerptList).
This yields:
| ?- writer_excerpts('AHorowitz', E).
E = [448-'Stolen Gods',340-'Matador',240-'Night Visitors']
yes
And finally, you only need to pick off the first element of the result of this predicate:
writers_most_excerpt_pages(Writer, Excerpt, Pages) :-
setof( N-E, Y^T^C^localLib(Writer, E, Y, T, N, C), EList),
reverse(EList, [Pages-Excerpt|_]).
Here, we are unifying the sorted list inline with [Pages-Excerpt|_] since we only care about the Pages-Excerpt info for the first element. We don't care about the tail (rest) of the list, so we just use _.