/* Crossfigure in Comet. CSPLib problem 21 http://www.cs.st-andrews.ac.uk/~ianm/CSPLib/prob/prob021/index.html """ Crossfigures are the numerical equivalent of crosswords. You have a grid and some clues with numerical answers to place on this grid. Clues come in several different forms (for example: Across 1. 25 across times two, 2. five dozen, 5. a square number, 10. prime, 14. 29 across times 21 down ...). """ Also, see http://en.wikipedia.org/wiki/Cross-figure William Y. Sit: "On Crossnumber Puzzles and The Lucas-Bonaccio Farm 1998 http://scisun.sci.ccny.cuny.edu/~wyscc/CrossNumber.pdf Bill Williams: Crossnumber Puzzle, The Little Pigley Farm http://jig.joelpomerantz.com/fun/dogsmead.html This Comet model was created by Hakan Kjellerstrand (hakank@bonetmail.com) Also, see my Comet page: http://www.hakank.org/comet */ /* This model was inspired by the ECLiPSe model written by Warwick Harvey: http://www.cs.st-andrews.ac.uk/~ianm/CSPLib/prob/prob021/code.html Data from http://thinks.com/crosswords/xfig.htm. This problem is 001 from http://thinks.com/crosswords/xfig.htm ("X" is the blackbox and is fixed to the value of 0) 1 2 3 4 5 6 7 8 9 --------------------------- 1 2 _ 3 X 4 _ 5 6 1 7 _ X 8 _ _ X 9 _ 2 _ X 10 _ X 11 12 X _ 3 13 14 _ _ X 15 _ 16 _ 4 X _ X X X X X _ X 5 17 _ 18 19 X 20 21 _ 22 6 _ X 23 _ X 24 _ X _ 7 25 26 X 27 _ _ X 28 _ 8 29 _ _ _ X 30 _ _ _ 9 The answer is 1608 9183 60 201 42 3 72 14 1 5360 2866 3 4 4556 1156 9 67 16 8 68 804 48 1008 7332 Solutions: MiniZinc and Gecode/fz solves the problem in about 8 seconds. ECLiPSe/ic: 35 seconds MiniZinc/fdmip in 14 seconds. Comet: 1 second. */ import cotfd; int t0 = System.getCPUTime(); int n = 9; range D = 0..9999; // the max length of the numbers in this problem is 4 Solver m(); var{int} M[1..n, 1..n](m, 0..9); var{int} A1(m, D); var{int} A4(m, D); var{int} A7(m, D); var{int} A8(m, D); var{int} A9(m, D); var{int} A10(m, D); var{int} A11(m, D); var{int} A13(m, D); var{int} A15(m, D); var{int} A17(m, D); var{int} A20(m, D); var{int} A23(m, D); var{int} A24(m, D); var{int} A25(m, D); var{int} A27(m, D); var{int} A28(m, D); var{int} A29(m, D); var{int} A30(m, D); var{int} D1(m, D); var{int} D2(m, D); var{int} D3(m, D); var{int} D4(m, D); var{int} D5(m, D); var{int} D6(m, D); var{int} D10(m, D); var{int} D12(m, D); var{int} D14(m, D); var{int} D16(m, D); var{int} D17(m, D); var{int} D18(m, D); var{int} D19(m, D); var{int} D20(m, D); var{int} D21(m, D); var{int} D22(m, D); var{int} D26(m, D); var{int} D28(m, D); // // Convert array <-> number // function void toNum10(var{int}[] x, var{int} num) { Solver m = x[1].getSolver(); int start = x.getLow(); int end = x.getHigh(); m.post(num == sum(i in start..end) 10^(end-i)*x[i] ); } /* across(Matrix, Across, Len, Row, Col) Constrains 'Across' to be equal to the number represented by the 'Len' digits starting at position (Row, Col) of the array 'Matrix' and proceeding across. */ function void across(var{int}[,] Matrix, var{int} Across, int Len, int Row, int Col) { Solver m = Matrix[1,1].getSolver(); var{int} tmp[1..Len](m, 0..9999); toNum10(tmp, Across); forall(i in 0..Len-1) { m.post(Matrix[Row,Col+i] == tmp[i+1]); } } /* down(Matrix, Down, Len, Row, Col): Constrains 'Down' to be equal to the number represented by the 'Len' digits starting at position (Row, Col) of the array 'Matrix' and proceeding down. */ function void down(var{int}[,] Matrix, var{int} Across, int Len, int Row, int Col) { Solver m = Matrix[1,1].getSolver(); var{int} tmp[1..Len](m, 0..9999); toNum10(tmp, Across); forall(i in 0..Len-1) { m.post(Matrix[Row+i,Col] == tmp[i+1]); } } /* x is a prime (is not needed, since I found m.inside) */ function void is_prime(Solver m, var{int} x) { forall(i in 2..x.getMax() / 2) m.post(i != x => x % i > 0); } // // x is a square (is not needed, since I found m.inside) // function void square(Solver m, var{int} x) { var{int} tmp(m, 0..x.getSize()); m.post(x == tmp^2); } Integer num_solutions(0); exploreall { // Set up the constraints between the matrix elements and the // clue numbers. across(M, A1, 4, 1, 1); across(M, A4, 4, 1, 6); across(M, A7, 2, 2, 1); across(M, A8, 3, 2, 4); across(M, A9, 2, 2, 8); across(M, A10, 2, 3, 3); across(M, A11, 2, 3, 6); across(M, A13, 4, 4, 1); across(M, A15, 4, 4, 6); across(M, A17, 4, 6, 1); across(M, A20, 4, 6, 6); across(M, A23, 2, 7, 3); across(M, A24, 2, 7, 6); across(M, A25, 2, 8, 1); across(M, A27, 3, 8, 4); across(M, A28, 2, 8, 8); across(M, A29, 4, 9, 1); across(M, A30, 4, 9, 6); down(M, D1, 4, 1, 1); down(M, D2, 2, 1, 2); down(M, D3, 4, 1, 4); down(M, D4, 4, 1, 6); down(M, D5, 2, 1, 8); down(M, D6, 4, 1, 9); down(M, D10, 2, 3, 3); down(M, D12, 2, 3, 7); down(M, D14, 3, 4, 2); down(M, D16, 3, 4, 8); down(M, D17, 4, 6, 1); down(M, D18, 2, 6, 3); down(M, D19, 4, 6, 4); down(M, D20, 4, 6, 6); down(M, D21, 2, 6, 7); down(M, D22, 4, 6, 9); down(M, D26, 2, 8, 2); down(M, D28, 2, 8, 8); // Set up the clue constraints. // Across // 1 27 across times two // 4 4 down plus seventy-one // 7 18 down plus four // 8 6 down divided by sixteen // 9 2 down minus eighteen // 10 Dozen in six gross // 11 5 down minus seventy // 13 26 down times 23 across // 15 6 down minus 350 // 17 25 across times 23 across // 20 A square number // 23 A prime number // 24 A square number // 25 20 across divided by seventeen // 27 6 down divided by four // 28 Four dozen // 29 Seven gross // 30 22 down plus 450 m.post(A1 == 2 * A27); m.post(A4 == D4 + 71); m.post(A7 == D18 + 4); m.post(A8 == D6 / 16); m.post(A9 == D2 - 18); m.post(A10 == 6 * 144 / 12); m.post(A11 == D5 - 70); m.post(A13 == D26 * A23); m.post(A15 == D6 - 350); m.post(A17 == A25 * A23); square(m, A20); is_prime(m, A23); square(m, A24); m.post(A25 == A20 / 17); m.post(A27 == D6 / 4); m.post(A28 == 4 * 12); m.post(A29 == 7 * 144); m.post(A30 == D22 + 450); // Down // // 1 1 across plus twenty-seven // 2 Five dozen // 3 30 across plus 888 // 4 Two times 17 across // 5 29 across divided by twelve // 6 28 across times 23 across // 10 10 across plus four // 12 Three times 24 across // 14 13 across divided by sixteen // 16 28 down times fifteen // 17 13 across minus 399 // 18 29 across divided by eighteen // 19 22 down minus ninety-four // 20 20 across minus nine // 21 25 across minus fifty-two // 22 20 down times six // 26 Five times 24 across // 28 21 down plus twenty-seven m.post(D1 == A1 + 27); m.post(D2 == 5 * 12); m.post(D3 == A30 + 888); m.post(D4 == 2 * A17); m.post(D5 == A29 / 12); m.post(D6 == A28 * A23); m.post(D10 == A10 + 4); m.post(D12 == A24 * 3); m.post(D14 == A13 / 16); m.post(D16 == 15 * D28); m.post(D17 == A13 - 399); m.post(D18 == A29 / 18); m.post(D19 == D22 - 94); m.post(D20 == A20 - 9); m.post(D21 == A25 - 52); m.post(D22 == 6 * D20); m.post(D26 == 5 * A24); m.post(D28 == D21 + 27); // Fix the black boxes m.post(M[1,5] == 0); m.post(M[2,3] == 0); m.post(M[2,7] == 0); m.post(M[3,2] == 0); m.post(M[3,5] == 0); m.post(M[3,8] == 0); m.post(M[4,5] == 0); m.post(M[5,1] == 0); m.post(M[5,3] == 0); m.post(M[5,4] == 0); m.post(M[5,5] == 0); m.post(M[5,6] == 0); m.post(M[5,7] == 0); m.post(M[5,9] == 0); m.post(M[6,5] == 0); m.post(M[7,2] == 0); m.post(M[7,5] == 0); m.post(M[7,8] == 0); m.post(M[8,3] == 0); m.post(M[8,7] == 0); m.post(M[9,5] == 0); } using { labelFF(m); num_solutions := num_solutions + 1; forall(i in 1..n) { forall(j in 1..n) { cout << M[i,j] << " "; } cout << endl; } cout << endl; } cout << "\nnum_solutions: " << num_solutions << endl; int t1 = System.getCPUTime(); cout << "time: " << (t1-t0) << endl; cout << "#choices = " << m.getNChoice() << endl; cout << "#fail = " << m.getNFail() << endl; cout << "#propag = " << m.getNPropag() << endl;