Imperial College London – Department of Computing

276: Introduction to Prolog Exercise 5: N Queens
(Based on an Exercise and Solution by Robert Craven)

Solutions and Comments
November 2013
A chessboard is an 8 × 8 grid; the chess piece known as a ‘queen’ can attack along any horizontal, vertical and diagonal, as shown below:

The problem: place 8 queens on a chessboard, in such a way that no queen is attacking any other queen. Suppose that a queen in the mth column and the nth row is represented by the Prolog term q(m,n) (like Cartesian co-ordinates); a configuration of the board is to be represented by a Prolog list. For example, the list [q(5,6),q(6,2),q(7,4),q(8,1)] would represent the partially-completed non-solution: 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 Notice that if all solutions are forced to have the ‘canonical’ form [q(1,Y1),q(2,Y2),q(3,Y3),q(4,Y4),q(5,Y5),q(6,Y6),q(7,Y7),q(8,Y8)] then we do not need to check that there are no vertical attacks. (Why?)
Page 1


Y)) :Y \= Y1. attack_pair(q(X. Queen) ). Here is one more method. attack_pair(q(X. Y1-Y =:= X1-X.no_attack([q(7. Queen) :attack(Rest.q(8. q(X. Here are a few possibilities and (Rob Craven) % ----. (PartialSoln will be ground. q(6.4).Y). variations._). mjs/276/5 Page 2 .Y1)) :- The first clause for attack_pair/2 is not strictly necessary but I will include it since it is cheap and perhaps makes things clearer.Y)). no ?. so that checking for vertical attacks is unnecessary. yes You can assume that solutions are in the canonical form described above. q(6. q(X1.) For example (with reference to the grid above): ?. q(X.no_attack/2 (alternative) no_attack(Partial. Queen). Queen) : attack(Partial. attack_pair(Qx.no_attack/2 no_attack([]. attack_pair(q(_.N)) when a queen can be placed in the mth column and nth row without falling under attack from any of the queens in PartialSoln.1)]. attack_pair(q(X. Y1-Y =\= X-X1. q(X. You should assume that none of the queens in PartialSoln attack each other.q(8. Queen). There are many different solutions to each of these exercises. Y1-Y =\= X1-X.Y1)) :q(X1.Y1)|Rest]. no_attack/2 above could also be written equivalently as: % ----.Y). % ----.Y). attack_pair(Qx.no_attack/2 (alternative. Solution.1. Partial). Queen) ).no_attack(PartialSoln. Queen) :forall(member(Qx. Write a program no attack/2. Partial). q(M. Queen) :attack_pair(Q. q(_. attack([_|Rest]. that combines attack_pair and member into one. no_attack([q(X1.Y))._)).no_attack([q(7. Y1-Y =:= X-X1. Queen). equivalently) no_attack(Partial.1)]. Alternatively: % ----.no_attack/2 no_attack(Partial. Queen) : (member(Qx. which succeeds on a call ?. This the solution I prefer (because it will be easiest to modify and generalise later). _).4).1)).2)). no_attack(Rest. attack([Q|_].

mjs/276/5 Page 3 . [1. you can just write: ?. Note how this works: template/1 gives the general form of the solution. Sols).4. so that ?.Y). q(X.8]). Using your answer from (1). write a program queens8/1. findall(X. queens8([q(X. member(Y. This is easy._).6.Y)|Rest]) :queens8(Rest).template(X). You should have a Prolog fact template/1 in the program.7._). Solution. Min) :Min =< Max. Sols). There is no point doing the (much more expensive) setof instead of findall if you don’t have to.q(5. to give the ‘canonical’ form of the solution. Construct a one-line query which gives you the number of solutions to the 8 Queens problem.q(6. queens8(X). which takes as argument a list representing an empty grid.8]).5. N).6._). setof(X.queens8/1 (Rob Craven) queens8([]). Max._). If you cannot assume that your program does not generate duplicate solutions. Max. How to make it tail recursive? Many ways.template(Sol).q(8. length(Sols. See discussion below. will give solutions. then ?. where in_range/3 is defined as follows: % -----------.q(2.q(7. It is the job of queens8/1 to produce values for the columns by instantiating the variables.8.q(4.3. queens8(X).template(X). % ----. and returns a solution to the 8 Queens problem. X). length(Sols.7.2. Max. but represents one of the most intuitive ways to solve the problem.Y)). Note Instead of member(Y.q(3.5._). % to be on the safe side in_range(Min. we could write in_range(1._). NextMin is Min + 1._)._)]). [1.template/1 template([q(1. Given the previous answers. in_range(NextMin. % ----. queens8(Sol).4. Solution. X) :Min < Max. N).in_range/3 in_range(Min.2. 3. The following solution by Rob Craven is not tail recursive.3.2. no_attack(Rest.

Row).6. 8) ) ). Here is one way. QueenCol. nl.print_row/3 print_row(End. write(’_ ’) ). 8) ) ). % ----. Y).5.2. Max) :End > Max.1]).Row). forall( member(q(Col. ( member(q(Col. [8.7.3. _.5.4. forall( member(Row. print_row(1. NextCol is Col + 1.Board).forall/2 forall(X. nl. given the canonical list representation of the solution as input. [8.print_board/1 (Rob Craven. Write a program print board/1 which will output a picture of a completed solution. print_row(1. % ----. forall( member(Row. Y) : (X.7. There are many ways to do this.3.4. % could put a ’cut’ here nl. equivalently) print_board(Board) :nl. Solution.Board).print_board/1 (Rob Craven) print_board(Board) :nl. % could be omitted if ’cut’ above ( Col = QueenCol -> write(’Q ’) .4. Max) :Col =< Max. mjs/276/5 Page 4 . Something like the following should result: _ _ _ Q _ _ _ _ _ Q _ _ _ _ _ _ _ _ _ _ _ _ Q _ _ _ Q _ _ _ _ _ _ _ _ _ _ Q _ _ _ _ _ _ _ _ _ Q _ _ _ _ Q _ _ _ Q _ _ _ _ _ _ _ Prolog I/O primitives are not an examinable part of the course but what follows is a typical bit of procedural Prolog code. print_row(Col. using forall/2: % ----.2. print_board/1 could also have been expressed equivalently as follows: % ----. Col.1]). Col. Max). print_row(NextCol.6. QueenCol.

Question: Why are they equivalent? forall( P. R ) ) is the same as ( P. Q). y ) ) ) ∀x ∀y ( (P (x) ∧ Q(x. Col.5. nl. ( Q.2. R ) which is the same as forall( ( P. y )) → R(x. y ) → R(x. Q).3. 8) ). member(q(Col. forall( Q.1]). R ) In logic: ∀x ( P (x) → ∀y ( Q(x. y ) ) ) ≡ ≡ ∀x ∀y ( P (x) → ( Q(x.Board) ). ( Q. R ) ) is not logically equivalent but equivalent procedurally — but only because R is print_row(1. R ) ) is the same as (conjunction is associative) ( ( P. Pick whichever you think is clearest. 8) and so • never fails • is only used for its side-effects (printing) mjs/276/5 Page 5 . y ) → R(x. R ) ) is the same as ( P. R ) ) is the same as ( P.4. print_row(1. forall( ( member(Row.print_board/1 (Rob Craven. equivalently again) print_board(Board) :nl. forall( Q. ( Q.And also like this: % ----. [8.Row).7. Col.6. y ) ) Rob Craven’s forall( P.

Row) :( Y = Row -> write(’Q ’) .We can also write print_row differently. q(2. 8).7. ] So to print row N we just recurse down the list representing the board looking for an entry q( . Row). q(4. q(3. write(’_ ’) ). print_row([q(X.N ).. . Row) ).6).5. print_row(Board. nl.4. _Row) :nl. [8. print_row(RestBoard.print_board/1 print_board(Board) :nl. by exploiting the fact that the board is represented in canonical form.Y)|RestBoard]. mjs/276/5 Page 6 . % ----. forall( member(Row.3).. Since the board is in ’canonical form’ it will look something like this [q(1.print_row/2 (recursive) print_row([].2.5).6.1]). as follows: % ----.3.

6.4. [q(X. CandCols. select(X.queens8_tr_opt/3 queens8_tr_opt([].8]). PartialSol) :member(Y. Y. [X|Y].Y)|PartialSol]. the PartialSol will be built up in reverse order. [q(X.Discussion: Some alternative (tail recursive) solutions First. Note(2) We do not need an additional argument to represent the final solution. ColsRem). % ----. queens8_tr(Rest.Y)). using a ‘template’ for the final solution: ?. % slightly optimised version (improved) queens8_tr_opt(Sol) :queens8_tr_opt(Sol.6.8]). initially []. % ----.2. queens8_tr_opt(Rest. Note(1) As written above. The second argument in queens8_tr/2 represents the partial solution constructed so far. let us keep a list of candidate column numbers (columns not used so far). _CandCols). mjs/276/5 Page 7 . Y). % _CandCols will be [] queens8_tr_opt([q(X. [U|Y]. no_attack(PartialSol.7. []).Y)).Y)|Rest]. queens8_tr([q(X. []. Note(3) We can improve the efficiency slightly. as select(X. no_attack(PartialSol. Z).5. We do not need to keep an explicit list of candidate column values remaining: the candidate values are just those that have not been used in PartialSol so far.7. []).3.template(Sol). Instead of generating all candidate values from 1 to 8 at every step. CandCols) :select(Y.2.4. Note(4) The above was just for illustration. _Sol. % ---------. [U|Z]) :select(X. ColsRem). % slightly optimised version queens8_tr_opt(Sol) :queens8_tr_opt(Sol. That does not matter because no_attack/2 as written does not depend on the order that items appear in its PartialSol argument. q(X. _PartialSol).3. The ‘template’ already stores the solution.Y)|PartialSol]).Y)|Rest]. q(X.5. [1. from position of the 8th queen back to the 1st.queens8_tr/2 % tail recursive queens8_tr([]. nor any call in the base case to reverse the partial solution into the right order. [1. queens8_tr/2 simply fills in values for the variables in the template. queens8_tr(Sol. PartialSol.

Y).% ----.6. Sol).Y). queens8_tr(NextM. Y). [1. PartialSoln). % -----.queens8_tr_opt/2 queens8_tr_opt([].queens8_tr/1 (recursive. Sol. ColsRem).Y)). 8 queens8_tr(0. _Sol). in_range(1. Final) :M > 0.5. % PartialSoln has queens in columns M+1 . then 7th queen in column 7.Y)|PartialSol].e. 8.2. PartialSoln. []. % alternatively: % member(Y. Discussion: an alternative (tail recursive) solution Suppose we don’t use a ‘template’. queens8_tr_opt(Rest. % ------queens8_tr/3 % queens8_tr(M. i. q(M. % alternatively: % member(Y. Final) % place queen M.5. is unchanged. [q(X. It is easiest to go from the right. Final). 8. no template) queens8_tr(Sol) :queens8_tr(8. [1. % optional no_attack(PartialSoln.7. [q(M. no_attack(PartialSol. Y). NextM is M-1.4. PartialSoln. Sol).4.. .7. .8]).8]). Solve the problem by means of the query: ?.Y)|PartialSoln].Y)). member(q(_. queens8_tr(M. Let us solve the problem by building up a partial solution. PartialSol) :in_range(1. q(X.Y)|Rest].2. mjs/276/5 Page 8 . The printing of the board etc.3.3. placing 8th queen in column 8. member(q(_. queens8_tr_opt([q(X. .6.queens8_tr(Sol). PartialSol).

Sol). attack_pair(q(_. attack([_|Rest]. rewrite your program to use this new representation.Q). [Y|PartialSoln].3. The only thing we need to change is attack/2 since that is the only component of the program that depends on how the board is represented.6. % ----------. % ----------.7. Sol.5. we could alter our terms so that. PartialSoln). q(_. q(M.6. [1.4.attack_pair/2 attack_pair(q(X. Mx.Y).7). for efficiency Mx is M + 1.1. Queen).2. % ----------. One could do one with a ‘template’ too but I will not bother. % queens8_tr(M. MxNext. Queen) :attack_pair(q(Mx. queens8_tr(NextM.) Every solution will thus be a permutation of [1.6).8]).3. member(Y. attack_pair(q(X. Mx.Y)).4. Final) :M > 0. 8 queens8_tr(0. attack(PartialSoln. instead of [q(1.5. Sol).q(2.q(4.5. NextM is M-1. PartialSoln. queens8_tr(M. Queen)._)). PartialSoln.5] This gives a much more compact representation.2.5)] we have [3. Y1-Y =:= X1-X.Y1)) :q(X1. attack_pair(q(X.8. % alternatively: % member(Y.Y1)) :- Page 9 . There is a redundancy in the data structures we are using to represent our chessboards.Y)).attack/3 % attack(PartialSoln. Queen) % PartialSoln has queens placed in columns Mx .2).Y).4.2. []. Y1-Y =:= X-X1.6. % optional. Final) % place queen M where % PartialSoln has placed queens M+1 . (New file so we can use the same predicates without name clashes.q(3. Mx..Y). 8 attack([Q|_]. for example. q(X1.8). attack(Rest. Final).q(5.q(8. Queen) :MxNext is Mx + 1.q(7.3). in_range(1. As the mth element of our list represents the mth column. In a new file. mjs/276/5 q(X. Here is a solution without a ‘template’..1).4). Mx._).Y).7.8.8] Solution.q(6.queens8_tr/1 and queens8_tr/3 queens8_tr(Sol) :queens8_tr(8.7.

1. Max. Both will generate all solutions eventually. nl. NextMax is Max . print_row(RestBoard. mjs/276/5 Page 10 . % ----------- print_board/1 print_board(Board) :length(Board. in_range_desc(Min. but whether we use in_range/3 or in_range_desc/3 to generate candidate values in queens8_tr/3 makes no difference. % to be on the safe side in_range_desc(Min. X). write(’_ ’) ). though in a different order.For printing the Board: % ----------. _Row) :nl. print_row([Y|RestBoard]. % ----------print_row/2 print_row([]. Row) ). NextMax. Note that we could dispense with in_range/3. print_row(Board. We need in_range_desc/3 to print the board rows in the correct order.N. Max) :Min =< Max. forall( in_range_desc(1. Row) :( Y = Row -> write(’Q ’) . highest to lowest.Row). nl. X) :Min < Max.in_range_desc/3 in_range_desc(Min. N). Row). Max.

% to be on the safe side queensN_tr(N. The 8 Queens problem can be generalized to the ‘n Queens’ problem. there is an n × n grid and n queens. attack/3 and attack_pair/2 are unchanged (they do not depend on number of queens). Sol. Mx. NextM is M-1. [Y|PartialSoln]. % this is the only change! member(Y. for efficiency Mx is M + 1.. The printing of the board is unchanged (my version). Solution. Sol).queensN_tr/2 and queensN_tr/3 queensN_tr(N. PartialSoln). attack(PartialSoln. [].6. Modify your answer for (5) to solve the n Queens problem. Sol). in_range(1.) % ----------. N queensN_tr(0. PartialSoln. PartialSoln.Y). queensN_tr(NextM. (One could do one with a ‘template’ too but I will not bother. where instead of an 8 × 8 chessboard. Final) :M > 0. N > 0. Final). mjs/276/5 Page 11 . % queensN_tr(M. Final) % place queen M where % PartialSoln has placed queens M+1 . % optional.N. queensN_tr(M.Y)). Sol) :integer(N). q(M. The generalisation of the previous program to n queens is trivial.