GNU Prolog - searching a list of facts - prolog

I must be having a brain fart or something, but i just can't seem to find a solution to this.
If you have a list facts such as:
%country(country, population, capital)
country(sweden, 8823, stockholm).
country(usa, 221000, washington).
country(france, 56000, paris).
country(denmark, 3400, copenhagen).
%city(city, country, population)
city(lund, sweden, 88).
city(new_york, usa, 5000).
city(paris, usa, 1).
city(copenhagen, denmark, 1200).
city(aarhus, denmark, 330).
city(odense, denmark, 120).
city(stockholm, sweden, 350).
city(washington, usa, 3400).
city(paris, france, 2000).
city(marseilles, france, 1000).
I want to find the second largest populated city, which in this case would be washington, usa with 3400 people. How would you be able to do this?
Thanks.

Try this out for size:
second_largest_city(City) :-
findall(Size, city(_, _, Size), Sizes),
sort(Sizes, SortedSizes),
append(_, [Size2, _], SortedSizes),
city(City, _Country, Size2).
Explanation: The findall/3 finds the sizes of all city/3 facts, which are sorted into ascending order by sort/2 with duplicates removed. The call to append/3 pattern matches to partition the sorted list SortedSizes into two parts; a list of any size (_) and a remainder of length two ([Size2, _]) - this binds the variable Size2 to the second-largest city size from city/3 facts. Lastly, all cities with this size are located amongst city/3 facts, and are bound on the output.
Note: This won't work properly in general if your built-in for sort/2 doesn't remove duplicates, because this leaves open the possibility that city/3 facts with more than one equal maximum will return the maximum (largest) only. This implementation using append/3 to seek the second-last element of the sorted list of sizes also assumes sort/2 sorted numbers into ascending order.
Also, lastly, note that this will fail outright if there are less than two city/3 facts -- but this is probably fine, given that the predicate seeks the 'second largest' city, and strictly speaking there wouldn't be one unless there are indeed at least two cities in the DB with different sizes. If this is an issue, you can just write more clauses for second_largest_city/1 to handle such a case.

Slightly shorter version of #sharky's excellent answer:
second_largest_city(Second) :-
setof(Size/City, Country^city(City,Country,Size), Cities),
append(_, [_/Second, _], Cities).
setof combines findall and sort. We collect Size/City pairs so they are sorted on size automatically. The construct X^Goal introduces an existentially quantified variable X (like ∃x in first-order logic).

Related

Prolog project getting started

This is my first time asking a question on here. Sorry if I do something incorrectly.
First, I want to be up front; this is a homework question. I am not looking for anyone to do it for me. I just need help getting started.
The problem is-
Table of data
Using above information, Write a prolog program to answer the following queries.
Please note that you cannot simply answer these queries with only facts, you
should answer then using facts and rules.
A. Which country/countries has/have the largest case number?
B. Which country/countries has/have the smallest number of deaths?
C. Which country has the largest case number or the largest number of deaths?
D. Which country has the largest case number less than 5?
E. How many cases are reported for Austria?
F. How many cases are reported for the Asia?
G. How many cases are reported for the Asia and middle east?
H. Which region has the smallest case number?
I. Which region has the largest number of deaths?
J. Which regions have a smaller case number than Europe?
K. Which country/countries has/have a larger number of deaths than Iran?
L. Which country/countries has/have a larger case number and a larger number
of deaths than Iran?
M. Which country/countries has/have a larger case number or a larger number of
deaths than Iran?
N. What is the average case number in this report?
Ok so. I roughly understand how to complete the rules for how many cases are reported in X. That seems easy. But what I don't understand, is how we can traverse data and compare it in Prolog. In java for example, I would just put all the values in an array and sort it. Making this easy.
My question boils down to this:
How would you store the initial data from the table? (facts)
how would you write the rules to find the largest element?
Example for question A, using swi-prolog:
country_cases_deaths('Jordan', 35, 14).
country_cases_deaths('UK', 5, 3).
country_cases_deaths('Someplace Else', 35, 5).
country_cases_deaths('Austria', 2, 1).
country_max_cases(Country, MaxCases) :-
aggregate_all(
% Want the maximum
max(Cases),
% This is the filter
country_cases_deaths(_, Cases, _),
% Variable to put the max in
MaxCases
),
% Lookup country - could be multiple countries having the same max
country_cases_deaths(Country, MaxCases, _).
Result:
?- country_max_cases(Country, MaxCases).
Country = 'Jordan',
MaxCases = 35 ;
Country = 'Someplace Else',
MaxCases = 35.
?- findall(Country, country_max_cases(Country, _), Countries).
Countries = ['Jordan','Someplace Else'].
Some useful links: aggregate_all (click the orange "show source" icon in the top right, to see sourcecode), discussion
% dt.pl
data(japan,10).
data(usa,20).
readDatas(Datas):- findall(dt(Num,Country),data(Country,Num),Datas).
findMax(Datas,Max):-
sort(Datas,SortedDatas),reverse(SortedDatas,RevDatas),
[Max|_]=RevDatas.
:- readDatas(Datas),findMax(Datas,Max),writeln(Max).
:- halt.
$ apt install swi-prolog
$ swipl dt.pl

How to make a random list of a specific length without duplicates in prolog?

I'm trying to write a predicate randomnames/1 that generates a random list of three different names. The names are in a database and I already have a predicate for one random name:
name(1, Mary).
name(2, Pat).
name(3, James).
name(4, Bob).
name(5, Susan).
random_name(Name):-
random(0, 6, N),
name(N, Name).
To get this in a list, someone suggested I should do:
random_names([A,B,C]) :-
random_name(A),
random_name(B),
random_name(C).
The only problem with this is that it's possible to get duplicates in the list generated. I can't figure out how to fix that issue. I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements? And how would I even write the remove predicate when there isn't a clear head and tail in the random_names predicate?
When programming in Prolog, think in terms of conditions that your solutions must satisfy.
Currently, you have already figured out how to describe a list with three elements, where each element is a random name.
That's a good start, but not yet sufficient: In addition, you now want to describe the condition that the elements are pairwise distinct.
So, think about how to describe a list of three elements where all elements are pairwise distinct.
I give you a start, using dif/2 to express disequality of terms in a sound way (see prolog-dif):
three_distinct_elements([A,B,C]) :-
dif(A, B),
dif(A, C),
dif(B, C).
You may find a more general and more elegant way to describe this for lists with more than 3 elements. However, the above suffices to solve the task at hand.
So, it only remains to combine the predicates you already have, using for example:
three_distinct_random_names(Ls) :-
random_names(Ls),
three_distinct_elements(Ls).
This is simply the conjunction of conditions which you have already implemented. In total, solutions of this predicate will give you what you want: A list with three distinct random names.
However, the predicate may also fail (Exercise: Why?).
To try the predicate until it finds a solution, use for example repeat/0:
?- repeat, three_distinct_random_names(Ls).
There are also better ways to solve this. However, as a first approximation, I recommend to focus on good building-blocks, describing the conditions you want to satisfy.
I have a general comment on what you write:
I could write a new predicate for removing duplicates, but how would I substitute the duplicate with another variable then, so that the list still has three elements?
This is all worded very imperatively: You think here about "removing", "substituting" etc. To get the most out of Prolog, focus on describing the conditions that must hold for the solutions you want to find!
You want to find a list without duplicates? Describe what such a a list must look like. You want random names? Describe what such a name looks like, etc.
In case if you are using Swi-Prolog you can use very handy randseq/3 predicate which comes bundled with Swi. randseq/3 generates list of all distinct random numbers in range from 1 to N. After getting this list generated all that remains is mapping numbers to names:
name(1, 'Mary').
name(2, 'Pat').
name(3, 'James').
name(4, 'Bob').
name(5, 'Susan').
random_names(Names, Count) :-
% 5 is the amount of names available in database
randseq(Count, 5, L),
maplist(name, L, Names).
Usage examples:
?- random_names(Names, 3).
Names = ['Mary', 'James', 'Susan'].
?- random_names(Names, 5).
Names = ['Susan', 'Bob', 'James', 'Mary', 'Pat'].

Check if a country is needed for a critical amount of votes

Hey guys I'm new to prolog and learning it myself.
I found this question on the internet but without any answers.
I have this database
countries([belgium, france, germany, italy, luxembourg, netherlands]).
weight(france, 4).
weight(germany, 4).
weight(italy, 4).
weight(belgium, 2).
weight(netherlands, 2).
weight(luxembourg, 1).
threshold(12).
Now I made this program to see if a list of countries have enough votes to get over the threshold.
winning([], 0).
winning([Head | Tail], N):-
weight(Head, N1),
winning(Tail, N2),
N is N1 + N2.
winning(Y):-
winning(Y, N),
threshold(X),
N >= X.
Now I need to write a program critical/2, first argument a country, second argument a list of countries. Is the first country needed to get over the threshold or not.
example:
?- critical(netherlands, [belgium, france, germany]).
True
?- critical(netherlands, [france, germany, italy]).
False
for this program I need to check first, if the second argument is winning already. And if so it will fail. If not i need to get the value of the first argument add it to the second value and then check if its over the threshold. If it won't be enough it will fail. If it is enough it will succeed.
critical(X,Y):-
winning(Y,N),
weight(X,Value),
N1 is N+Value,
threshold(X),
N1 >= X.
I'm doing alot of things wrong here but I have no idea how to fix it.
You are quite close to the solution. A few hints:
First, a good naming convention helps you keep track of which argument is what:
critical(C, Cs) :-
This makes clear that the first argument is a single country, and the second is a list of zero or more countries.
So, let us first relate Cs to their total weight. Again, it helps to have a good naming convention, making clear which argument is what:
country_weight(france, 4).
country_weight(germany, 4).
country_weight(italy, 4).
country_weight(belgium, 2).
country_weight(netherlands, 2).
country_weight(luxembourg, 1)
Now, to relate multiple countries to their respective weights, we use the meta-predicate maplist/3:
maplist(country_weight, Cs, Ws)
and to sum the weights, we use sum_list/2:
sum_list(Ws, Sum)
It is obvious how to describe that Sum must be below the threshold (left as an exercise).
Finally, to denote that the sum plus the weight of the country denoted by the first argument is greater than the threshould, we use:
country_weight(C, W),
W + Sum > Threshold
This completes the definition. Notice that it was not necessary to describe which countries exist at all. Therefore, you can omit the first predicate of your program.

Find best result without findall and a filter

I'm in a bit of pickle in Prolog.
I have a collection of objects. These objects have a certain dimension, hence weight.
I want to split up these objects in 2 sets (which form the entire set together) in such a way that their difference in total weight is minimal.
The first thing I tried was the following (pseudo-code):
-> findall with predicate createSets(List, set(A, B))
-> iterate over results while
---> calculate weight of both
---> calculate difference
---> loop with current difference and compare to current difference
till end of list of sets
This is pretty straightforward. The issue here is that I have a list of +/- 30 objects. Creating all possible sets causes a stack overflow.
Helper predicates:
sublist([],[]).
sublist(X, [_ | RestY]) :-
sublist(X,RestY).
sublist([Item|RestX], [Item|RestY]) :-
sublist(RestX,RestY).
subtract([], _, []) :-
!.
subtract([Head|Tail],ToSubstractList,Result) :-
memberchk(Head,ToSubstractList),
!,
subtract(Tail, ToSubstractList, Result).
subtract([Head|Tail], ToSubstractList, [Head|ResultTail]) :-
!,
subtract(Tail,ToSubstractList,ResultTail).
generateAllPossibleSubsets(ListToSplit,sets(Sublist,SecondPart)) :-
sublist(Sublist,ListToSplit),
subtract(ListToSplit, Sublist, SecondPart).
These can then be used as follows:
:- findall(Set, generateAllPossibleSubsets(ObjectList,Set), ListOfSets ),
findMinimalDifference(ListOfSets,Set).
So because I think this is a wrong way to do it, I figured I'd try it in an iterative way. This is what I have so far:
totalWeightOfSet([],0).
totalWeightOfSet([Head|RestOfSet],Weight) :-
objectWeight(Head,HeadWeight),
totalWeightOfSet(RestOfSet, RestWeight),
Weight is HeadWeight + RestWeight.
findBestBalancedSet(ListOfObjects,Sets) :-
generateAllPossibleSubsets(ListOfObjects,sets(A,B)),
totalWeightOfSet(A,WeightA),
totalWeightOfSet(B,WeightB),
Temp is WeightA - WeightB,
abs(Temp, Difference),
betterSets(ListOfObjects, Difference, Sets).
betterSets(ListOfObjects,OriginalDifference,sets(A,B)) :-
generateAllPossibleSubsets(ListOfObjects,sets(A,B)),
totalWeightOfSet(A,WeightA),
totalWeightOfSet(B,WeightB),
Temp is WeightA - WeightB,
abs(Temp, Difference),
OriginalDifference > Difference,
!,
betterSets(ListOfObjects, Difference, sets(A, B)).
betterSets(_,Difference,sets(A,B)) :-
write_ln(Difference).
The issue here is that it returns a better result, but it hasn't traversed the entire solution tree. I have a feeling this is a default Prolog scheme I'm missing here.
So basically I want it to tell me "these two sets have the minimal difference".
Edit:
What are the pros and cons of using manual list iteration vs recursion through fail
This is a possible solution (the recursion through fail) except that it can not fail, since that won't return the best set.
I would generate the 30 objects list, sort it descending on weight, then pop objects off the sorted list one by one and put each into one or the other of the two sets, so that I get the minimal difference between the two sets on each step. Each time we add an element to a set, just add together their weights, to keep track of the set's weight. Start with two empty sets, each with a total weight of 0.
It won't be the best partition probably, but might come close to it.
A very straightforward implementation:
pair(A,B,A-B).
near_balanced_partition(L,S1,S2):-
maplist(weight,L,W), %// user-supplied predicate weight(+E,?W).
maplist(pair,W,L,WL),
keysort(WL,SL),
reverse(SL,SLR),
partition(SLR,0,[],0,[],S1,S2).
partition([],_,A,_,B,A,B).
partition([N-E|R],N1,L1,N2,L2,S1,S2):-
( abs(N2-N1-N) < abs(N1-N2-N)
-> N3 is N1+N,
partition(R,N3,[E|L1],N2,L2,S1,S2)
; N3 is N2+N,
partition(R,N1,L1,N3,[E|L2],S1,S2)
).
If you insist on finding the precise answer, you will have to generate all the partitions of your list into two sets. Then while generating, you'd keep the current best.
The most important thing left is to find the way to generate them iteratively.
A given object is either included in the first subset, or the second (you don't mention whether they're all different; let's assume they are). We thus have a 30-bit number that represents the partition. This allows us to enumerate them independently, so our state is minimal. For 30 objects there will be 2^30 ~= 10^9 generated partitions.
exact_partition(L,S1,S2):-
maplist(weight,L,W), %// user-supplied predicate weight(+E,?W).
maplist(pair,W,L,WL),
keysort(WL,SL), %// not necessary here except for the aesthetics
length(L,Len), length(Num,Len), maplist(=(0),Num),
.....
You will have to implement the binary arithmetics to add 1 to Num on each step, and generate the two subsets from SL according to the new Num, possibly in one fused operation. For each freshly generated subset, it's easy to calculate its weight (this calculation too can be fused into the same generating operation):
maplist(pair,Ws,_,Subset1),
sumlist(Ws,Weight1),
.....
This binary number, Num, is all that represents our current position in the search space, together with the unchanging list SL. Thus the search will be iterative, i.e. running in constant space.

How can i tell if an object is unique

i just can't get my head around this problem i'm having with prolog. Only just started, but i can't seem to find a way to find out if an object is unique. Heres my code:
/* (Student Name, Student Number)*/
Student(stuart, 11234).
Student(ross, 11235).
Student(rose, 11236).
Student(stuart, 11237).
how can i find out if a student is unique. Take for example Stuart, there's two students named Stuart, so Stuart is not unique. How could i write a procedure to tell if its another Student called Stuart.
I've tried spending so many hours on this, but i can't seem to get my head around dealing with the original Stuart rather than the other Stuart because i can't exclude the one i'm trying to find out if its unique.
Thanks for the help.
With your database example this could do
unique(S) :-
student(S, N), \+ (student(S, M), M \= N).
as it yields
?- unique(S).
S = ross ;
S = rose ;
false.
Generally, Prolog is targeted toward existence of solutions. Then predication about cardinality need some support from the 'impure' part of the language: nb_setarg it's currently our best friend when we need to efficiently tracking cardinality.
Using a metapredicate like this:
%% count_solutions(+Goal, ?C)
%
% adapted from call_nth/2 for http://stackoverflow.com/a/14280226/874024
%
count_solutions(Goal, C) :-
State = count(0, _), % note the extra argument which remains a variable
( Goal,
arg(1, State, C1),
C2 is C1 + 1,
nb_setarg(1, State, C2),
fail
; arg(1, State, C)
).
:- meta_predicate count_solutions(0, ?).
you could solve the problem without considering the second argument
unique(S) :-
student(S, _), count_solutions(student(S, _), 1).
The same predicate could use aggregate_all(count, student(S,_), 1) from library(aggregate), but such library currently builds a list internally, then you could consider the answer from Peter as easier to implement.
There are probably quite a few ways to solve this problem but I would do it this way:
% a student has a name and a number
student(stuart, 11234).
student(ross, 11235).
student(rose, 11236).
student(stuart, 11237).
This code says "find a list the same length as the number of students with Name" and then "make Count the same as the length of the list":
% for every student name there is an associated count of how many times
% that name appears
number_students(Name, Count) :-
findall(_, student(Name, _), Students),
length(Students, Count).
This predicate will only be true if the number_students is 1:
% a student name is unique (appears once and only once) is the
% number_students count is 1
unique_student(Name) :-
number_students(Name, 1).
Testing:
12 ?- unique_student(ross).
true.
13 ?- unique_student(rose).
true.
14 ?- unique_student(bob).
false.
15 ?- unique_student(stuart).
false.
This is an easy way to solve the problem, but it isn't a great Prolog solution because you cannot say things like "give me a unique student name" and get a list of all the unique names.
Some comments on the code you have. This is not a fact:
Student(Ross).
These are two different facts (in SWI-Prolog, at least):
student(ross).
student('Ross').
In other words, predicate names must start with small letters, and identifiers starting with a capital letters denote variables, not atoms. You can put any character string in single quotes to make it a valid atom.
Now this out of the way, it is not clear what you are aiming at. What are you going to do with your unique student? How do you know the first one is the one you are looking for, and not the second? And why not use the student number for that (at least in your example the two Stuarts seem to have different numbers)?

Resources