Prolog, how to order a list with constraints - prolog

I am quite new with Prolog and I am trying to use it to order a list with defined constraints.
The problem starts with the following definitions:
An Item is length 3 list: [Name, Type, Weight].
An Content is a list of Items [item_0,.....item_n].
A ContentList is a list of Content
For example:
With the items:
item_1 = [chicken, meat, 1.0]
item_2 = [eggplant, vegetable, 0.5]
item_3 = [tomatoes, vegetable, 1.0]
item_4 = [bread, other, 0.2]
We build two Contents:
contentA = [item_2, item_3]
contentB = [item_1, item_4]
contentC = [item_3, item_4]
So now, let's say we have some content defintions:
hasItemType([_, XType, _], XType).
hasListItemType([H|T], Type) :-
hasItemType(H, Type);
hasListItemType(T, Type).
hasListOnlyItemType([H|T], Type) :-
hasItemType(H, Type),
hasListItemType(T, Type)
isVegetarian(Content) :- hasListOnlyItemType(Content, vegetable).
hasVegetables(Content) :- hasListItemType(Content, vegetable).
hasMeat(Content) :- hasListItemType(Content, meat).
The goal would be:
Given a list of Content returns a ContentList that matches the best a defined order:
For example (but thus my question i'm not sure if it is the right way of doing it.)
-> `order(A, B)` A is before B in the output list.
order(ContentA, ContentB) :-
isVegetarian(ContentA),
hasVegetables(ContentB).
order(ContentA, ContentB) :-
hasVegetables(ContentA),
hasMeat(ContentB).
Ideally, I would like something like that:
solve([contentB, contentC, contentA]) to would return [contentA, contentB, contentC]
because:
order(contentA, contentB), order(contentA, contentC), order(contentB, contentC)
So I have basically two questions:
Is it a reasonable way to formalise my problem.
Once the order and constraints are correctly defined, what would be the way to write a solver?
I understand my question is a bit wide, so I will take any suggestions, references, ideas ;)
Thanks in advance if you read this!

What you need is a sorting function but not sorting with standard comparing functions-predicates like =< or >= but with using your order predicate. So you need to implement a sorting algorithm in Prolog, for example insertion sort:
insertionSort([], []).
insertionSort([HEAD|TAIL], RESULT) :-
insertionSort(TAIL, LIST), insertInPlace(HEAD, LIST, RESULT).
insertInPlace(ELEMENT, [], [ELEMENT]).
insertInPlace(ELEMENT, [HEAD|TAIL], [ELEMENT|LIST]) :-
order(ELEMENT,HEAD), insertInPlace(HEAD, TAIL, LIST).
insertInPlace(ELEMENT, [HEAD|TAIL], [HEAD|LIST]) :-
\+order(ELEMENT ,HEAD), insertInPlace(ELEMENT, TAIL, LIST).
You could also implement mergesort which is more efficient. Since I have no data I can't see if the above is really working or if it has some bugs so I'm waiting for comments...
I think it works I tested it by querying:
insertionSort([[[chicken, meat, 1.0],[beaf, meat, 1.0]],[[eggplant, vegetable, 0.5],[tomatoes, vegetable, 1.0]]],Result).
where I gave a list [content1,cntent2] where content1 had type meat and content 2 had type vegetable so according to order predicate the output should be [content2,content1] so the output I think is right:
?- insertionSort([[[chicken, meat, 1.0],[beaf, meat, 1.0]],[[eggplant, vegetable, 0.5],[tomatoes, vegetable, 1.0]]],Result).
Result = [[[eggplant, vegetable, 0.5], [tomatoes, vegetable, 1.0]], [[chicken, meat, 1.0], [beaf, meat, 1.0]]] ;
false.

Related

Prolog: count number of predicates relating to an atom

I have a set of facts in Prolog, such that:
likes(alice, burger).
likes(bob, burger).
likes(charlie, burger).
likes(bob, sandwich).
likes(charlie, sandwich).
likes(alice, muffin).
I want to know what's the most popular dish (burgers in the example above). I'm not interested in the 2nd, 3rd, etc most popular for this query as the list of preferences can be really long.
I'm struggling to write this query in SWI-Prolog. Can you help me?
most_popular(Popular) :-
findall(X, likes(_, X), Xs),
sort(0, #=<, Xs, Items),
clumped(Items, Counts),
sort(2, #>=, Counts, [Popular|_]).
findall/3 pulls them out of the Prolog database into a list.
clumped/2 really does run length encoding, but if they are sorted first that is the same as counting them.
then sort them by the count (sort/4 first parameter is which part of the term to sort by. Item-Count is really the compound -(Item,Count) so 0 sorts with normal term sorting, 1 gets the Item name to sort by or 2 gets the Count to sort by) and unify the first element of the resulting list as the answer.
?- most_popular(Item-Count).
Count = 3,
Item = burger

PROLOG store nth element of list if string is found in list

What im trying to do is:
fromHistory/2
fromHistory(HL,FL)
FL is the 3rd element of the list if the list contains the word "ate"
FL is the 4th element of the list if the list contains all the words ["you","can","have"]
The predicate is supposed to loop on a list of lists HL and if one of the lists inside contains the words above, it should append the 3rd/4th element depending on the word found to FL, else it shouldn't get anything.
?- fromHistory([[i,ate,x], [you,can,have,y]], FL).
FL = [x, y] ;
false.
?- fromHistory([[this,is,a,useless,input], [i,ate,x], [another,input],
[another,useless,input], ["Ok"], [you,can,have,y]], FL).
FL = [x, y] ;
false.
x and y are not always at the end of the list but are the strings after ["ate"] and ["you","can","have"]
my attempt using the find version in here
find(X,Y,[X,Y|Tail]):-
!.
find(X,Y,[_|Tail]):-
find(X,Y,Tail).
foodFromHistory(HL1, FL):-
flatten(HL1, HL),
find(ate, FL1, HL),
find([you, can, have], FL2, HL),
FL = [FL1|FL2].
However it doesnt work with [you,can,have] and returns false, it also doesn't work on the entire list but rather on the first occurrence only.
As a rule of thumb, if you need to process a list of some things element by element, first get a very clear idea of what to do for every single element (in this case, these "elements" are input phrases) and implement and test that without worrying about the whole problem yet. So:
FL is the 3rd element of the list if the list contains the word "ate"
FL is the 4th element of the list if the list contains all the words ["you","can","have"]
This isn't a very good specification, but here is one implementation you can test and tweak in isolation from the bigger problem:
input_food([_Somebody, ate, Food], Food).
input_food(Input, Food) :-
append(_Something, [you, can, have, Food | _Rest], Input).
That is all! You have two requirements, each describing a simple pattern match on a list. The Prolog implementation can therefore be two clauses, each implementing a simple pattern match on a list.
Let's test:
?- input_food([i, ate, x], Food).
Food = x ;
false.
?- input_food([you, ate, x], Food).
Food = x ;
false.
?- input_food([ok, you, can, have, strawberries], Food).
Food = strawberries ;
false.
?- input_food([this, sentence, no, food], Food).
false.
OK, all we need to do now is to iterate over the input list and collect the foods given by input_food/2 for each input, if any. This is standard:
inputs_foods([], []).
inputs_foods([I|Is], [Food|Fs]) :-
input_food(I, Food),
inputs_foods(Is, Fs).
inputs_foods([I|Is], Fs) :-
\+ input_food(I, _Food),
inputs_foods(Is, Fs).
And it seems to mostly do what you want:
?- inputs_foods([[this,is,a,useless,input], [i,ate,x], [another,input],
[another,useless,input], ["Ok"], [you,can,have,y]], FL).
FL = [x, y] ;
false.
I dont completely understand how the prediacte should work, what about array like [some,input,i,ate,x,some,other,input], should it append x to the list ?
You could try with making your own lists like H1 = [i,ate,X|_], and H2 = [you,can,have,Y|_], then just recursively going through members of HL and comparing them and getting your solutions unifying them with X or Y.
edit :
I've made something for the [i,ate,x], now you have to make similar approach to [you,can,have,y].
The approach is to check if the list [i,ate,X] is sublist of current member of HL, if it is we can add X to our set of FL. Check if this is what you were expecting :)
fromHistory(HL,FL) :-
findAnswers(HL,[],FL).
findAnswers([],Answers,Answers).
findAnswers([H|HL],FL,Answers) :-
(isSublist([i,ate,X],H) -> append(FL,[X],FL2); FL2 = FL),
findAnswers(HL,FL2,Answers).
isSublist(SL, L) :-
append([_,SL,_],L).
A solution for your problem could be this:
solve(L,FL,FLO):-
member("ate",L),
\+consequent(L),
nth1(3,L,E),
append(FL,[E],FLO).
solve(L,FL,FLO):-
\+member("ate",L),
consequent(L),
nth1(4,L,E),
append(FL,[E],FLO).
consequent(L):-
nth1(P,L,"you"),
P1 is P+1,
nth1(P1,L,"can"),
P2 is P+2,
nth1(P2,L,"have").
fromHistory([],L,L).
fromHistory([H|T],L,FL):-
solve(H,L,FLO),
fromHistory(T,FLO,FL).
So first you check if ate is into the list and you can have is not into it. Then you can find the element you want with nth/3 and append it to the list with append/3. Similar case when you find you can have into the list and ate is not into it. You have to decide what to do when you have both ate and you can have into the list. In this implementation the predicate fails.
Query:
?- fromHistory([["you","can","have","hallo","at"],["ate","str1","str2"]],["test","aa"],L).
L = ["test", "aa", "hallo", "str2"]

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.

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