Solving a puzzle in Prolog about time constraints - prolog

Stuck on a Prolog problem. I know the answer (because I did it on paper first), but I cannot figure out how to get Prolog to come up with the answer.
Problem:
Bill eats a snack every night, having a different fruit and different
nuts each night. From the statements below, identify what Bill had for
a snack for each weeknight last week.
a) The apple was eaten later in the week than the mango.
b) The banana was eaten later in the week than both the almonds and
peanuts, but earlier in the week than the pear.
c) The cashews were eaten earlier in the week than both the banana and
the apricot, but later in the week than the peanuts.
d) The pecans were not eaten the evening after the almonds.
e) Bill ate walnuts one night.
Note that the problem is about 5 weeknights (Monday through Friday),
and mentions 5 kinds of fruit and 5 kinds of nuts. Your program should
solve the problem and print out the solution, which will be a set of 5
triples like (Monday, apple, pecans), ... (Friday, mango, walnuts).
Clearly, these are not the correct answers, but just values to show
you what the solution will look like.
Code so far:
before_in_week(X, Y, Days) :-
nth1(Xi, Days, X),
nth1(Yi, Days, Y),
Xi < Yi.
print_solve([Head|Tail]) :-
write(Head),
nl,
print_solve(Tail).
solve(A) :-
% all triples
A = [[day1, fruit1, nut1],
[day2, fruit2, nut2],
[day3, fruit3, nut3],
[day4, fruit4, nut4],
[day5, fruit5, nut5]],
Days = [monday, tuesday, wednesday, thursday, friday],
Days = [day1, day2, day3, day4, day5],
Fruits = [apple,banana,pear,mango,apricot],
permutation(Fruits, [fruit1, fruit2, fruit3, fruit4, fruit5]),
Nuts = [almonds,pecans,cashews,peanuts,walnuts],
permutation(Nuts, [nut1, nut2, nut3, nut4, nut5]),
% clue 1 - mango before apple
fruit5 \= mango,
member([C1,mango,_], A),
member([C2,apple,_], A), before_in_week(C1,C2,Days),
% clue 2 - banana after almonds and peanuts, but before pear
fruit5 \= banana,
member([C1,banana,_], A),
member([C2,pear,_], A), before_in_week(C1,C2,Days),
member([C3,_,almonds], A), before_in_week(C3,C1,Days),
member([C4,_,peanuts], A), before_in_week(C4,C1,Days),
% clue 3 - cashews before banana and apricot, but after peanuts
nut5 \= peanuts,
member([C1,_,cashews], A),
member([C2,_,peanuts], A), before_in_week(C1,C2,Days),
member([C3,banana,_], A), before_in_week(C3,C1,Days),
member([C4,apricot,_], A), before_in_week(C4,C1,Days),
% clue 4 - pecans not night after almonds
nut5 \= almonds,
% clue 5 - ate walnuts one night
print_solve(A).

First, there is really no need to print anything manually. Prolog's top level does this for you, if you enter the query solve(A). yet,
second, there is no solution. That is really what you are interested in. There is a very simple and very general method to narrow down the source of failure. Simply generalize away all the goals, one after the other. I like to do this by adding a * in front like so:
:- op(950, fy, *).
*_0.
solve(A) :-
* A = [[day1, fruit1, nut1], [day2, fruit2, nut2], [day3, fruit3, nut3],
[day4, fruit4, nut4], [day5, fruit5, nut5]],
Days = [monday|_/*[tuesday, wednesday, thursday, friday]*/],
Days = [day1|_/*[day2, day3, day4, day5]*/],
* Fruits = [apple,banana,pear,mango,apricot],
* permutation(Fruits, [fruit1, fruit2, fruit3, fruit4, fruit5]),
* Nuts = [almonds,pecans,cashews,peanuts,walnuts],
* permutation(Nuts, [nut1, nut2, nut3, nut4, nut5]),
% clue 1 - mango before apple
* fruit5 \= mango,
* member([C1,mango,_], A),
* member([C2,apple,_], A), before_in_week(C1,C2,Days),
% clue 2 - banana after almonds and peanuts, but before pear
* fruit5 \= banana,
* member([C1,banana,_], A),
* member([C2,pear,_], A), before_in_week(C1,C2,Days),
* member([C3,_,almonds], A), before_in_week(C3,C1,Days),
* member([C4,_,peanuts], A), before_in_week(C4,C1,Days),
% clue 3 - cashews before banana and apricot, but after peanuts
* nut5 \= peanuts,
* member([C1,_,cashews], A),
* member([C2,_,peanuts], A), before_in_week(C1,C2,Days),
* member([C3,banana,_], A), before_in_week(C3,C1,Days),
* member([C4,apricot,_], A), before_in_week(C4,C1,Days),
% clue 4 - pecans not night after almonds
* nut5 \= almonds.
% clue 5 - ate walnuts one night
In this program slice, which is a generalization of your original program, it boils down to the inability to succeed for
Days = [monday|_], Days = [day1|_]
You have to change there something. day1 is a constant, it rather should be a variable.
Later, replace all X \= const by dif(X, const).

Your biggest issue is that you are using atoms (fruit4) but you should use variables (Fruit4). Note the capitalization at the start.
Also, you're doing a permutation that you don't need. Prolog does all of the permutations you need via backtracking. That's what make Prolog such an interesting language.
Try this code:
?- solve(A),print_solve(A).
solve(A) :-
A = [[monday,_,_],[tuesday,_,_],[wednesday,_,_],[thursday,_,_],[friday,_,_]],
%clue 1 - mango before apple
before([_,mango,_],[_,apple,_],A),
% clue 2 - banana after almonds and peanuts, but before pear
before([_,_,almonds],[_,banana,_],A),
before([_,_,peanuts],[_,banana,_],A),
before([_,banana,_],[_,pear,_],A),
% clue 3 - cashews before banana and apricot, but after peanuts
before([_,_,cashews],[_,banana,_],A),
before([_,_,cashews],[_,apricot,_],A),
before([_,_,peanuts],[_,_,cashews],A),
% clue 4 - pecans not night after almonds
append(H,[[_,_,almonds],[_,_,_]|T],A),
(member([_,_,pecans],H);member([_,_,pecans],T)),
% clue 5 - ate walnuts one night
member([_,_,walnuts],A),
true.
print_solve([]).
print_solve([Head|Tail]) :-
write(Head),
nl,
print_solve(Tail).
before(X,Y,Days) :-
append(A,B,Days),
member(X,A),
member(Y,B).
That gives me:
[monday, mango, peanuts]
[tuesday, apple, cashews]
[wednesday, apricot, almonds]
[thursday, banana, walnuts]
[friday, pear, pecans]
Yes.

The puzzle can be easily solved by means of one of workhorses of Prolog: generate-and-test. The key is modelling expressions over domain variables (constraints) making easy to check if they are satisfied.
snacks(Week) :-
% model the problem with domain variables,
% make the symbolic associations explicit
% this is the 'generation phase'
Nuts = [
almonds:Almonds,
cashews:Cashews,
pecans:Pecans,
peanuts:Peanuts,
walnuts:_Walnuts
],
Fruits = [
apple:Apple,
banana:Banana,
pear:Pear,
mango:Mango,
apricot:Apricot
],
% since we are going to use plain arithmetic, assign numbers before attempt to evaluate constraints
assign_days(Nuts),
assign_days(Fruits),
% now the 'application symbols' are bound to integers, then we can
% code actual constraint expressions in a simple way...
% this is the 'test phase'
% a) The apple was eaten later in the week than the mango.
Apple>Mango,
% b) The banana was eaten later in the week than both the almonds and peanuts,
% but earlier in the week than the pear.
Banana>Almonds,Banana>Peanuts,Banana<Pear,
% c) The cashews were eaten earlier in the week than both the banana and the apricot,
% but later in the week than the peanuts.
Cashews<Banana,Cashews<Apricot,Cashews>Peanuts,
% d) The pecans were not eaten the evening after the almonds.
Pecans=\=Almonds+1,
% e) Bill ate walnuts one night.
% no constraints, just existance
% when we get here, domain variables satisfy the constraints
% just format the workspace in easy to read list
findall((Day,Fruit,Nut),(
nth1(NDay,['Monday','Tuesday','Wednesday','Thursday','Friday'],Day),
memberchk(Fruit:NDay,Fruits),
memberchk(Nut:NDay,Nuts)
),Week).
assign_days(Snacks) :-
numlist(1,5,Nums),
permutation(Nums,Perm),
maplist([Day,_:Day]>>true,Perm,Snacks).

Related

Solving a puzzle with Prolog, throws error "Arguments are not sufficiently instantiated"

I'm trying to solve the following problem using logical constraints:
The packer has to place 5 crates onto a long lorry. The 5 crates
contain chickens, barley, foxes, rat poison and wheat. The crates need
to be arranged in a long line without any gaps between them so that:
• the chickens are separated from the foxes;
• the rat poison is not next to the barley;
• the rat poison is not next to the wheat.
Find out
how may different ways there are of arranging these crates subject to
these packing constraints.
This is what I have so far:
:- use_module(library(clpfd)).
position(Crates) :-
Crates = [Chicken, Barley, Foxes, RatPoison, Wheat],
Regions ins 1..5,
Chicken #\= Foxes,
RatPoison #\= Barley,
RatPoison #\= Wheat,
labeling([], Regions).
It throws the error "Arguments are not sufficiently instantiated" when I try to run it.
I'm very new to Prolog so any help would be appreciated.
Firstly you are restricting Regions to 1..5 and than labeling it. But you want to know the possible positions for the 5 crates. So restrict and label Crates. Note that Regions is a free variable when you restrict it to values between 1 and 5 and the length of the list Regions is not restricted at all, thus the error when you try to label it. In this version, in the last goal of the predicate position/1, the list Crates is already restricted to a fixed length (=5) and to values between 1 and 5 when being labeled.
Then you want the chicken and the foxes to not be in the same crate: Chicken #\= Foxes. But according to the task description they are in different crates anyway. You rather want them to be not in adjacent crates. The same goes for ratpoison/barley and ratpoison/wheat. Also no two crates can be in the same position: you can use all_distinct/1 form library(clpfd) for that. Putting this all together you get something like:
:- use_module(library(clpfd)).
position(Crates) :-
Crates = [Chicken, Barley, Foxes, RatPoison, Wheat],
Crates ins 1..5,
all_distinct(Crates),
not_adjacent(Chicken,Foxes),
not_adjacent(RatPoison,Barley),
not_adjacent(RatPoison,Wheat),
labeling([], Crates).
not_adjacent(X,Y) :-
X #\= Y+1,
Y #\= X+1.
Now try to query position/1:
?- position(Crates).
Crates = [1,2,4,5,3] ? ;
Crates = [1,3,4,5,2] ? ;
Crates = [1,4,3,2,5] ?
...
If you don't want to go through all solutions interactively you can use findall/3 and length/2 to show all solutions and to count them:
?- findall(Crates,position(Crates),L),length(L,X).
L = [[1,2,4,5,3],[1,3,4,5,2],[1,4,3,2,5],[1,5,3,2,4],[2,1,4,3,5],[2,1,4,5,3],[2,3,4,1,5],[2,3,4,5,1],[2,3,5,1,4],[2,4,5,1,3],[2,5,4,1,3],[2,5,4,3,1],[3,1,5,4,2],[3,2,5,4,1],[3,4,1,2,5],[3,5,1,2,4],[4,1,2,3,5],[4,1,2,5,3],[4,2,1,5,3],[4,3,1,5,2],[4,3,2,1,5],[4,3,2,5,1],[4,5,2,1,3],[4,5,2,3,1],[5,1,3,4,2],[5,2,3,4,1],[5,3,2,1,4],[5,4,2,1,3]],
X = 28
My model gives different result WRT #tas answer. Maybe I don't fully understand the phrase
the chickens are separated from the foxes
that I translate like
abs(Chicken - Foxes) #> 2
Anyway, the full model
position(Crates) :-
Crates = [Chicken, Barley, Foxes, RatPoison, Wheat],
all_different(Crates),
Crates ins 1..5,
abs(Chicken - Foxes) #> 2,
abs(RatPoison - Barley) #> 1,
abs(RatPoison - Wheat) #> 1,
label(Crates).
yields
?- aggregate(count,Cs^position(Cs),N).
N = 8.

Limiting Prolog solutions based on rules

I have a Prolog program that says whether people like various types of fruit:
likes(alice,apple).
likes(bob,peach).
likes(bob,pear).
There are a number of these types of fruit available:
count(apple,1).
count(peach,2).
count(pear,6).
A user can each fruit if they like the fruit and there is enough of their liked fruit available:
can_eat(Person,Fruit) :- likes(Person, Fruit),
count(Fruit,N),
N > 0.
So you can do:
?- can_eat(X,Y).
X = bob,
Y = apple ; <-- single apple
X = bob,
Y = peach ;
X = alice,
Y = apple. <-- single apple, again
or
?- can_eat(alice,X).
X = apple. <-- Alice has apple
?- can_eat(bob,X).
X = apple ; <-- Bob has apple
X = peach.
Both Alice and Bob are allowed the apple but there is only one apple. How can I tell Prolog that only Alice or Bob are allowed the apple, not both. I think I need a way of keeping track of the number of apples that are left depending on the solution Prolog is giving me.
You can represent the set of available fruits and make relations update it according to the actions performed by people. Something along can_eat(Person, Fruit, Env) where Env contains (Fruit, Count) couples, for example.
An updated environment New_Env is based on an old one where for some old (Fruit, X), there is a (Fruit, Y) item where Y is X - 1. You could represent this relation with clpfd, Y #= X - 1, and Y #>= 0.
First, you should post your exact database, since right now bob doesn't like apples. So we get
?- setof(F-P, can_eat(F,P), L).
L = [alice-apple, bob-peach, bob-pear].
Anyway, as long as can_eat/2 arguments are atomic, there is no way to solve your problem. The required interpretation implies a change of state, an assignment.
For instance
can_eat(A) :-
findall(F-N, count(F, N), L),
assign(L, A).
assign(L, [P-Fruit|As]) :-
likes(P, Fruit),
select(Fruit-N, L, R),
N > 0, M is N-1,
assign([Fruit-M|R], As).
assign(_, []).
that yields a long solution list,exactly
?- aggregate(count,A^can_eat(A),N).
N = 945.

Prolog riddle solving

The statement :
Four couples in all
Attended a costume ball.
2
The lady dressed as a cat
Arrived with her husband Matt.
3
Two couples were already there,
One man dressed like a bear.
4
First to arrive wasn't Vince,
But he got there before the Prince.
5
The witch (not Sue) is married to Chuck,
Who was dressed as Donald Duck.
6
Mary came in after Lou,
Both were there before Sue.
7
The Gipsy arrived before Ann,
Neither is wed to Batman.
8
If Snow White arrived after Tess,
Then how was each couple dressed?
My code is here , but it returns false :
sol(S):-
S=[[1,L1,M1,LD1,MD1],
[2,L2,M2,LD2,MD2],
[3,L3,M3,LD3,MD3],
[4,L4,M4,LD4,MD4]],
member([_,_,matt,cat,_],S),
member([ALR,_,_,_,bear],S),
(ALR =:= 1 ; ALR =:= 2),
not(member([1,_,vince,_,_],S)),
member([VN,_,vince,_,_],S),
member([PS,_,_,_,prince],S),
VN < PS ,
member([_,_,chuck,witch,donald],S),
not(member([_,sue,_,witch,_],S)),
member([MRY,mary,_,_,_],S),
member([LOU,_,lou,_,_],S),
member([SUE,sue,_,_,_],S),
MRY > LOU,
MRY < SUE,
member([GPS,_,_,gipsy,_],S),
member([ANN,ann,_,_,_],S),
GPS < ANN ,
not(member([_,_,_,gipsy,batman],S)),
not(member([_,ann,_,_,batman],S)),
member([SW,_,_,snowwhite,_],S),
member([TS,tess,_,_,_],S),
SW > TS ,
perm([sue,mary,ann,tess],[L1,L2,L3,L4]),
perm([matt,lou,vince,chuck],[M1,M2,M3,M4]),
perm([cat,witch,gipsy,snowwhite],[LD1,LD2,LD3,LD4]),
perm([donald,prince,batman,bear],[MD1,MD2,MD3,MD4]).
takeout(X,[X|R],R).
takeout(X,[F|R],[F|S]) :- takeout(X,R,S).
perm([],[]).
perm([X|Y],Z) :- perm(Y,W), takeout(X,Z,W).
Any solution ?
You should move all your not(...) goals to the very end of the predicate.
not(G) means, "G is impossible to satisfy right now". When tried too early, with many still non-instantiated variables in the lists, it is in fact very often possible to satisfy a goal, and the whole not(...) call will fail right away.
Alternatively, delay the checking of the inequality on a variable until it is instantiated, e.g. in SWI Prolog with freeze/2 (as seen e.g. in this answer).

Strange warning and computation result in constraint logic program

First, sorry for posting the whole program, but as I don't know were the problem is I don't know which parts are irrelevant. These are two slightly different implementations of the same logic puzzle in SWI-Prolog, the first one succeeds the second one fails and I can't find the reason for the failure.
The puzzle:
4 persons are having a diner:
Donna, Doreen, David, Danny
the woman (Donna,Doreen) are sitting vis-a-vis.
the men (David,Danny) are sitting vis-a-vis.
Each of them picked a unique meal and beverage.
1) Doreen sits next to the person that ordered risotto.
2) the salad came with a coke.
3) the person with the lasagna sits vis-a-vis the person with the milk.
4) david never drinks coffee.
5) donna only drinks water.
6) danny had no appetite for risotto.
who ordered the pizza?
I choose the following approach
table with positions:
1
4 O 2
3
domain: positions{1,2,3,4}
variables: persons, meals, beverages
First the inefficient succeeding implementation:
solution(Pizza, Doreen, Donna, David, Danny) :-
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk),
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
next_to(Doreen,Risotto),
Salad = Coke,
vis_a_vis(Lasagna,Milk),
\+ David = Coffee,
Donna = Water,
\+ Danny = Risotto.
unique(X1,X2,X3,X4) :-
pos(X1),
pos(X2),
\+ X1 = X2,
pos(X3),
\+ X1 = X3, \+ X2 = X3,
pos(X4),
\+ X1 = X4, \+ X2 = X4, \+ X3 = X4.
right(1,2).
right(2,3).
right(3,4).
right(4,1).
vis_a_vis(1,3).
vis_a_vis(3,1).
vis_a_vis(2,4).
vis_a_vis(4,2).
next_to(X,Y) :- right(X,Y).
next_to(X,Y) :- right(Y,X).
pos(1).
pos(2).
pos(3).
pos(4).
This works and gives the right result. But when I try to reorder the clauses of the solution procedure to be more efficient (this is the second implementation)
solution(Pizza, Doreen, Donna, David, Danny) :-
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
Salad = Coke,
vis_a_vis(Lasagna,Milk),
\+ David = Coffee,
Donna = Water,
\+ Danny = Risotto,
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk).
%% all other predicates are like the ones in the first implementation
I get a unassigned variable warning when trying to load the file:
Warning: /home/pizza.pl:28:
Singleton variable in \+: Coffee
and the computation returns false. But shouldn't it return the same result?
I see no reason for the difference...
the warning is due to the fact that Coffe and Risotto are unbound when the negation is executed. If you replace \+ David = Coffee, by David \= Coffee, you will avoid the warning, but the solution cannot will not be computed. Should be clear indeed that since Coffee is unbound, David \= Coffee will always fail. You can use dif/2, the solution will work and will be more efficient. I've named solution1/2 your first snippet, and solution2/5 this one (using dif/2):
solution2(Pizza, Doreen, Donna, David, Danny) :-
% general setting
vis_a_vis(Donna,Doreen),
vis_a_vis(David,Danny),
% the six constraints
next_to(Doreen,Risotto), % note: you forgot this one
Salad = Coke,
vis_a_vis(Lasagna,Milk),
dif(David, Coffee),
Donna = Water,
dif(Danny, Risotto),
% assignment of unique positions to the variables
unique(Doreen,Donna,David,Danny),
unique(Lasagna,Pizza,Risotto,Salad),
unique(Water,Coke,Coffee,Milk).
a small test:
?- time(aggregate_all(count,solution1(P,A,B,C,D),N)).
% 380,475 inferences, 0.058 CPU in 0.058 seconds (100% CPU, 6564298 Lips)
N = 8.
?- time(aggregate_all(count,solution2(P,A,B,C,D),N)).
% 10,626 inferences, 0.002 CPU in 0.002 seconds (100% CPU, 4738996 Lips)
N = 8.

Prolog oldest Successor

I'm working on a simple program that has a database of people. When given a year it should state the "King" of that year, where the king is the eldest living man.
For simplicity, all people in the database are eligible as long as they are alive at the given year and I'm assuming there are no twins.
My problem is picking the "oldest" person alive during a given year. I can't seem to figure out how to ask Prolog to examine all possible kings and pick the oldest.
male(jack).
male(roy).
male(ele).
born(jack,2000).
born(dave,1999).
born(roy,1980).
born(ele,1990).
died(jack, 2100).
died(dave, 2099).
died(roy, 1990).
died(ele, 1999).
% compare X against all other possibleSuccessors and make sure he was born 1st.
eldest(X,Year):-
born(X,T1),
born((possibleSuccessor(Year,_)),T2),
T1 < T2.
% must be male and have been born before or during the given year and must not be dead.
possibleSuccessor(Year, X):-
male(X),
born(X,B),
died(X,D),
(B =< Year),
(D >= Year).
successor(Year):-
possibleSuccessor(Year,X),
eldest(X,Year),
write(X).
Any help on comparing all possible answers vs one another would be appreciated. I attempted to use findall before but was unsuccessful.
Prolog offers a restricted form of negation (negation by failure) that solve your problem:
eldest(X,Year):-
born(X,Year),
\+((born(_,T), T<Year)).
this says that X is eldest if we can't find any other born before him.
alternatively, setof/3 can be used:
eldest(X,Year):-
setof((Y,K), born(K,Y), [(Year,X)|_]).
this works sorting all pairs (Y,K), then we can pick just the head of the result.
edit this should solve the problem, but I've introduced a service predicate
eldest(X, Year):-
alive(X, Year, B),
\+((alive(_, Year, T), T<B)).
alive(X, Year, B) :- born(X, B), B =< Year, \+ (died(X, D), D < Year).
% must be male and have been born before or durring the given year and must not be dead.
possibleSuccessor(Year,X):-
male(X),
alive(X, Year, _).
successor(Year):-
possibleSuccessor(Year,X),
eldest(X,Year),
write(X).

Resources