Add files via upload
This commit is contained in:
1 parent
449b26e84b
commit
49971cca1b
4 files changed
+49668
-49678
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
+119
-129
@@ -1,17 +1,14 @@
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
//////////////////////////////// Solve Sudoku Puzzles ////////////////////////////////
|
//////////////////////////////// Solve Sudoku Puzzles ////////////////////////////////
|
||||||
//////////////////////////////// @author Peter Norvig ////////////////////////////////
|
//////////////////////////////// @author Peter Norvig ////////////////////////////////
|
||||||
//////////////////////////////// 2007, 2021 ////////////////////////////////
|
//////////////////////////////// 2007, 2021, 2026 ////////////////////////////////
|
||||||
|
|
||||||
/** Solve Sudoku Puzzles
|
/**
|
||||||
** @author Peter Norvig
|
|
||||||
** Mostly 2007, som2 2021, 2026
|
|
||||||
**
|
|
||||||
** There are two representations of puzzles that we will use:
|
** 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.
|
** 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));
|
** 2. A puzzle grid is an int[81] with a digit d (1-9) represented by the integer (1 << (d - 1));
|
||||||
@@ -25,9 +22,11 @@ import java.util.stream.IntStream;
|
|||||||
** - If the guess is consistent, search deeper; if not, try a different guess for the square.
|
** - 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.
|
** - 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.
|
** - In selecting an empty square, we pick one that has the minimum number of possible digits.
|
||||||
|
** That makes us more likely to fail early if we guess wrong.
|
||||||
** - To be able to back up, we need to keep the grid from the previous recursive level.
|
** - 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,
|
** To save garbage collection, we pre-allocate one grid per level (81 levels) in a `gridpool`.
|
||||||
** we pre-allocate one grid per level (there are 81 levels) in a `gridpool`.
|
** - We can work in parallel in multiple threads, each with its own grodpool.
|
||||||
|
**
|
||||||
** Do constraint propagation with `arcConsistent`, `dualConsistent`, and `nakedPairs`.
|
** Do constraint propagation with `arcConsistent`, `dualConsistent`, and `nakedPairs`.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
@@ -35,22 +34,21 @@ public class Sudoku {
|
|||||||
|
|
||||||
//////////////////////////////// main; command line options //////////////////////////////
|
//////////////////////////////// main; command line options //////////////////////////////
|
||||||
|
|
||||||
static final String USAGE = String.join("\n",
|
static final String USAGE = """
|
||||||
"usage: java Sudoku -(no)[fghnprstuv] | -[RT]<number> | <filename> ...",
|
usage: java Sudoku -(no)[fghnprstuv] | -[RT]<number> | <filename> ...\n
|
||||||
"Options and filenames are processed left-to-right. Use '-no' to turn an option off\n",
|
Options and filenames are processed left-to-right. Use '-no' to turn an option off
|
||||||
"E.g.: -v turns verify flag on, -nov turns it off. -R and -T require a number. The options:\n",
|
E.g.: -v turns verify flag on, -nov turns it off. -R and -T require a number. The options:\n
|
||||||
" -g(rid) Print each puzzle grid and solution grid (default off)",
|
-g(rid) Print each puzzle grid and solution grid (default off)
|
||||||
" -h(elp) Print this usage message",
|
-h(elp) Print this usage message
|
||||||
" -n(aked) Run the naked pairs strategy (default on)",
|
-n(aked) Run the naked pairs strategy (default on)
|
||||||
" -p(uzzle) Print summary stats for each puzzle (default off)",
|
-p(uzzle) Print summary stats for each puzzle (default off)
|
||||||
" -r(everse) Solve the reverse of each puzzle as well as each puzzle itself (default off)",
|
-r(everse) Solve the reverse of each puzzle as well as each puzzle itself (default off)
|
||||||
" -s(ummary) Print per-file summary stats (default on)",
|
-s(ummary) Print per-file summary stats (default on)
|
||||||
" -t(hread) Print summary stats for each thread (default off)",
|
-t(hread) Print summary stats for each thread (default off)
|
||||||
" -u(nitTest) Run a suite of unit tests (default off)",
|
-v(erify) Verify each solution is valid (default on)
|
||||||
" -v(erify) Verify each solution is valid (default on)",
|
-T<number> Concurrently run <number> threads (default 28)
|
||||||
" -T<number> Concurrently run <number> threads (default 25)",
|
-R<number> Repeat the solving of each puzzle <number> times (default 1)
|
||||||
" -R<number> Repeat the solving of each puzzle <number> times (default 1)",
|
<filename> Solve all puzzles in filename, which has one puzzle per line""";
|
||||||
" <filename> Solve all puzzles in filename, which has one puzzle per line");
|
|
||||||
|
|
||||||
boolean printGrid = false; // -g
|
boolean printGrid = false; // -g
|
||||||
boolean runNakedPairs = true; // -n
|
boolean runNakedPairs = true; // -n
|
||||||
@@ -59,15 +57,29 @@ public class Sudoku {
|
|||||||
boolean printFileStats = true; // -s
|
boolean printFileStats = true; // -s
|
||||||
boolean printThreadStats = false; // -t
|
boolean printThreadStats = false; // -t
|
||||||
boolean verifySolution = true; // -v
|
boolean verifySolution = true; // -v
|
||||||
int nThreads = 25; // -T
|
int nThreads = 28; // -T
|
||||||
int repeat = 1; // -R
|
int repeat = 1; // -R
|
||||||
|
|
||||||
private final AtomicInteger backtracks = new AtomicInteger(0);
|
|
||||||
private volatile boolean headerPrinted = false;
|
private volatile boolean headerPrinted = false;
|
||||||
|
private static final int WORK_BLOCK_SIZE = 16;
|
||||||
|
|
||||||
|
/** Mutable state owned by exactly one solver thread. **/
|
||||||
|
static final class WorkerState {
|
||||||
|
final int[] root = new int[N * N];
|
||||||
|
final int[][] gridpool = new int[N * N][N * N];
|
||||||
|
long backtracks;
|
||||||
|
int puzzlesSolved;
|
||||||
|
}
|
||||||
|
|
||||||
/** Parse command line args and solve puzzles in files. **/
|
/** Parse command line args and solve puzzles in files. **/
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
Sudoku s = new Sudoku();
|
Sudoku s = new Sudoku();
|
||||||
|
|
||||||
|
// Warm up the JIT code cache by solving a puzzle a few times
|
||||||
|
String puzzle = "........8..3...4...9..2..6.....79.......612...6.5.2.7...8...5...1.....2.4.5.....3";
|
||||||
|
s.solveListWithWorkers(Collections.nCopies(1000, s.parseGrid(puzzle)), 1);
|
||||||
|
|
||||||
|
// Process the options and filenames
|
||||||
for (String arg : args) {
|
for (String arg : args) {
|
||||||
if (!arg.startsWith("-")) {
|
if (!arg.startsWith("-")) {
|
||||||
s.solveFile(arg);
|
s.solveFile(arg);
|
||||||
@@ -81,7 +93,6 @@ public class Sudoku {
|
|||||||
case 'r' -> s.reversePuzzle = value;
|
case 'r' -> s.reversePuzzle = value;
|
||||||
case 's' -> s.printFileStats = value;
|
case 's' -> s.printFileStats = value;
|
||||||
case 't' -> s.printThreadStats = value;
|
case 't' -> s.printThreadStats = value;
|
||||||
case 'u' -> s.runUnitTests();
|
|
||||||
case 'v' -> s.verifySolution = value;
|
case 'v' -> s.verifySolution = value;
|
||||||
case 'T' -> s.nThreads = Integer.parseInt(arg.substring(2));
|
case 'T' -> s.nThreads = Integer.parseInt(arg.substring(2));
|
||||||
case 'R' -> s.repeat = Integer.parseInt(arg.substring(2));
|
case 'R' -> s.repeat = Integer.parseInt(arg.substring(2));
|
||||||
@@ -96,62 +107,58 @@ public class Sudoku {
|
|||||||
|
|
||||||
/** Solve all the puzzles in a file. Report timing statistics. **/
|
/** Solve all the puzzles in a file. Report timing statistics. **/
|
||||||
void solveFile(String filename) throws IOException {
|
void solveFile(String filename) throws IOException {
|
||||||
List<int[]> grids = readPuzzlesFromFile(filename);
|
var grids = readPuzzlesFromFile(filename);
|
||||||
long startFileTime = System.nanoTime();
|
long startFileTime = System.nanoTime();
|
||||||
if (nThreads == 1) {
|
long backtracks = solveListWithWorkers(grids, nThreads);
|
||||||
solveList(grids);
|
if (printFileStats) {
|
||||||
} else {
|
printStats(grids.size() * repeat, startFileTime, filename, backtracks);
|
||||||
solveListThreaded(grids, nThreads);
|
|
||||||
}
|
}
|
||||||
if (printFileStats) printStats(grids.size() * repeat, startFileTime, filename);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Solve a list of puzzles in a single thread.
|
/** Use nThreads workers to pull puzzle rids off the list and solve them.
|
||||||
** repeat -R<number> times; print each puzzle's stats if -p; print grid if -g; verify if -v. **/
|
Keep track of the number of backtracks each worker does and return the sum. **/
|
||||||
void solveList(List<int[]> grids) {
|
long solveListWithWorkers(List<int[]> grids, int nThreads) {
|
||||||
int[] puzzle = new int[N * N];
|
int nGrids = grids.size();
|
||||||
int[][] gridpool = new int[N * N][N * N];
|
var nextGrid = new AtomicInteger(0);
|
||||||
for (int g = 0; g < grids.size(); ++g) {
|
try (var pool = Executors.newFixedThreadPool(nThreads)) {
|
||||||
int[] grid = grids.get(g);
|
var tasks = IntStream.range(0, nThreads)
|
||||||
System.arraycopy(grid, 0, puzzle, 0, grid.length);
|
.<Callable<Long>>mapToObj(c -> () -> {
|
||||||
for (int i = 0; i < repeat; ++i) {
|
var worker = new WorkerState();
|
||||||
long startTime = printPuzzleStats ? System.nanoTime() : 0;
|
for (int start; (start = nextGrid.getAndAdd(WORK_BLOCK_SIZE)) < nGrids; ) {
|
||||||
int[] solution = initialize(grid);
|
solveRange(grids, start, Math.min(start + WORK_BLOCK_SIZE, nGrids), worker);
|
||||||
solution = search(solution, gridpool, 0);
|
|
||||||
if (printPuzzleStats) {
|
|
||||||
printStats(1, startTime, "Puzzle " + (g + 1));
|
|
||||||
}
|
|
||||||
if (i == 0 && (printGrid || (verifySolution && !verify(solution, puzzle)))) {
|
|
||||||
printGrids("Puzzle " + (g + 1), grid, solution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** Break a list of puzzles into nThreads sublists and solve each sublist in a separate thread. **/
|
|
||||||
void solveListThreaded(List<int[]> 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) {
|
|
||||||
int end = (c == nThreads - 1) ? nGrids : (c + 1) * size;
|
|
||||||
final List<int[]> sublist = grids.subList(c * size, end);
|
|
||||||
new Thread(() -> {
|
|
||||||
solveList(sublist);
|
|
||||||
latch.countDown();
|
|
||||||
if (printThreadStats) {
|
|
||||||
printStats(repeat * sublist.size(), startTime, "Thread");
|
|
||||||
}
|
}
|
||||||
}).start();
|
return worker.backtracks;
|
||||||
}
|
}).toList();
|
||||||
latch.await();
|
return pool.invokeAll(tasks).stream().mapToLong(f -> {
|
||||||
|
try { return f.get(); } catch (Exception e) { throw new RuntimeException(e); }
|
||||||
|
}).sum();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
System.err.println("Solver thread was interrupted.");
|
System.err.println("Solver thread was interrupted.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Solve grids in [start, end) using one worker in a thread. **/
|
||||||
|
void solveRange(List<int[]> grids, int start, int end, WorkerState worker) {
|
||||||
|
for (int g = start; g < end; ++g) {
|
||||||
|
var grid = grids.get(g);
|
||||||
|
for (int i = 0; i < repeat; ++i) {
|
||||||
|
long startTime = printPuzzleStats ? System.nanoTime() : 0;
|
||||||
|
long startingBacktracks = worker.backtracks;
|
||||||
|
initialize(grid, worker.root);
|
||||||
|
var solution = search(worker.root, worker, 0);
|
||||||
|
if (printPuzzleStats) {
|
||||||
|
printStats(1, startTime, "Puzzle " + (g + 1),
|
||||||
|
worker.backtracks - startingBacktracks);
|
||||||
|
}
|
||||||
|
if (i == 0 && (printGrid || (verifySolution && !verify(solution, grid)))) {
|
||||||
|
printGrids("Puzzle " + (g + 1), grid, solution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++worker.puzzlesSolved;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +166,8 @@ public class Sudoku {
|
|||||||
//////////////////////////////// Utility functions ////////////////////////////////
|
//////////////////////////////// Utility functions ////////////////////////////////
|
||||||
|
|
||||||
/** Return an array of all squares in the intersection of these rows and cols **/
|
/** Return an array of all squares in the intersection of these rows and cols **/
|
||||||
int[] cross(int[] rows, int[] cols) {
|
static int[] cross(int[] rows, int[] cols) {
|
||||||
int[] result = new int[rows.length * cols.length];
|
var result = new int[rows.length * cols.length];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int r : rows) {
|
for (int r : rows) {
|
||||||
for (int c : cols) {
|
for (int c : cols) {
|
||||||
@@ -171,10 +178,10 @@ public class Sudoku {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Return true iff item is an element of array. **/
|
/** Return true iff item is an element of array. **/
|
||||||
boolean member(int item, int[] array) { return member(item, array, array.length); }
|
static boolean member(int item, int[] array) { return member(item, array, array.length); }
|
||||||
|
|
||||||
/** Return true iff item appears within array[0..end). **/
|
/** Return true iff item appears within array[0..end). **/
|
||||||
boolean member(int item, int[] array, int end) {
|
static boolean member(int item, int[] array, int end) {
|
||||||
for (int i = 0; i < end; ++i) {
|
for (int i = 0; i < end; ++i) {
|
||||||
if (array[i] == item) return true;
|
if (array[i] == item) return true;
|
||||||
}
|
}
|
||||||
@@ -184,20 +191,21 @@ public class Sudoku {
|
|||||||
|
|
||||||
//////////////////////////////// Constants ////////////////////////////////
|
//////////////////////////////// Constants ////////////////////////////////
|
||||||
|
|
||||||
final int N = 9;
|
static final int N = 9; // Number of distinct digits, also length of side of grid
|
||||||
final int[] DIGITS = {1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, 1<<8};
|
static final int[] DIGITS = {1<<0, 1<<1, 1<<2, 1<<3, 1<<4, 1<<5, 1<<6, 1<<7, 1<<8};
|
||||||
final int ALL_DIGITS = Integer.parseInt("111111111", 2);
|
static final int ALL_DIGITS = 0b111111111;
|
||||||
final int[] ROWS = IntStream.range(0, N).toArray();
|
static final int[] ROWS = IntStream.range(0, N).toArray();
|
||||||
final int[] COLS = ROWS;
|
static final int[] COLS = ROWS;
|
||||||
final int[] SQUARES = IntStream.range(0, N * N).toArray();
|
static final int[] SQUARES = IntStream.range(0, N * N).toArray();
|
||||||
final int[][] BLOCKS = {{0,1,2},{3,4,5},{6,7,8}};
|
static final int[][] BLOCKS = {{0,1,2},{3,4,5},{6,7,8}};
|
||||||
final int[][] ALL_UNITS = new int[3 * N][];
|
static final int[][] ALL_UNITS = new int[3 * N][];
|
||||||
final int[][][] UNITS = new int[N * N][3][N];
|
static final int[][][] UNITS = new int[N * N][3][N];
|
||||||
final int[][] PEERS = new int[N * N][20];
|
static final int[][] PEERS = new int[N * N][20];
|
||||||
final int[] NUM_DIGITS = new int[ALL_DIGITS + 1];
|
static final int[] NUM_DIGITS = new int[ALL_DIGITS + 1];
|
||||||
final int[] HIGHEST_DIGIT = new int[ALL_DIGITS + 1];
|
static final int[] HIGHEST_DIGIT = new int[ALL_DIGITS + 1];
|
||||||
|
|
||||||
{
|
|
||||||
|
static { // Initialize UNITS, ALL_UNITS, PEERS, NUM_DIGITS, HIGHEST_DIGIT
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int r : ROWS) { ALL_UNITS[i++] = cross(new int[]{r}, COLS); }
|
for (int r : ROWS) { ALL_UNITS[i++] = cross(new int[]{r}, COLS); }
|
||||||
for (int c : COLS) { ALL_UNITS[i++] = cross(ROWS, new int[]{c}); }
|
for (int c : COLS) { ALL_UNITS[i++] = cross(ROWS, new int[]{c}); }
|
||||||
@@ -234,17 +242,16 @@ public class Sudoku {
|
|||||||
|
|
||||||
/** Search for a solution to grid. If there is an unfilled square, select one
|
/** Search for a solution to grid. If there is an unfilled square, select one
|
||||||
** and try--that is, search recursively--every possible digit for the square. **/
|
** and try--that is, search recursively--every possible digit for the square. **/
|
||||||
int[] search(int[] grid, int[][] gridpool, int level) {
|
int[] search(int[] grid, WorkerState worker, int level) {
|
||||||
if (grid == null) return null;
|
if (grid == null) return null;
|
||||||
int s = select_square(grid);
|
int s = select_square(grid);
|
||||||
if (s == -1) return grid; // All squares filled — puzzle is solved.
|
if (s == -1) return grid; // All squares filled — puzzle is solved.
|
||||||
for (int d : DIGITS) {
|
for (int candidates = grid[s]; candidates != 0; candidates &= candidates - 1) {
|
||||||
if ((d & grid[s]) > 0) {
|
int d = candidates & -candidates; // lowest set bit
|
||||||
System.arraycopy(grid, 0, gridpool[level], 0, grid.length);
|
System.arraycopy(grid, 0, worker.gridpool[level], 0, grid.length);
|
||||||
int[] result = search(fill(gridpool[level], s, d), gridpool, level + 1);
|
var result = search(fill(worker.gridpool[level], s, d), worker, level + 1);
|
||||||
if (result != null) return result;
|
if (result != null) return result;
|
||||||
backtracks.incrementAndGet(); // thread-safe
|
++worker.backtracks;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -285,7 +292,7 @@ public class Sudoku {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Fill grid[s] = d. Return null if this creates a contradiction. **/
|
/** Fill grid[s] = d. Return grid, or return null if this creates a contradiction. **/
|
||||||
int[] fill(int[] grid, int s, int d) {
|
int[] fill(int[] grid, int s, int d) {
|
||||||
if (grid == null || (grid[s] & d) == 0) return null;
|
if (grid == null || (grid[s] & d) == 0) return null;
|
||||||
grid[s] = d;
|
grid[s] = d;
|
||||||
@@ -337,7 +344,7 @@ public class Sudoku {
|
|||||||
|
|
||||||
|
|
||||||
/** If two squares in a unit share exactly the same two possible values, eliminate
|
/** If two squares in a unit share exactly the same two possible values, eliminate
|
||||||
** those values from every other square in that unit (naked pairs strategy). **/
|
** those values from every other square in that unit. **/
|
||||||
boolean naked_pairs(int[] grid, int s) {
|
boolean naked_pairs(int[] grid, int s) {
|
||||||
if (!runNakedPairs || NUM_DIGITS[grid[s]] != 2) return true;
|
if (!runNakedPairs || NUM_DIGITS[grid[s]] != 2) return true;
|
||||||
int val = grid[s];
|
int val = grid[s];
|
||||||
@@ -364,10 +371,11 @@ public class Sudoku {
|
|||||||
|
|
||||||
//////////////////////////////// Input ////////////////////////////////
|
//////////////////////////////// Input ////////////////////////////////
|
||||||
|
|
||||||
/** Read one puzzle per line from filename and return a list of puzzle grids. **/
|
/** Read one puzzle per line from filename and return a list of puzzle grids.
|
||||||
|
** if -reverse option is true, also include the reversed puzzle. **/
|
||||||
List<int[]> readPuzzlesFromFile(String filename) throws IOException {
|
List<int[]> readPuzzlesFromFile(String filename) throws IOException {
|
||||||
try (BufferedReader in = new BufferedReader(new FileReader(filename))) {
|
try (var in = new BufferedReader(new FileReader(filename))) {
|
||||||
List<int[]> grids = new ArrayList<>(1000);
|
var grids = new ArrayList<int[]>(400);
|
||||||
String gridstring;
|
String gridstring;
|
||||||
while ((gridstring = in.readLine()) != null) {
|
while ((gridstring = in.readLine()) != null) {
|
||||||
grids.add(parseGrid(gridstring));
|
grids.add(parseGrid(gridstring));
|
||||||
@@ -382,7 +390,7 @@ public class Sudoku {
|
|||||||
|
|
||||||
/** Parse a gridstring into a puzzle grid. **/
|
/** Parse a gridstring into a puzzle grid. **/
|
||||||
int[] parseGrid(String gridstring) {
|
int[] parseGrid(String gridstring) {
|
||||||
int[] grid = new int[N * N];
|
var grid = new int[N * N];
|
||||||
int s = 0;
|
int s = 0;
|
||||||
for (int i = 0; i < gridstring.length(); ++i) {
|
for (int i = 0; i < gridstring.length(); ++i) {
|
||||||
char c = gridstring.charAt(i);
|
char c = gridstring.charAt(i);
|
||||||
@@ -401,24 +409,22 @@ public class Sudoku {
|
|||||||
|
|
||||||
|
|
||||||
/** Initialize a fresh grid from puzzle, then fill known squares to trigger constraint propagation. **/
|
/** Initialize a fresh grid from puzzle, then fill known squares to trigger constraint propagation. **/
|
||||||
int[] initialize(int[] puzzle) {
|
void initialize(int[] puzzle, int[] grid) {
|
||||||
int[] grid = new int[N * N];
|
|
||||||
Arrays.fill(grid, ALL_DIGITS);
|
Arrays.fill(grid, ALL_DIGITS);
|
||||||
for (int s : SQUARES) {
|
for (int s : SQUARES) {
|
||||||
if (puzzle[s] != ALL_DIGITS) fill(grid, s, puzzle[s]);
|
if (puzzle[s] != ALL_DIGITS) fill(grid, s, puzzle[s]);
|
||||||
}
|
}
|
||||||
return grid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////// Output and Tests ////////////////////////////////
|
//////////////////////////////// Output and Tests ////////////////////////////////
|
||||||
|
|
||||||
/** Print stats: puzzles solved, average µs, KHz, threads, backtracks, and name. **/
|
/** Print stats: puzzles solved, average µs, KHz, threads, backtracks, and name. **/
|
||||||
void printStats(int nGrids, long startTime, String name) {
|
void printStats(int nGrids, long startTime, String name, long backtracks) {
|
||||||
double usecs = (System.nanoTime() - startTime) / 1_000.0;
|
double usecs = (System.nanoTime() - startTime) / 1_000.0;
|
||||||
int bt = backtracks.getAndSet(0); // thread-safe
|
|
||||||
String line = String.format("%7d %6.1f %7.3f %7d %10.1f %s",
|
String line = String.format("%7d %6.1f %7.3f %7d %10.1f %s",
|
||||||
nGrids, usecs / nGrids, 1_000 * nGrids / usecs, nThreads, bt * 1.0 / nGrids, name);
|
nGrids, usecs / nGrids, 1_000 * nGrids / usecs, nThreads,
|
||||||
|
backtracks * 1.0 / nGrids, name);
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (!headerPrinted) {
|
if (!headerPrinted) {
|
||||||
System.out.println("Puzzles μsec KHz Threads Backtracks Name\n"
|
System.out.println("Puzzles μsec KHz Threads Backtracks Name\n"
|
||||||
@@ -448,7 +454,7 @@ public class Sudoku {
|
|||||||
|
|
||||||
/** Return a String representing one row of the grid. **/
|
/** Return a String representing one row of the grid. **/
|
||||||
String rowString(int[] grid, int r) {
|
String rowString(int[] grid, int r) {
|
||||||
StringBuilder row = new StringBuilder(30);
|
var row = new StringBuilder(30);
|
||||||
for (int s = r * 9; s < (r + 1) * 9; ++s) {
|
for (int s = r * 9; s < (r + 1) * 9; ++s) {
|
||||||
int nd = NUM_DIGITS[grid[s]];
|
int nd = NUM_DIGITS[grid[s]];
|
||||||
char cell = nd == 9 ? '.' : nd != 1 ? '?' : (char)('1' + Integer.numberOfTrailingZeros(grid[s]));
|
char cell = nd == 9 ? '.' : nd != 1 ? '?' : (char)('1' + Integer.numberOfTrailingZeros(grid[s]));
|
||||||
@@ -457,20 +463,4 @@ public class Sudoku {
|
|||||||
}
|
}
|
||||||
return row.toString();
|
return row.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Unit Tests. **/
|
|
||||||
void runUnitTests() {
|
|
||||||
assert N == 9;
|
|
||||||
assert SQUARES.length == 81;
|
|
||||||
for (int s : SQUARES) {
|
|
||||||
assert UNITS[s].length == 3;
|
|
||||||
assert PEERS[s].length == 20;
|
|
||||||
}
|
|
||||||
assert Arrays.equals(PEERS[19],
|
|
||||||
new int[]{18,20,21,22,23,24,25,26,1,10,28,37,46,55,64,73,0,2,9,11});
|
|
||||||
assert Arrays.deepToString(UNITS[19]).equals(
|
|
||||||
"[[18, 19, 20, 21, 22, 23, 24, 25, 26], [1, 10, 19, 28, 37, 46, 55, 64, 73], [0, 1, 2, 9, 10, 11, 18, 19, 20]]");
|
|
||||||
System.out.println("Unit tests pass.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+91
-91
@@ -4,9 +4,9 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"<div style=\"text-align:right\" align=\"right\"><i>Peter Norvig<br>Oct. 2021</i></div>\n",
|
"<div style=\"text-align:right\" align=\"right\"><i>Peter Norvig<br>2007, 2026</i></div>\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Solving Sudoku at 200,000 puzzles per second\n",
|
"# Solving Sudoku at 400,000 puzzles per second\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The rules of [**Sudoku**](http://en.wikipedia.org/wiki/Sudoku) are [simple and finite](https://clip.cafe/legally-blonde-2001/the-rules-of-hair-care-are-simple-finite-s1/?srsltid=AfmBOoqLAWkreHwp-ZaP5JeYIlVYwljjgG-7aHmt-kLQ_D0q8zvxd4GG): fill in the empty squares in the puzzle so that each column, row, and 3x3 box in the solution contains all the digits from 1 to 9. For example:\n",
|
"The rules of [**Sudoku**](http://en.wikipedia.org/wiki/Sudoku) are [simple and finite](https://clip.cafe/legally-blonde-2001/the-rules-of-hair-care-are-simple-finite-s1/?srsltid=AfmBOoqLAWkreHwp-ZaP5JeYIlVYwljjgG-7aHmt-kLQ_D0q8zvxd4GG): fill in the empty squares in the puzzle so that each column, row, and 3x3 box in the solution contains all the digits from 1 to 9. For example:\n",
|
||||||
"\n",
|
"\n",
|
||||||
@@ -15,7 +15,9 @@
|
|||||||
"|||\n",
|
"|||\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"In 2006, I wrote a [Python program](http://norvig.com/sudoku.html) to solve Sudoku. Soon after that, [Peter Seibel](https://gigamonkeys.com/) invited me to publish an article in [*Code Quarterly*](https://gigamonkeys.com/code-quarterly/) magazine which would go into more detail about backtracking search and constraint propagation in general, and would feature a more efficient program. Unfortunately, the magazine [folded](https://gigamonkeys.wordpress.com/2011/10/17/end-of-the-line-for-code-quarterly/) before I could finish the article. In 2021 I [updated the Python Sudoku program](Sudoku.ipynb) to Jupyter notebook form, with modern coding idioms, and now I do the same for the [more efficient Java Sudoku program](Sudoku.java). (I apologize for some remaining legacy Java idioms from decades ago).\n",
|
"In 2006, I wrote a [Python program](http://norvig.com/sudoku.html) to solve Sudoku. Soon after that, [Peter Seibel](https://gigamonkeys.com/) invited me to publish an article in [*Code Quarterly*](https://gigamonkeys.com/code-quarterly/) magazine which would go into more detail about backtracking search and constraint propagation in general, and would feature a more efficient program. Unfortunately, the magazine [folded](https://gigamonkeys.wordpress.com/2011/10/17/end-of-the-line-for-code-quarterly/) before I could finish the article. In 2021 I [updated the Python Sudoku program](Sudoku.ipynb) to Jupyter notebook form, with modern coding idioms, and now I do the same for the [more efficient Java Sudoku program](Sudoku.java). \n",
|
||||||
|
"\n",
|
||||||
|
"(I asked Claude and Codex to help me modernize my Java, and I apologize for any remaining legacy Java idioms from decades ago. It turns out the AI coding tools didn't have any significant suggestions for how to make the code faster.)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"- [**Sudoku.java**](Sudoku.java) is the Java program discussed in this notebook.\n",
|
"- [**Sudoku.java**](Sudoku.java) is the Java program discussed in this notebook.\n",
|
||||||
"- [**Sudoku.ipynb**](Sudoku.ipynb) is the Python notebook."
|
"- [**Sudoku.ipynb**](Sudoku.ipynb) is the Python notebook."
|
||||||
@@ -89,7 +91,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Command Line Options\n",
|
"# Command Line Options\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Here are all the options for the `java Sudoku` command:"
|
"Here are all the options for my `java Sudoku` command:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -102,7 +104,9 @@
|
|||||||
"output_type": "stream",
|
"output_type": "stream",
|
||||||
"text": [
|
"text": [
|
||||||
"usage: java Sudoku -(no)[fghnprstuv] | -[RT]<number> | <filename> ...\n",
|
"usage: java Sudoku -(no)[fghnprstuv] | -[RT]<number> | <filename> ...\n",
|
||||||
"E.g., -v turns verify flag on, -nov turns it off. -R and -T require a number. The options:\n",
|
"\n",
|
||||||
|
"Options and filenames are processed left-to-right. Use '-no' to turn an option off\n",
|
||||||
|
"E.g.: -v turns verify flag on, -nov turns it off. -R and -T require a number. The options:\n",
|
||||||
"\n",
|
"\n",
|
||||||
" -g(rid) Print each puzzle grid and solution grid (default off)\n",
|
" -g(rid) Print each puzzle grid and solution grid (default off)\n",
|
||||||
" -h(elp) Print this usage message\n",
|
" -h(elp) Print this usage message\n",
|
||||||
@@ -111,10 +115,9 @@
|
|||||||
" -r(everse) Solve the reverse of each puzzle as well as each puzzle itself (default off)\n",
|
" -r(everse) Solve the reverse of each puzzle as well as each puzzle itself (default off)\n",
|
||||||
" -s(ummary) Print per-file summary stats (default on)\n",
|
" -s(ummary) Print per-file summary stats (default on)\n",
|
||||||
" -t(hread) Print summary stats for each thread (default off)\n",
|
" -t(hread) Print summary stats for each thread (default off)\n",
|
||||||
" -u(nitTest) Run a suite of unit tests (default off)\n",
|
|
||||||
" -v(erify) Verify each solution is valid (default on)\n",
|
" -v(erify) Verify each solution is valid (default on)\n",
|
||||||
" -T<number> Concurrently run <number> threads (default 25)\n",
|
" -T<number> Concurrently run <number> threads (default 28)\n",
|
||||||
" -R<number> Repeat each puzzle <number> times (default 1)\n",
|
" -R<number> Repeat the solving of each puzzle <number> times (default 1)\n",
|
||||||
" <filename> Solve all puzzles in filename, which has one puzzle per line\n"
|
" <filename> Solve all puzzles in filename, which has one puzzle per line\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,7 @@
|
|||||||
"- [sudokus_17.txt](sudokus_17.txt): Puzzles with exactly 17 filled-in squares, from [Gordon Royle at the University of Western Australia](https://web.archive.org/web/20131019184812if_/http://school.maths.uwa.edu.au/~gordon/sudokumin.php).\n",
|
"- [sudokus_17.txt](sudokus_17.txt): Puzzles with exactly 17 filled-in squares, from [Gordon Royle at the University of Western Australia](https://web.archive.org/web/20131019184812if_/http://school.maths.uwa.edu.au/~gordon/sudokumin.php).\n",
|
||||||
"- [sudokus.txt](sudokus.txt): My collection, from various sources, and some automatically generated (by permuting rows, or swapping digits).\n",
|
"- [sudokus.txt](sudokus.txt): My collection, from various sources, and some automatically generated (by permuting rows, or swapping digits).\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Hee are the number of puzzles in each file:"
|
"Here are the number of puzzles in each file:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -161,7 +164,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"Let's run the `java Sudoku` program on all these puzzles. To get even more puzzles (more puzzles should lower variance), I use the `-r` option to include the reverse of each puzzle (the last square becomes the first, etc.) and `-R` to repeat puzzles."
|
"Let's run the `java Sudoku` program on all these puzzles, and show the timing results. To get even more puzzles (more puzzles should lower timing variance), I use the `-r` option to include the reverse of each puzzle (the last square becomes the first, etc.) and `-R` to repeat puzzles."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -175,32 +178,30 @@
|
|||||||
"text": [
|
"text": [
|
||||||
"Puzzles μsec KHz Threads Backtracks Name\n",
|
"Puzzles μsec KHz Threads Backtracks Name\n",
|
||||||
"======= ====== ======= ======= ========== ====\n",
|
"======= ====== ======= ======= ========== ====\n",
|
||||||
" 3000 60.0 16.668 25 330.7 sudokus_hard.txt\n",
|
" 30000 28.3 35.305 28 330.7 sudokus_hard.txt\n",
|
||||||
" 196604 4.8 210.408 25 41.7 sudokus_17.txt\n",
|
" 983020 2.0 493.460 28 41.7 sudokus_17.txt\n",
|
||||||
"1000000 3.2 311.832 25 32.5 sudokus.txt\n"
|
"1000000 2.5 397.963 28 32.5 sudokus.txt\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"!java Sudoku -r -R4 sudokus_hard.txt -R2 sudokus_17.txt sudokus.txt "
|
"!java Sudoku -r -R40 sudokus_hard.txt -R10 sudokus_17.txt -R2 sudokus.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
|
"**Conclusion: The program solves about 35,000 puzzles per second with the hardest puzzles, and about 400,000 per second with easier puzzles.** This is a pretty good performance for such a simple program! One of the fastest known Sudoku solvers, [**tdoku**](https://github.com/t-dillon/tdoku), reports similar speed (but that was in 2020, so they probably used a slower computer than my 28-core Mac Studio M3 Ultra).\n",
|
||||||
|
"\n",
|
||||||
"The columns of the output are:\n",
|
"The columns of the output are:\n",
|
||||||
"\n",
|
"\n",
|
||||||
"- `Puzzles`: the total number of puzzles in each file.\n",
|
"- `Puzzles`: the total number of puzzles solved.\n",
|
||||||
"- `μsec`: the mean time to solve a puzzle in microseconds (millionths of a second).\n",
|
"- `μsec`: the mean time to solve a puzzle in microseconds (millionths of a second).\n",
|
||||||
"- `KHz`: the throughput expressed as thousands of puzzles solved per second (the inverse of the μsec column).\n",
|
"- `KHz`: the throughput expressed as thousands of puzzles solved per second (the inverse of the μsec column).\n",
|
||||||
"- `Threads`: the number of concurrent threads used (the default is 25).\n",
|
"- `Threads`: the number of concurrent threads used.\n",
|
||||||
"- `Backtracks`: the average number of times per puzzle that the search guessed wrong and had to back up.\n",
|
"- `Backtracks`: the average number of times per puzzle that the search guessed wrong and had to back up.\n",
|
||||||
"- `Name`: here the name of the file; could also be a puzzle number.\n",
|
"- `Name`: here the name of the file; could also be a puzzle number when -p option is on.\n",
|
||||||
"\n",
|
|
||||||
"**Conclusion: The program solves about 16,000 puzzles per second with the hardest puzzles, and 200,000 to 300,000 per second with easier puzzles.**\n",
|
|
||||||
"\n",
|
|
||||||
"This is a pretty good performance for such a simple program! One of the fastest known Sudoku solvers, [**tdoku**](https://github.com/t-dillon/tdoku), is twice as fast on the hardest puzzles, and only about 15% faster for the 17-clue puzzles, but is a much more complex program.\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Note that the μsec and KHz columns are displaying **throughput**: the number of puzzles that can be solved in a given amount of time. This is different than **latency**: the time it takes to solve a single puzzle from start to end. Throughput and latency are different because there are multiple threads working in parallel. If, say, there are 25 threads working in parallel on 25 CPUs and each puzzle takes 1 second to solve, then latency would be 1 second but throughput would be 25 puzzles per second. I did some experiments, and on my machine (an M3 Ultra Mac Studio), the best performance is somewhere around 25 threads (although there is 5% to 10% variance from run to run, so I couldn't nail down an optimal number). \n",
|
"Note that the μsec and KHz columns are displaying **throughput**: the number of puzzles that can be solved in a given amount of time. This is different than **latency**: the time it takes to solve a single puzzle from start to end. Throughput and latency are different because there are multiple threads working in parallel. If, say, there are 25 threads working in parallel on 25 CPUs and each puzzle takes 1 second to solve, then latency would be 1 second but throughput would be 25 puzzles per second. I did some experiments, and on my machine (an M3 Ultra Mac Studio), the best performance is somewhere around 25 threads (although there is 5% to 10% variance from run to run, so I couldn't nail down an optimal number). \n",
|
||||||
@@ -219,9 +220,9 @@
|
|||||||
"text": [
|
"text": [
|
||||||
"Puzzles μsec KHz Threads Backtracks Name\n",
|
"Puzzles μsec KHz Threads Backtracks Name\n",
|
||||||
"======= ====== ======= ======= ========== ====\n",
|
"======= ====== ======= ======= ========== ====\n",
|
||||||
" 3000 250.5 3.992 1 292.2 sudokus_hard.txt\n",
|
" 3000 284.3 3.518 1 292.2 sudokus_hard.txt\n",
|
||||||
" 49151 65.1 15.363 1 40.2 sudokus_17.txt\n",
|
" 49151 69.4 14.417 1 40.2 sudokus_17.txt\n",
|
||||||
" 250000 61.6 16.238 1 34.2 sudokus.txt\n"
|
" 250000 66.5 15.032 1 34.2 sudokus.txt\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -233,7 +234,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"The average latency is about 250 microseconds for the hard puzzles and 60 to 65 microseconds for the easier puzzles."
|
"The average latency is about 300 microseconds for the hard puzzles and 70 microseconds for the easier puzzles."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -242,7 +243,7 @@
|
|||||||
"source": [
|
"source": [
|
||||||
"# Effect of Naked Pairs Strategy\n",
|
"# Effect of Naked Pairs Strategy\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The Java program explicitly uses the so-called [naked pairs strategy](https://www.learn-sudoku.com/naked-pairs.html) to eliminate some possible digits, whereas the Python program did not. We can check how much the naked pairs strategy helps by disabling it with the `-nonaked` option:"
|
"This Java program uses the so-called [naked pairs strategy](https://www.learn-sudoku.com/naked-pairs.html) to eliminate some possible digits, whereas my earlier Python program did not. The following invocation yields two lines of output, the first using the naked pairs strategy, the second without:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -256,20 +257,20 @@
|
|||||||
"text": [
|
"text": [
|
||||||
"Puzzles μsec KHz Threads Backtracks Name\n",
|
"Puzzles μsec KHz Threads Backtracks Name\n",
|
||||||
"======= ====== ======= ======= ========== ====\n",
|
"======= ====== ======= ======= ========== ====\n",
|
||||||
" 250000 10.7 93.850 25 34.2 sudokus.txt\n",
|
" 250000 3.1 325.734 28 34.2 sudokus.txt\n",
|
||||||
" 250000 13.6 73.619 25 109.1 sudokus.txt\n"
|
" 250000 4.8 207.398 28 109.1 sudokus.txt\n"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"source": [
|
"source": [
|
||||||
"!java Sudoku sudokus.txt -nonaked sudokus.txt"
|
"!java Sudoku -naked sudokus.txt -nonaked sudokus.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"The program is three times as fast with naked pairs compared to without. Perhaps implementing [other strategies](https://bestofsudoku.com/sudoku-strategy) could give similar speedups. However, my feeling is that the pr ogram is already fast enough, and there would probably be diminishing returns from further strategies."
|
"The program is three times as fast with naked pairs compared to without. Perhaps implementing [other strategies](https://bestofsudoku.com/sudoku-strategy) could give similar speedups. However, my feeling is that the program is already fast enough, and there would probably be diminishing returns from further strategies."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -280,7 +281,7 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"What's the distribution of puzzle run times, or of backtracks, across the benchmark puzzles? \n",
|
"What's the distribution of puzzle run times, or of backtracks, across the benchmark puzzles? \n",
|
||||||
"\n",
|
"\n",
|
||||||
"I can get data for individual puzzles with the `-p` option, then eliminate the header lines with `tail`."
|
"I can get data for individual puzzles with the `-p` option, eliminate the header lines with `tail`, and pipe the results to the file `sudata.txt`:"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -289,7 +290,7 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"!java Sudoku -T1 -p sudokus_17.txt sudokus_hard.txt | tail -n +3 > sudata.txt"
|
"!java Sudoku -T1 -p sudokus_17.txt sudokus_hard.txt | tail -n +3 > sudata.txt"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -339,44 +340,44 @@
|
|||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>mean</th>\n",
|
" <th>mean</th>\n",
|
||||||
" <td>74.740426</td>\n",
|
" <td>73.332460</td>\n",
|
||||||
" <td>21.117872</td>\n",
|
" <td>23.580335</td>\n",
|
||||||
" <td>42.138952</td>\n",
|
" <td>42.145663</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>std</th>\n",
|
" <th>std</th>\n",
|
||||||
" <td>135.958244</td>\n",
|
" <td>148.850484</td>\n",
|
||||||
" <td>9.120126</td>\n",
|
" <td>11.314832</td>\n",
|
||||||
" <td>187.339951</td>\n",
|
" <td>187.343129</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>min</th>\n",
|
" <th>min</th>\n",
|
||||||
" <td>11.800000</td>\n",
|
" <td>11.700000</td>\n",
|
||||||
" <td>0.118000</td>\n",
|
" <td>0.099000</td>\n",
|
||||||
" <td>0.000000</td>\n",
|
" <td>0.000000</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>25%</th>\n",
|
" <th>25%</th>\n",
|
||||||
" <td>37.700000</td>\n",
|
" <td>32.300000</td>\n",
|
||||||
" <td>15.152000</td>\n",
|
" <td>15.605000</td>\n",
|
||||||
" <td>1.000000</td>\n",
|
" <td>1.000000</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>50%</th>\n",
|
" <th>50%</th>\n",
|
||||||
" <td>45.200000</td>\n",
|
" <td>41.800000</td>\n",
|
||||||
" <td>22.120000</td>\n",
|
" <td>23.952000</td>\n",
|
||||||
" <td>5.000000</td>\n",
|
" <td>5.000000</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>75%</th>\n",
|
" <th>75%</th>\n",
|
||||||
" <td>66.000000</td>\n",
|
" <td>64.100000</td>\n",
|
||||||
" <td>26.549000</td>\n",
|
" <td>30.928000</td>\n",
|
||||||
" <td>24.000000</td>\n",
|
" <td>24.000000</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" <tr>\n",
|
" <tr>\n",
|
||||||
" <th>max</th>\n",
|
" <th>max</th>\n",
|
||||||
" <td>8487.500000</td>\n",
|
" <td>10142.500000</td>\n",
|
||||||
" <td>85.106000</td>\n",
|
" <td>85.412000</td>\n",
|
||||||
" <td>13438.000000</td>\n",
|
" <td>13438.000000</td>\n",
|
||||||
" </tr>\n",
|
" </tr>\n",
|
||||||
" </tbody>\n",
|
" </tbody>\n",
|
||||||
@@ -386,13 +387,13 @@
|
|||||||
"text/plain": [
|
"text/plain": [
|
||||||
" μsec KHz Backtracks\n",
|
" μsec KHz Backtracks\n",
|
||||||
"count 49528.000000 49528.000000 49528.000000\n",
|
"count 49528.000000 49528.000000 49528.000000\n",
|
||||||
"mean 74.740426 21.117872 42.138952\n",
|
"mean 73.332460 23.580335 42.145663\n",
|
||||||
"std 135.958244 9.120126 187.339951\n",
|
"std 148.850484 11.314832 187.343129\n",
|
||||||
"min 11.800000 0.118000 0.000000\n",
|
"min 11.700000 0.099000 0.000000\n",
|
||||||
"25% 37.700000 15.152000 1.000000\n",
|
"25% 32.300000 15.605000 1.000000\n",
|
||||||
"50% 45.200000 22.120000 5.000000\n",
|
"50% 41.800000 23.952000 5.000000\n",
|
||||||
"75% 66.000000 26.549000 24.000000\n",
|
"75% 64.100000 30.928000 24.000000\n",
|
||||||
"max 8487.500000 85.106000 13438.000000"
|
"max 10142.500000 85.412000 13438.000000"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"execution_count": 9,
|
"execution_count": 9,
|
||||||
@@ -412,7 +413,7 @@
|
|||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"The mean latency is 74 microseconds, the median is 45 microseconds, and the maximum is about 8 milliseconds. For backtracks, the mean is 42, the median is only 5, and the maximum is over 13,000. \n",
|
"The mean latency is 73 microseconds, the median is about 42 microseconds, and the maximum is about 10 milliseconds. For number of backtracks, the mean is 42, the median is only 5, and the maximum is over 13,000. \n",
|
||||||
"\n",
|
"\n",
|
||||||
"Here is a scatterplot:"
|
"Here is a scatterplot:"
|
||||||
]
|
]
|
||||||
@@ -424,7 +425,7 @@
|
|||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAlcAAAGwCAYAAACEkkAjAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAQD9JREFUeJzt3XtcVVXC//HvARRR8SgYIIoKxVSGZaEp2qSmqTNeZmpmrCzKpjGbvNFN63H61TSTVk+T2mhlTY/OdHmsppynpsYEx7wiOiipmXlDxQuRigct5HbW7w9ly+Em4IbDgc/79eL1Yu+9zt5rr2k8X9Zae22HMcYIAAAAtvDzdgUAAACaEsIVAACAjQhXAAAANiJcAQAA2IhwBQAAYCPCFQAAgI0IVwAAADYK8HYFfIXb7daRI0cUHBwsh8Ph7eoAAIAaMMbo1KlTioyMlJ9fw/QpEa5q6MiRI4qKivJ2NQAAQB1kZWWpS5cuDXItwlUNBQcHSzr7P067du28XBsAAFATeXl5ioqKsr7HGwLhqoZKhwLbtWtHuAIAwMc05JQeJrQDAADYiHAFAABgI8IVAACAjQhXAAAANiJcAQAA2IhwBQAAYCPCFQAAgI0IVwAAADYiXAEAANiIcAUAAGAjwhUAAICNCFcAAAA24sXNAADAa4pL3Fqwcq827T+hPt1DNGnwpQrw9+2+H8IVAADwmgUr92puyi4ZSev2HJMkTRsa691KXSTfjoYAAMCnbdp/Qubc7+bctq8jXAEAAK/p0z1EjnO/O85t+zqGBQEAgNdMGnypJHnMufJ1hCsAAOA1Af5+Pj/HqjyGBQEAAGxEuAIAALAR4QoAAMBGhCsAAAAbEa4AAABsRLgCAACwEeEKAADARoQrAAAAGxGuAAAAbES4AgAAsBHhCgAAwEaEKwAAABt5NVwVFxfrd7/7naKjoxUUFKSYmBg988wzcrvdVhljjJ5++mlFRkYqKChIgwYN0ldffeVxnoKCAk2ZMkUdO3ZUmzZtNGbMGB06dMijTG5urhITE+V0OuV0OpWYmKiTJ082xG0CAIBmxKvh6vnnn9drr72m+fPn6+uvv9YLL7yg//7v/9af//xnq8wLL7ygl156SfPnz9emTZsUERGhm2++WadOnbLKJCUlaenSpVqyZInWrl2r06dPa9SoUSopKbHKjBs3ThkZGVq2bJmWLVumjIwMJSYmNuj9AgCAps9hjDHeuvioUaMUHh6uN99809r3i1/8Qq1bt9Zbb70lY4wiIyOVlJSkGTNmSDrbSxUeHq7nn39eEydOlMvl0iWXXKK33npLt912myTpyJEjioqK0meffabhw4fr66+/Vo8ePbRhwwb17dtXkrRhwwYlJCRo586duvzyyyvUraCgQAUFBdZ2Xl6eoqKi5HK51K5du/psFgAAYJO8vDw5nc4G/f72as/VDTfcoBUrVmjXrl2SpC+//FJr167VT3/6U0lSZmamsrOzNWzYMOszgYGBGjhwoNavXy9JSk9PV1FRkUeZyMhIxcXFWWVSU1PldDqtYCVJ/fr1k9PptMqUN3v2bGsI0el0Kioqyt6bBwAATVKANy8+Y8YMuVwuXXHFFfL391dJSYmeffZZ3XHHHZKk7OxsSVJ4eLjH58LDw3XgwAGrTMuWLdWhQ4cKZUo/n52drbCwsArXDwsLs8qU98QTT+jhhx+2tkt7rgAAAKrj1XD13nvv6e2339a7776rq666ShkZGUpKSlJkZKTuueceq5zD4fD4nDGmwr7yypeprHx15wkMDFRgYGBtbgcAAMC74eqxxx7T448/rttvv12S1LNnTx04cECzZ8/WPffco4iICElne546depkfS4nJ8fqzYqIiFBhYaFyc3M9eq9ycnLUv39/q8y3335b4frfffddhV4xAACAi+HVOVc//PCD/Pw8q+Dv728txRAdHa2IiAglJydbxwsLC7Vq1SorOMXHx6tFixYeZY4ePart27dbZRISEuRyubRx40arTFpamlwul1UGAADADl7tuRo9erSeffZZde3aVVdddZW2bNmil156Sb/+9a8lnR3KS0pK0qxZsxQbG6vY2FjNmjVLrVu31rhx4yRJTqdT9913nx555BGFhoYqJCREjz76qHr27KmhQ4dKkq688kqNGDFCEyZM0MKFCyVJ999/v0aNGlXpk4IAAAB15dVw9ec//1lPPvmkHnzwQeXk5CgyMlITJ07U//t//88qM336dOXn5+vBBx9Ubm6u+vbtq+XLlys4ONgqM2fOHAUEBGjs2LHKz8/XkCFDtHjxYvn7+1tl3nnnHU2dOtV6qnDMmDGaP39+w90sAABoFry6zpUv8cY6GQAA4OI0u3WuAAAAmhrCFQAAgI0IVwAAADYiXAEAANiIcAUA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 truncated
|
"image/png": "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 truncated
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"<Figure size 640x480 with 1 Axes>"
|
"<Figure size 640x480 with 1 Axes>"
|
||||||
]
|
]
|
||||||
@@ -463,7 +464,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"data": {
|
"data": {
|
||||||
"image/png": "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 truncated
|
"image/png": "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 truncated
|
||||||
"text/plain": [
|
"text/plain": [
|
||||||
"<Figure size 640x480 with 1 Axes>"
|
"<Figure size 640x480 with 1 Axes>"
|
||||||
]
|
]
|
||||||
@@ -504,15 +505,16 @@
|
|||||||
" - If a check fails, back up a level in the search.\n",
|
" - If a check fails, back up a level in the search.\n",
|
||||||
"\n",
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"The [Python program](http://norvig.com/sudoku.html) was written for clarity and brevity. The [Java program](Sudoku.java) for efficiency. It uses these tricks:\n",
|
"The [Python program](http://norvig.com/sudoku.html) was written for clarity and brevity. The [Java program](Sudoku.java) for efficiency. The Java program uses these tricks:\n",
|
||||||
"- Primitive Java data types are used:\n",
|
"- Primitive Java data types are used:\n",
|
||||||
" - A grid is an `int[81]` array, not a hash table. \n",
|
" - A grid is an `int[81]` array, not a hash table. \n",
|
||||||
" - A square is an int (like `8`), not a string (like `'A9'`).\n",
|
" - A square is an int (like `8`), not a string (like `'A9'`).\n",
|
||||||
" - A set of possible digits is an int bitset (like `0b110010100`) not a string of digits (like `'9853'`). \n",
|
" - A set of possible digits is bitset represented by an int (like `0b110010100`, aka 404), not a string of digits (like `'9853'`). \n",
|
||||||
"- Multiple puzzles are solved in parallel, in different threads.\n",
|
"- Multiple puzzles are solved in parallel, in different threads.\n",
|
||||||
"- Rather than allocating (and then garbage collecting) a new copy of the grid at each step in the depth-first search, instead we pre-allocate a `gridpool` array of grids once and for all (at the start of each thread), and then every recursive call to `search` re-uses `gridpool[level]`. \n",
|
"- Rather than allocating (and then garbage collecting) a new copy of the grid at each step in the depth-first search, instead we pre-allocate a `gridpool` array of grids once and for all (at the start of each thread), and then every recursive call to `search` re-uses `gridpool[level]`, copying data into that array. \n",
|
||||||
"- The specific Sudoku strategy of [naked pairs](https://www.learn-sudoku.com/naked-pairs.html) is (optionally) implemented.\n",
|
"- The specific Sudoku strategy of [naked pairs](https://www.learn-sudoku.com/naked-pairs.html) is (optionally) implemented.\n",
|
||||||
"- The Java compiler produces code that is inherently faster than the Python interpreter.\n",
|
"- The Java compiler produces code that is inherently faster than the Python interpreter.\n",
|
||||||
|
"- To warm up Java's JIT (just-in-time) code cache, I solve a few puzzles before reporting any timing results. \n",
|
||||||
" "
|
" "
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -534,25 +536,8 @@
|
|||||||
" - Each unit in the solution contains all nine digits.\n",
|
" - Each unit in the solution contains all nine digits.\n",
|
||||||
" - Squares in the puzzle that are filled with a digit keep that same digit in the solution.\n",
|
" - Squares in the puzzle that are filled with a digit keep that same digit in the solution.\n",
|
||||||
"- I have looked at some example solutions and double-checked some with an [online Sudoku](https://sudokuspoiler.azurewebsites.net/) program. [LGTM](https://www.dictionary.com/e/acronyms/lgtm/).\n",
|
"- I have looked at some example solutions and double-checked some with an [online Sudoku](https://sudokuspoiler.azurewebsites.net/) program. [LGTM](https://www.dictionary.com/e/acronyms/lgtm/).\n",
|
||||||
"- The `-u` option runs a suite of unit tests.\n",
|
"- I have looked at the code carefully, but I am nowhere near a proof of correctness.\n",
|
||||||
"- I have looked at the code carefully, but I am nowhere near a proof of correctness.\n"
|
"- It would be nice to have a suite of unit tests."
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": 12,
|
|
||||||
"metadata": {},
|
|
||||||
"outputs": [
|
|
||||||
{
|
|
||||||
"name": "stdout",
|
|
||||||
"output_type": "stream",
|
|
||||||
"text": [
|
|
||||||
"Unit tests pass.\n"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"source": [
|
|
||||||
"!java Sudoku -u "
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -564,7 +549,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": 13,
|
"execution_count": 12,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
{
|
{
|
||||||
@@ -718,22 +703,37 @@
|
|||||||
"\n",
|
"\n",
|
||||||
"There are a few things I didn't get a chance to explore; maybe you can:\n",
|
"There are a few things I didn't get a chance to explore; maybe you can:\n",
|
||||||
"\n",
|
"\n",
|
||||||
"- Would the program be even faster in Golang or C++ or Rust? Or with some more optimizations?\n",
|
"- Would the program be even faster in Golang or C++ or Rust?\n",
|
||||||
|
"- Are there more algorithmic optimizations to try?\n",
|
||||||
"- The depth-first search makes a choice to *fill* some square with a digit. What if instead the choice was to *eliminate* a digit from the square? At first glance it seems that would be slower, because more choices would be required, but would constraint propagation make it work well?\n",
|
"- The depth-first search makes a choice to *fill* some square with a digit. What if instead the choice was to *eliminate* a digit from the square? At first glance it seems that would be slower, because more choices would be required, but would constraint propagation make it work well?\n",
|
||||||
"- On each recursive call, the depth-first search copies the current grid into a new grid (re-using the one in `gridpool[level]`). That requires copying an array of 81 ints. Would it be faster to instead make changes directly to the current grid, and then undo the changes when failure is detected? You would need to keep track of the changes made so that they can be undone. My guess is that this would make the code more complex and not much faster (if any), but you might want to try it.\n",
|
"- On each recursive call, the depth-first search copies the current grid into a new grid (re-using the one in `gridpool[level]`). That requires copying an array of 81 ints. Would it be faster to instead make changes directly to the current grid, and then undo the changes when failure is detected? You would need to keep track of the changes made so that they can be undone. My guess is that this would make the code more complex and not much faster (if any), but you might want to try it.\n",
|
||||||
"- In the theory of constraint propagation, [shaving](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.175.7143&rep=rep1&type=pdf) means guessing a value for some variable, detecting a contradiction, and then keeping track of the fact that the value is not possible. But in our program, when we guess wrong we don't keep track of anything. Can the program be made faster by incorporating shaving?\n",
|
"- In the theory of constraint propagation, [shaving](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.175.7143&rep=rep1&type=pdf) means guessing a value for some variable, detecting a contradiction, and then keeping track of the fact that the value is not possible. But in our program, when we guess wrong we don't keep track of anything. Can the program be made faster by incorporating shaving?\n",
|
||||||
"- Can you create an adversarial puzzle, where the program guesses wrong the maximal number of times? In other words, if you draw a tree of choices, what's a puzzle with a tree that has the solution in the bottom-right corner? How much time and how many backtracks does that puzzle take to solve?\n",
|
"- Can you create an adversarial puzzle, where the program guesses wrong the maximal number of times? In other words, if you draw a tree of choices, what's a puzzle with a tree that has the solution in the bottom-right corner? How much time and how many backtracks does that puzzle take to solve?\n",
|
||||||
"- What [other Sudoku strategies](https://bestofsudoku.com/sudoku-strategy) can be implemented? Can you find a suite of strategies that will solve all the puzzles with no search? \n",
|
"- What [other Sudoku strategies](https://bestofsudoku.com/sudoku-strategy) can be implemented? Can you find a suite of strategies that will solve all the puzzles with no search? \n",
|
||||||
"- Can you develop a system to rank the difficulty of puzzles, based on the complexity of the strategies needed to solve it?\n",
|
"- Can you develop a system to rank the difficulty of puzzles, based on the complexity of the strategies needed to solve it?\n"
|
||||||
"\n",
|
|
||||||
"One final word: I got around to writing this up after a friend mentioned to me \"*Hey, a while back didn't you do a Sudoku program that could solve like **a dozen puzzles a second** or something?*\" I felt like Robert Wagner when he had to explain to Dr. Evil that \"*a million dollars isn't exactly a lot of money these days.*\" So this is for all of you who were cryogenically frozen in 1967 (and for all of you who weren't).\n",
|
|
||||||
"\n",
|
|
||||||
"| | |\n",
|
|
||||||
"|---|---|\n",
|
|
||||||
"|||\n",
|
|
||||||
"|\"*One **dozen** puzzles per second!*|*A dozen isn't exactly a lot of throughput these days...*|\n",
|
|
||||||
"</center>"
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"One final word: I got around to writing this up after a friend mentioned to me \"*Hey, a while back didn't you do a Sudoku program that could solve like **a dozen puzzles a second** or something?*\" I felt like Robert Wagner when he had to explain to Dr. Evil that \"*a million dollars isn't exactly a lot of money these days.*\" \n",
|
||||||
|
"\n",
|
||||||
|
"<img src=\"https://upload.wikimedia.org/wikipedia/en/1/16/Drevil_million_dollars.jpg\" height=\"249\" width=\"201\">\n",
|
||||||
|
"\n",
|
||||||
|
"*One **dozen** puzzles per second!*\n",
|
||||||
|
"\n",
|
||||||
|
"<img src=\"Number_2.png\" height=\"249\" width=\"201\">\n",
|
||||||
|
"\n",
|
||||||
|
"*A dozen isn't exactly a lot of throughput these days...*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
+49458
-49458
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user