goran@erix.UUCP (04/15/87)
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 goran@erix.UUCP Phone: +46 8 719 9993 Mail: TN/T/SU Ericsson Telecom S-126 25 STOCKHOLM Sweden ----------------------------------------------------------------------- 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.