import java.io.*; import java.lang.Integer.*; import java.util.*; import java.util.stream.*; import java.lang.StringBuilder; import java.util.concurrent.CountDownLatch; //////////////////////////////// Solve Sudoku Puzzles //////////////////////////////// //////////////////////////////// See http://norvig.com/CQ //////////////////////////////// //////////////////////////////// @author Peter Norvig //////////////////////////////// /** There are two representations of puzzles that we will use: ** 1. A gridstring is 81 chars, with characters '0' or '.' for blank and '1' to '9' for digits. ** 2. A puzzle grid is an int[81] with a digit d (1-9) represented by the integer (1 << (d - 1)); ** that is, a bit pattern that has a single 1 bit representing the digit. ** A blank is represented by the OR of all the digits 1-9, meaning that any digit is possible. ** While solving the puzzle, some of these digits are eliminated, leaving fewer possibilities. ** The puzzle is solved when every square has only a single possibility. ** ** Search for a solution with `search`: ** - Fill an empty square with a guessed digit and do constraint propagation. ** - If the guess is consistent, search deeper; if not, try a different guess for the square. ** - If all guesses fail, back up to the previous level. ** - In selecting an empty square, we pick one that has the minimum number of possible digits. ** - To be able to back up, we need to keep the grid from the previous recursive level. ** But we only need to keep one grid for each level, so to save garbage collection, ** we pre-allocate one grid per level (there are 81 levels) in a `gridpool`. ** Do constraint propagation with `arcConsistent`, `dualConsistent`, and `nakedPairs`. **/ public class Sudoku { //////////////////////////////// main; command line options ////////////////////////////// static final String usage = String.join("\n", "usage: java Sudoku -help | -(no)[fptgadnsrv] | -[RT] | ...", "E.g., -v turns verify flag on, -nov turns it off. -R and -T require a number. The args are:\n", " -h(elp) Print this usage message", " -f(ile) Print summary stats for each file (default on)", " -p(uzzle) Print summary stats for each puzzle (default off)", " -t(hread) Print summary stats for each thread (default off)", " -g(rid) Print each puzzle grid and solution grid (default off)", " -a(rc) Run arc consistency (default on)", " -d(ual) Run dual consistency (default on)", " -n(aked) Run naked pairs (default on)", " -s(earch) Run search (default on, but some puzzles can be solved with CSP methods alone)", " -r(everse) Solve the reverse of each puzzle as well as each puzzle itself (default off)", " -v(erify) Verify each solution is valid (default off)", " -T Concurrently run threads (default 26)", " -R Repeat each puzzle times (default 1)", " Solve all puzzles in filename, which has one puzzle per line"); boolean printFileStats = true; // -f boolean printPuzzleStats = false; // -p boolean printThreadStats = false; // -t boolean printGrid = false; // -g boolean runArcCons = true; // -a boolean runDualCons = true; // -d boolean runNakedPairs = true; // -n boolean runSearch = true; // -s boolean reversePuzzle = false; // -r boolean verifySolution = false; // -v int nThreads = 26; // -T int repeat = 1; // -R /** Parse command line args and solve puzzles in files. **/ public static void main(String[] args) throws IOException { Sudoku s = new Sudoku(); for (String arg: args) { if (!arg.startsWith("-")) { s.solveFile(arg); } else { boolean value = !arg.startsWith("-no"); switch(arg.charAt(value ? 1 : 3)) { case 'h': if (value) { System.out.println(usage); } break; case 'f': s.printFileStats = value; break; case 'p': s.printPuzzleStats = value; break; case 't': s.printThreadStats = value; break; case 'a': s.runArcCons = value; break; case 'g': s.printGrid = value; break; case 'd': s.runDualCons = value; break; case 's': s.runSearch = value; break; case 'n': s.runNakedPairs = value; break; case 'r': s.reversePuzzle = value; break; case 'v': s.verifySolution = value; break; case 'T': s.nThreads = Integer.parseInt(arg.substring(2)); break; case 'R': s.repeat = Integer.parseInt(arg.substring(2)); break; default: System.out.println("Unrecognized option: " + arg + "\n" + usage); } } } } //////////////////////////////// Handling Lists of Puzzles //////////////////////////////// /** Solve all the puzzles in a file. Report timing statistics. **/ void solveFile(String filename) throws IOException { List grids = readFile(filename); long startTime = System.nanoTime(); switch(nThreads) { case 1: solveList(grids); break; default: solveListThreaded(grids, nThreads); break; } if (printFileStats) printStats(grids.size() * repeat, startTime, filename); } /** Solve a list of puzzles in a single thread. ** repeat -R times; print each puzzle's stats if -p; print grid if -g; verify if -v. **/ void solveList(List grids) { int[] puzzle = new int[N * N]; // Used to save a copy of the original grid int[][] gridpool = new int[N * N][N * N]; // Reuse grids during the search for (int g=0; g grids, int nThreads) { try { final long startTime = System.nanoTime(); int nGrids = grids.size(); final CountDownLatch latch = new CountDownLatch(nThreads); int size = nGrids / nThreads; for (int c = 0; c < nThreads; ++c) { final List sublist = grids.subList(c * size, c == nThreads - 1 ? nGrids : (c + 1) * size); new Thread() { public void run() { solveList(sublist); latch.countDown(); if (printThreadStats) { printStats(repeat * sublist.size(), startTime, "Thread"); } } }.start(); } latch.await(); // Wait for all threads to finish } catch (InterruptedException e) { System.err.println("And you may ask yourself, 'Well, how did I get here?'"); } } //////////////////////////////// Utility functions //////////////////////////////// /** Return an array of ints {0, 1, ..., n-1} **/ int[] range(int n) { int[] result = new int[n]; for (int i = 0; i 0) { // Copy grid's contents into the gridpool to be used at the next level System.arraycopy(grid, 0, gridpool[level], 0, grid.length); int[] result = search(assign(gridpool[level], s, d), gridpool, level + 1); if (result != null) { return result; } } } return null; } /** Check if grid is a solution to the puzzle. **/ boolean isSolution(int[] grid, int[] puzzle) { if (grid == null) { return false; } // Check that all squares have a single digit, and // no filled square in the puzzle was changed in the solution. for (int s: SQUARES) { if (NUM_DIGITS[grid[s]] != 1 || (NUM_DIGITS[puzzle[s]] == 1 && grid[s] != puzzle[s])) return false; } // Check that each unit is a permutation of digits for (int[] u: ALL_UNITS) { if (IntStream.of(u).sum() != ALL_DIGITS) return false; } return true; } /** Choose an unfilled square with the minimum number of possible values. ** If all squares are filled, return -1 (which means the puzzle is complete). **/ int select_square(int[] grid) { int square = -1; int min = N + 1; for (int s: SQUARES) { int c = NUM_DIGITS[grid[s]]; if (c == 2) { return s; // Can't get fewer than 2 possible digits } else if (c > 1 && c < min) { square = s; min = c; } } return square; } /** Assign grid[s] = d. If this leads to contradiction, return null. **/ int[] assign(int[] grid, int s, int d) { if ((grid == null) || ((grid[s] & d) == 0)) { return null; } // d not possible for grid[s] grid[s] = d; for (int p: PEERS[s]) { if (!eliminate(grid, p, d)) { // If we can't eliminate d from all peers of s, then fail return null; } } return grid; } /** Remove digit d from possibilities for grid[s]. ** Run the 3 constraint propagation routines (unless a command line flag says not to). ** If constraint propagation detects a contradiction return false. **/ boolean eliminate(int[] grid, int s, int d) { if ((grid[s] & d) == 0) { return true; } // d already eliminated from grid[s] grid[s] -= d; return ((!runArcCons || arc_consistent(grid, s)) && (!runDualCons || dual_consistent(grid, s, d)) && (!runNakedPairs || naked_pairs(grid, s))); } //////////////////////////////// Constraint Propagation //////////////////////////////// /** Check if square s is ok: that is, it has multiple possible values, or it has ** one possible value which we can consistently assign. **/ boolean arc_consistent(int[] grid, int s) { int c = NUM_DIGITS[grid[s]]; return c >= 2 || (c == 1 && (assign(grid, s, grid[s]) != null)); } /** After we eliminate d from possibilities for grid[s], check each unit of s ** and make sure there is some position in the unit where d can go. ** If there is only one possible place for d, assign it. **/ boolean dual_consistent(int[] grid, int s, int d) { for (int[] u: UNITS[s]) { int dPlaces = 0; // The number of possible places for d within unit u int dplace = -1; // Try to find a place in the unit where d can go for (int s2: u) { if ((grid[s2] & d) > 0) { // s2 is a possible place for d dPlaces++; if (dPlaces > 1) break; dplace = s2; } } if (dPlaces == 0 || (dPlaces == 1 && (assign(grid, dplace, d) == null))) { return false; } } return true; } /** Look for two squares in a unit with the same two possible values, and no other values. ** For example, if s and s2 both have the possible values 8|9, then we know that 8 and 9 ** must go in those two squares. We don't know which is which, but we can eliminate ** 8 and 9 from any other square s3 that is in the unit. **/ boolean naked_pairs(int[] grid, int s) { int val = grid[s]; if (NUM_DIGITS[val] != 2) { return true; } // Doesn't apply for (int s2: PEERS[s]) { if (grid[s2] == val) { // s and s2 are a naked pair; find what unit(s) they share for (int[] u: UNITS[s]) { if (member(s2, u)) { for (int s3: u) { // s3 can't have either of the values in val (e.g. 8|9) if (s3 != s && s3 != s2) { int d = HIGHEST_DIGIT[val]; int d2 = val - d; if (!eliminate(grid, s3, d) || !eliminate(grid, s3, d2)) { return false; } } } } } } } return true; } //////////////////////////////// Input //////////////////////////////// /** The method `readFile` reads one puzzle per file line and returns a List of puzzle grids. **/ List readFile(String filename) throws IOException { BufferedReader in = new BufferedReader(new FileReader(filename)); List grids = new ArrayList(260000); String gridstring; while ((gridstring = in.readLine()) != null) { grids.add(parseGrid(gridstring)); if (reversePuzzle) { grids.add(parseGrid(new StringBuilder(gridstring).reverse().toString())); } } return grids; } /** Parse a gridstring into a puzzle grid: an int[] with values 0-9. **/ int[] parseGrid(String gridstring) { int[] grid = new int[N * N]; int s = 0; for (int i = 0; i