[comp.lang.prolog] PROLOG Digest V5 #29

PROLOG-REQUEST@SUSHI.STANFORD.EDU (Chuck Restivo, The Moderator) (04/21/87)

PROLOG Digest            Tuesday, 21 Apr 1987      Volume 5 : Issue 29

Today's Topics:
                          Puzzle - Christmas
----------------------------------------------------------------------

Date: 15 Apr 87 14:36:47 GMT
From: Goeran Baage <mcvax!enea!erix!goran@seismo.css.gov>  
Subject: Xmas puzzle solution

Here is a solution to the Xmas shoppers problem posted a year or more
back. The problem text is also included.

Some comments about the solution:

The execution time needed to solve the problem is very dependent on
the order in which the clues are given. The clues have been separated
into three classes:

 - Simple clues giving information that can be entered straight into
   the choosen representation.
 - Important clues, involving many couples. Using these clues early
   will hopefully reduce the search space significally.
 - Other clues.

Inside these classes the clues are stated in the order they were given
in the text. By rearranging the clues and some goals in clues the
execution time can be drastically reduced. The program below used 82
cpusec on a VAX750, which was reduced to 1.5 cpusec by some
rearranging (almost no backtracking over clues).

An interesting observation is that the text of the solution is
less than twice that of the problem text, 5260 vs 2980 characters
for resonably filtered versions of the solution and problem text.

The generate and test method used in the solution below is very much
adapded to Prolog. Another method that seemed natural to me when I
solved it by hand (took an hour or two) was to repeatedly scan the
clues extracting information that could be safely entered into the
solution, and always apply the constraints on purchases and items. If
I remember correctly almost no backtracking was needed.  I guess a
similar method could be used in a program to solve the problem more
efficiently.

The solution below is rather straightforward, the only part that is
somewhat complicated is constraints checking. That part can be
slightly simplified by using a global variable (using something like
assert or record) instead of an extra parameter (F in check_ and fill_
predicates below), but this is costly in execution time. Using assert/
retract increased execution time for the naive version from 82 to 106
cpusec and the clue-optimised version from 1.5 to 3.4 cpusec.  The
transpose predicate, used to simplify constraints checking, uses
difference lists to transpose in O(N**2) time. That is off course not
important here, a naive O(N**3) time algorithm would do, as the matrix
is transposed once.

-- Goran Boge
   Ericsson Telecom
   S-126 25 STOCKHOLM

-----------------------------------------------------------------------
Text of Puzzle:

Christmas was one short month away, and Filene's was crowded with
holiday shoppers.  The throngs moved quickly, however, and the vivid
decorations and festivities added a cheerful air.  Among the shoppers
on one snowy Saturday morning were members of December 25, a Christmas
shoppers' club of 12 married couples.  A Sales Associate waited on all
12 couples consecutively, as they bought a total of 8 each of the
following items:

        1. Aris Gloves
        2. Airplane Book
        3. COCO Perfume
        4. Pearl Strands
        5. A Football Sweater
        6. A Handbag

Each husband and wife were waited on together.  Each couple bought 4
items.  No two couples bought the same combination of items, and none
of the couples bought two or more of the same item.  Using the
following clues, can you determine:

        1. The full name of each husband and wife.
        2. What order they were waited on.
        3. What items each couple bought.

CLUES:

Hint: One husband is Bob, one wife is Elizabeth, and one surname is
Stanton.

1. The Craigs, who bought a handbag, were waited on before the
Murphys, who were not waited on last.

2. The Collins bought Aris gloves, a sweater, and handbag, and COCO.

3. The couples waited on 8th and 10th bought the Airplane Book.

4. These five couples were waited on consecutively:  the Smiths; Gary
and his wife; a couple who bought the Airplane Book and a handbag; the
Swains; and Bill and his wife.

5. Geraldine and her husband did not buy either a handbag or a
sweater.

6. The couple who were waited on last did not buy pearls.

7. One of the items Tom and his wife bought was the Airplane Book.

8. The Marshalls did not buy COCO or pearls.

9. Evelyn and her husband bought Aris gloves but not COCO.

10. These five couples were waited on consecutively:  Martha and her
husband; Jack and his wife; the couple who bought Aris gloves, COCO,
the Airplane Book, and a handbag; the couple who did not buy either
pearls or the Airplane Book; and Margaret and her husband.

11. The first five couples waited on all bought COCO.

12. Chuck and his wife did not buy Aris gloves.

13. The couples waited on first, second, and fourth did not buy
a sweater.

14. Eleanor and her husband did not buy COCO.

15. Neither Allen and his wife, who did not buy a handbag, nor
the Anthonys bought Aris gloves.

16. Cheryl and her husband, who were not waited on 10th or 12th, and
John and his wife are two couples who bought both a sweater and a
handbag.

17. The Douglases, who did not buy Aris gloves or a sweater, were
waited on 9th.

18. Adam and his wife, who did not buy a handbag, were waited on
immediately before the Days.

19. Steve and his wife bought pearls, the Airplane Book, a sweater,
and one other item.

20. The last three couples waited on did not buy Aris gloves.

21. The Joneses did not buy a sweater.

22. Susan and her husband bought pearls.

23. George and his wife bought a sweater.

24. The four couples who did not buy Aris gloves are (in no particular
order): Dorothy and her husband; the Craigs; Joe and his wife; and
Rosalyn and her husband (who did not buy a sweater).

25. The O'Connors bought both COCO and a sweater.

26. Sandra and her husband, who did not buy a sweater, were waited on
immediately before Cathleen and her husband.

Logic Problem courtesy of your friends at Dell ... the Puzzle People!
------------------------------------------------------------------------

And here is the solution in Quintus Prolog:

solve:-
        init(1,CL),
    time(0,T0),
        gen(CL),
        test(CL),
    time(T0,_),
        write('  SOLUTION:'),nl,
        pr(CL).

% The main datastructure is a list of 12 couples. Each couple
  entry contains arrival order (1-12), his name, her name,
  family name and a list of 6 items. The list of items indicates
  which items the couple did (y) or didn't (n) buy. Considering
  the items only, the datastructure may be regarded as a 12 couples
  by 6 items matrix.

init(N,[couple(N,_,_,_,[_,_,_,_,_,_])|T]):-
        N<12 -> N1 is N+1, init(N1,T) |
                true.

gen(CL):-
        transpose(CL,CLT), % CLT is used to constraint check columns -
                           % this makes constraints checking easier.
        simple_clues(CL),
        constraints(CL,CLT),
        important_clues(CL,CLT),
        other_clues(CL,CLT).

test(CL):-
        check_names_and_purchases(CL,[],[],[],[]).

check_names_and_purchases([],_,_,_,_).
check_names_and_purchases([couple(_,MX,FX,NX,PX)|T],ML,FL,NL,PL):-
        \+ member(MX,ML), % MX not in ML
        \+ member(FX,FL),
        \+ member(NX,NL),
        \+ member(PX,PL),
        check_names_and_purchases(T,[MX|ML],[FX|FL],[NX|NL],[PX|PL]).

% Naive ordering of clues.

simple_clues(CL):-
% The following simple clues contains information that can be entered
% into CL straight away.
        clue3(CL),
        clue6(CL),
        clue11(CL),
        clue13(CL),
        clue17(CL),
        clue20(CL).

important_clues(CL,CLT):-
% These clues contains information about many couples and will reduce
% the search space substantially.
% Check constraints after each clue.
        clue4(CL),
        constraints(CL,CLT),
        clue10(CL),
        constraints(CL,CLT),
        clue24(CL),
        constraints(CL,CLT).

other_clues(CL,CLT):-
% The rest of the clues. Check constraints now and then.
        clue1(CL),
        clue2(CL),
        clue5(CL),
        clue7(CL),
        clue8(CL),
        clue9(CL),
        constraints(CL,CLT),
        clue12(CL),
        clue14(CL),
        clue15(CL),
        clue16(CL),
        clue18(CL),
        clue19(CL),
        constraints(CL,CLT),
        clue21(CL),
        clue22(CL),
        clue23(CL),
        clue25(CL),
        clue26(CL),
        name_clue(CL),
        constraints(CL,CLT).

% And here are the clues in Prolog

name_clue(CL):-
        member(couple(_,bob,_,_,_),CL),
        member(couple(_,_,elizabeth,_,_),CL),
        member(couple(_,_,_,stanton,_),CL).

clue1(CL):-
        before(couple(_,_,_,craig,[_,_,_,_,_,y]),
               couple(N1,_,_,murphy,_),CL),
        N1=\=12.

clue2(CL):-
        member(couple(_,_,_,collin,[y,n,y,n,y,y]),CL).

clue3(CL):-
        member(couple( 8,_,_,_,[_,y,_,_,_,_]),CL),
        member(couple(10,_,_,_,[_,y,_,_,_,_]),CL).

clue4(CL):-
        consec([couple(_,_,_,smith,_),
                couple(_,gary,_,_,_),
                couple(_,_,_,_,[_,y,_,_,_,y]),
                couple(_,_,_,swain,_),
                couple(_,bill,_,_,_)],CL).

clue5(CL):-
        member(couple(_,_,geraldine,_,[_,_,_,_,n,n]),CL).

clue6(CL):-
        member(couple(12,_,_,_,[_,_,_,n,_,_]),CL).

clue7(CL):-
        member(couple(_,tom,_,_,[_,y,_,_,_,_]),CL).

clue8(CL):-
        member(couple(_,_,_,marshall,[_,_,n,n,_,_]),CL).

clue9(CL):-
        member(couple(_,_,evelyn,_,[y,_,n,_,_,_]),CL).

clue10(CL):-
        consec([couple(_,_,martha,_,_),
                couple(_,jack,_,_,_),
                couple(_,_,_,_,[y,y,y,n,n,y]),
                couple(_,_,_,_,[_,n,_,n,_,_]),
                couple(_,_,margaret,_,_)], CL).

clue11(CL):-
        member(couple(1,_,_,_,[_,_,y,_,_,_]),CL),
        member(couple(2,_,_,_,[_,_,y,_,_,_]),CL),
        member(couple(3,_,_,_,[_,_,y,_,_,_]),CL),
        member(couple(4,_,_,_,[_,_,y,_,_,_]),CL),
        member(couple(5,_,_,_,[_,_,y,_,_,_]),CL).

clue12(CL):-
        member(couple(_,chuck,_,_,[n,_,_,_,_,_]),CL).

clue13(CL):-
        member(couple(1,_,_,_,[_,_,_,_,n,_]),CL),
        member(couple(2,_,_,_,[_,_,_,_,n,_]),CL),
        member(couple(4,_,_,_,[_,_,_,_,n,_]),CL).

clue14(CL):-
        member(couple(_,_,eleanor,_,[_,_,n,_,_,_]),CL).

clue15(CL):-
        member(couple(N1,allen,_,_,[n,_,_,_,_,n]),CL),
        member(couple(N2,_,_,anthony,[n,_,_,_,_,_]),CL),
        N1=\=N2.

clue16(CL):-
        member(couple(N1,john,_,_,[_,_,_,_,y,y]),CL),
        member(couple(N2,_,cheryl,_,[_,_,_,_,y,y]),CL),
        N2=\=10,
        N2=\=12,
        N2=\=N1.

clue17(CL):-
        member(couple(9,_,_,douglas,[n,_,_,_,n,_]),CL).

clue18(CL):-
        consec([couple(_,adam,_,_,[_,_,_,_,_,n]),
                couple(_,_,_,day,_)],CL).

clue19(CL):-
        member(couple(_,steve,_,_,[_,y,_,y,y,_]),CL).

clue20(CL):-
        member(couple(10,_,_,_,[n,_,_,_,_,_]),CL),
        member(couple(11,_,_,_,[n,_,_,_,_,_]),CL),
        member(couple(12,_,_,_,[n,_,_,_,_,_]),CL).

clue21(CL):-
        member(couple(_,_,_,jones,[_,_,_,_,n,_]),CL).

clue22(CL):-
        member(couple(_,_,susan,_,[_,_,_,y,_,_]),CL).

clue23(CL):-
        member(couple(_,george,_,_,[_,_,_,_,y,_]),CL).

clue24(CL):-
        member(couple(N1,_,dorothy,_,[n,_,_,_,_,_]),CL),
        member(couple(N2,_,_,craig,[n,_,_,_,_,_]),CL),
        N1=\=N2,
        member(couple(N3,joe,_,_,[n,_,_,_,_,_]),CL),
        N3=\=N1, N3=\=N2,
        member(couple(N4,_,rosalyn,_,[n,_,_,_,n,_]),CL),
        N4=\=N1, N4=\=N2, N4=\=N3.

clue25(CL):-
        member(couple(_,_,_,oconnor,[_,_,y,_,y,_]),CL).

clue26(CL):-
        consec([couple(_,_,sandra,_,[_,_,_,_,n,_]),
                couple(_,_,cathleen,_,_)],CL).

% Constraints on the number of items
%  -  Every couple bought 4 different items out of 6 (row)
%  -  There are 8 copies of each item (column)
% These constraints are checked and used to fill in purchases
% until nothing more can be filled in.

constraints(CL,CLT):-
% always check purchases (row) and items (columns)
        check_purchases(CL,_),
        item_constraints(CL,CLT).

purch_constraints(CL,CLT):-
% check purchases and if anything was filled in check items
        check_purchases(CL,F),
        ( F=fill -> item_constraints(CL,CLT) | true ).

item_constraints(CL,CLT):-
% check items and if anything was filled in check purchases
        check_items(CLT,F),
        ( F=fill -> purch_constraints(CL,CLT) | true ).

%   check_purchases(X,F) and check_items(X,F) check and fill in
% purchases (rows) and items (columns) using constraints on the
% number of items purchased and the number of items available.
% F indicates if some element was filled (fill) or not (nofill).
% Both predicates fail if some constraint cannot be met.

check_purchases(CL,F):-
        fill_purchases(CL,F).

fill_purchases([],nofill):- !.
fill_purchases([],fill).
fill_purchases([couple(_,_,_,_,P)|T],F):-
        fill(P,4,2,_,F1) ->
                F1=fill, % fail if F1=error
                F=fill,
                fill_purchases(T,fill) |
                fill_purchases(T,F).

check_items(CLT,F):-
        fill_items(CLT,F).

fill_items([],nofill):- !.
fill_items([],fill).
fill_items([P|T],F):-
        fill(P,8,4,_,F1) ->
                F1=fill, % fail if F1=error
                F=fill,
                fill_items(T,fill) |
                fill_items(T,F).

%     fill(L,Ny,Nn,V,F) fills unbound variables of L (row or column)
% if possible.
% L is the (tail of) a row (purchase) or column (items) and V its
% unified unbound variables.
% Ny (Nn) is the maximum number of y's (n's) remaining.
% fill succeeds with F=fill if some element is bound.
% If no element is bound, fill fails in order to de-unify unbound elements.
% If some constraint is not met (too many y's or n's) fill succeeds
% with F=error.

% end of L:
fill([],0,Nn,n,fill):- !, Nn=\=0.  % Ny=0 => bind remaining variables to n
                                   % fail if Ny=Nn=0 - no element bound
fill([],_,0,y,fill).               % Nn=0 => bind remaining variables to y
                                   % fail if Ny and Nn > 0 - no filling
% next element is unbound:
fill([V|T],Ny,Nn,V,F):-
        var(V),!,
        fill(T,Ny,Nn,V,F).
% next element is y:
fill([y|T],Ny,Nn,V,F):-
        Ny=0 -> F=error |   % too many y's
                Ny1 is Ny-1, fill(T,Ny1,Nn,V,F).
% next element is n:
fill([n|T],Ny,Nn,V,F):-
        Nn=0 -> F=error |   % too many n's
                Nn1 is Nn-1, fill(T,Ny,Nn1,V,F).

% Support predicates

% transpose the couple matrix (O(n**2))
% transpose(M,Tr)  Tr == transpose_purchases_part_of(M)
transpose([],[]).
transpose([],[[]|T]):-
        transpose([],T).
transpose([couple(_,_,_,_,P)|T],Tr):-
        tr_row(P,diff(Tr,TrT)),
        transpose(T,TrT).
% transpose a row
% tr_row(Row,TrR)   TrR == transpose(Row)
tr_row([],diff([],[])).
tr_row([X|T],diff([[X|XT]|T1],[XT|T2])):-
        tr_row(T,diff(T1,T2)).

member(X,[X|_]).
member(X,[_|T]):-
        member(X,T).

before(C1,C2,[C1|T]):-
        member(C2,T).
before(C1,C2,[_|T]):-
        before(C1,C2,T).

consec([X|T1],[X|T2]):-
        head_sublist(T1,T2).
consec(S,[_|T]):-
        consec(S,T).

head_sublist([],_).
head_sublist([X|T1],[X|T2]):-
        head_sublist(T1,T2).

pr([]).
pr([H|T]):-
        write(H), nl,
        pr(T).

time(TIN,TOUT):-
        TIN=0 -> statistics(runtime,[TOUT,_]) |
                statistics(runtime,[TOUT,_]),
                T is TOUT-TIN,
                write('  Extime = '), write(T), write(' ms'), nl.

------------------------------

End of PROLOG Digest
********************