Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial checkin

  • Loading branch information...
commit 22302238eeffc85d0d44b68ef92d00ce8bfe11bc 1 parent 1d05366
Rolf rolfl authored
Showing with 1,736 additions and 0 deletions.
  1. +6 −0 .classpath
  2. +1 −0  .gitignore
  3. +17 −0 .project
  4. +3 −0  .settings/org.eclipse.core.resources.prefs
  5. +4 −0 input/Sudoku.txt
  6. +9 −0 input/Sudoku1.txt
  7. +9 −0 input/Sudoku1Mod.txt
  8. +9 −0 input/Sudoku2.txt
  9. +9 −0 input/SudokuExample.txt
  10. +9 −0 input/SudokuHard.txt
  11. +9 −0 input/SudokuMed.txt
  12. +9 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Block.java
  13. +125 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Cell.java
  14. +9 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Column.java
  15. +81 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/DigitSet.java
  16. +115 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Grid.java
  17. +67 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/GridToText.java
  18. +53 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/GroupRelated.java
  19. +5 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Grouping.java
  20. +14 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/InvalidatesSudokuExcpeption.java
  21. +9 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Row.java
  22. +5 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SolutionListener.java
  23. +30 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SolvedListeningStrategy.java
  24. +15 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Source.java
  25. +71 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyBruteForce.java
  26. +71 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyHiddenTwinElimination.java
  27. +49 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyOnlyPlaceInGroup.java
  28. +43 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategySolvedElimination.java
  29. +81 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategySubGroupElimination.java
  30. +68 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Sudoku.java
  31. +64 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuRules.java
  32. +32 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuSolver.java
  33. +5 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuStrategy.java
  34. +87 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/BruteMain.java
  35. +123 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/BruteSolver.java
  36. +109 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/NaiveBruteSolver.java
  37. +292 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/RecursiveBruteSolver.java
  38. +19 −0 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/Solution.java
6 .classpath
View
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
1  .gitignore
View
@@ -0,0 +1 @@
+/bin
17 .project
View
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>SudokuSolver_Java_rolfl</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
3  .settings/org.eclipse.core.resources.prefs
View
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding//src/com/stackexchange/codreview/rolfl/sudoku_wec3/GridToText.java=UTF-8
+encoding//src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/BruteMain.java=UTF-8
4 input/Sudoku.txt
View
@@ -0,0 +1,4 @@
+2...
+.13.
+3..1
+.24.
9 input/Sudoku1.txt
View
@@ -0,0 +1,9 @@
+..4....98
+.5..74...
+.3.98..5.
+7.2..5.1.
+5..793...
+.........
+......8.4
+...1.....
+.....2.3.
9 input/Sudoku1Mod.txt
View
@@ -0,0 +1,9 @@
+..4....98
+.5..74...
+.3.98..5.
+7.2..5.1.
+5..793...
+.........
+......8.4
+...1.....
+.....2.3.
9 input/Sudoku2.txt
View
@@ -0,0 +1,9 @@
+...84...9
+..1.....5
+8...2146.
+7.8....9.
+.........
+.5....3.1
+.2491...7
+9.....5..
+3...84...
9 input/SudokuExample.txt
View
@@ -0,0 +1,9 @@
+...84...9
+..1.....5
+8...2146.
+7.8....9.
+.........
+.5....3.1
+.2491...7
+9.....5..
+3...84...
9 input/SudokuHard.txt
View
@@ -0,0 +1,9 @@
+7.814....
+.65...42.
+34.9.....
+6......75
+....9..4.
+..3....6.
+19.8.6..7
+...4.....
+5...3....
9 input/SudokuMed.txt
View
@@ -0,0 +1,9 @@
+.......52
+....2..1.
+.8...4...
+........6
+9....85.3
+..653...1
+.42......
+.75.86...
+36.1..7.5
9 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Block.java
View
@@ -0,0 +1,9 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class Block extends GroupRelated {
+
+ public Block(int id, int dimension) {
+ super(Grouping.Block, id, dimension);
+ }
+
+}
125 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Cell.java
View
@@ -0,0 +1,125 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.Arrays;
+
+public class Cell {
+ private final Column col;
+ private final Row row;
+ private final Block block;
+ private final int rowpos, colpos, blockpos;
+ private Source source = Source.Unknown;
+ private int value = SudokuRules.BLANK;
+ private final DigitSet digset;
+ private final Grid grid;
+
+ public Cell(Grid grid, int dimension, Column col, int colpos, Row row,
+ int rowpos, Block block, int blockpos) {
+ super();
+ this.grid = grid;
+ this.digset = new DigitSet(dimension);
+ this.col = col;
+ this.row = row;
+ this.block = block;
+ this.colpos = colpos;
+ this.rowpos = rowpos;
+ this.blockpos = blockpos;
+ }
+
+ public Column getCol() {
+ return col;
+ }
+
+ public Row getRow() {
+ return row;
+ }
+
+ public Block getBlock() {
+ return block;
+ }
+
+ public int getRowPos() {
+ return rowpos;
+ }
+
+ public int getColPos() {
+ return colpos;
+ }
+
+ public int getBlockPos() {
+ return blockpos;
+ }
+
+ public void setValue(Source source, int digit) {
+ if (isSet()) {
+ if (digit != this.value) {
+ throw new InvalidatesSudokuExcpeption("Cannot re-set " + this
+ + " to value [" + SudokuRules.getSudokuDigit(digit) + "]");
+ }
+ return;
+ }
+ if (!digset.isPossible(digit)) {
+ throw new InvalidatesSudokuExcpeption("Cannot set " + this
+ + " to value [" + SudokuRules.getSudokuDigit(digit) + "]. Possible values are "
+ + Arrays.toString(SudokuRules.getSudokuDigits(digset.getRemaining())));
+ }
+ this.value = digit;
+ this.source = source;
+ digset.coerce(digit);
+ grid.solved(this);
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public Source getSource() {
+ return source;
+ }
+
+ public boolean isSet() {
+ return source != Source.Unknown;
+ }
+
+ public boolean eliminate(Source src, int digit) {
+ if (isSet()) {
+ if (digit == value) {
+ throw new InvalidatesSudokuExcpeption("Cannot eliminate the value we have already been set to " + this);
+ }
+ return true;
+ }
+ if (digset.eliminate(digit)) {
+ if (!digset.isJustOne()) {
+ throw new IllegalStateException(
+ "Unexpected accounting in the DigSet: " + digset);
+ }
+ setValue(src, digset.getRemaining()[0]);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Cell (%d,%d)=>[%s]", colpos, rowpos,
+ SudokuRules.getSudokuDigit(value));
+ }
+
+ @Override
+ public int hashCode() {
+ return rowpos * digset.getDimension() + colpos;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // use identity equals always....
+ return obj == this;
+ }
+
+ public boolean canBe(int digit) {
+ return digset.isPossible(digit);
+ }
+
+ public DigitSet getDigitSet() {
+ return digset.duplicate();
+ }
+}
9 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Column.java
View
@@ -0,0 +1,9 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class Column extends GroupRelated {
+
+ public Column(int id, int dimension) {
+ super(Grouping.Column, id, dimension);
+ }
+
+}
81 src/com/stackexchange/codreview/rolfl/sudoku_wec3/DigitSet.java
View
@@ -0,0 +1,81 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.Arrays;
+
+public class DigitSet {
+
+ private final int[] digits;
+ private int size;
+
+ public DigitSet(final int dimension) {
+ this.digits = new int[dimension];
+ this.size = dimension;
+ for (int i = 0; i < dimension; i++) {
+ digits[i] = i;
+ }
+ }
+
+ private int position(final int digit) {
+ return Arrays.binarySearch(digits, 0, size, digit);
+ }
+
+ public boolean isPossible(int digit) {
+ return position(digit) >= 0;
+ }
+
+ public boolean eliminate(final int digit) {
+ final int pos = position(digit);
+ if (pos >= 0) {
+ System.arraycopy(digits, pos + 1, digits, pos, size - pos - 1);
+ size--;
+ }
+ return size <= 1;
+ }
+
+ public void restore(final int digit) {
+ final int pos = position(digit);
+ if (pos < 0) {
+ int p = -pos -1;
+ System.arraycopy(digits, p, digits, p + 1, size - p);
+ size++;
+ digits[p] = digit;
+ }
+ }
+
+ public void coerce(final int digit) {
+ size = 1;
+ digits[0] = digit;
+ }
+
+ public int[] getRemaining() {
+ return Arrays.copyOf(digits, size);
+ }
+
+ public int getRemainingSize() {
+ return size;
+ }
+
+ public boolean isJustOne() {
+ return size == 1;
+ }
+
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ public int getDimension() {
+ return digits.length;
+ }
+
+ @Override
+ public String toString() {
+ return "DigitSet: " + size + " remaining " + Arrays.toString(getRemaining());
+ }
+
+ public DigitSet duplicate() {
+ DigitSet ret = new DigitSet(digits.length);
+ ret.size = size;
+ System.arraycopy(digits, 0, ret.digits, 0, digits.length);
+ return ret;
+ }
+}
115 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Grid.java
View
@@ -0,0 +1,115 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+
+public class Grid {
+ private final int dimension;
+ private final int blocksize;
+ private final Row[] rows;
+ private final Column[] columns;
+ private final Block[] blocks;
+ private final GroupRelated[] related;
+ private final Cell[][] grid;
+ private final HashSet<Cell> unsolved = new HashSet<>();
+ private final HashSet<Cell> solved = new HashSet<>();
+ private final List<SolutionListener> listeners = new LinkedList<>();
+
+ public Grid(final int dim) {
+ dimension = dim;
+ blocksize = SudokuRules.getBlockSize(dimension);
+ grid = new Cell[dimension][dimension];
+ rows = new Row[dimension];
+ columns = new Column[dimension];
+ blocks = new Block[dimension];
+ related = new GroupRelated[dim * 3];
+
+ for (int i = 0; i < dimension; i++) {
+ rows[i] = new Row(i, dimension);
+ columns[i] = new Column(i, dimension);
+ blocks[i] = new Block(i, dimension);
+ related[i * 3 + 0] = rows[i];
+ related[i * 3 + 1] = columns[i];
+ related[i * 3 + 2] = blocks[i];
+ }
+
+
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ final int b = (r / blocksize) * blocksize + (c / blocksize);
+ final int bpos = (r % blocksize) * blocksize + (c % blocksize);
+ final Cell cell = new Cell(this, dimension, columns[c], r, rows[r], c, blocks[b], bpos);
+ rows[r].setCell(c, cell);
+ columns[c].setCell(r, cell);
+ blocks[b].setCell(bpos, cell);
+ grid[r][c] = cell;
+ unsolved.add(cell);
+ if (c != cell.getRowPos() || r != cell.getColPos()) {
+ throw new IllegalStateException("cell " + cell + " expected at (" + r + "," + c + ")");
+ }
+ }
+ }
+
+ }
+
+ public int getDimension() {
+ return dimension;
+ }
+
+ public int getBlocksize() {
+ return blocksize;
+ }
+
+ public Cell get(int row, int col) {
+ return grid[row][col];
+ }
+
+ public final Row getRow(int row) {
+ return rows[row];
+ }
+
+ public final Column getColumn(int col) {
+ return columns[col];
+ }
+
+ public final Block getBlock(int block) {
+ return blocks[block];
+ }
+
+ public final GroupRelated[] getRelatedGroups() {
+ return Arrays.copyOf(related, related.length);
+ }
+
+ public void solved(Cell cell) {
+ if (!unsolved.remove(cell)) {
+ throw new InvalidatesSudokuExcpeption("Expected " + cell + " to be in the unsolved set, but it was not.");
+ }
+ if (!solved.add(cell)) {
+ throw new InvalidatesSudokuExcpeption("Expected " + cell + " not to be in the solved set, but it was.");
+ }
+ for (SolutionListener listener : listeners) {
+ listener.solved(cell);
+ }
+ }
+
+ public void addSolutionListener(SolutionListener listener) {
+ listeners.add(listener);
+ // play catch-up for the listener
+ for (Cell c : solved) {
+ listener.solved(c);
+ }
+ }
+
+ public boolean isSolved() {
+ return unsolved.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return GridToText.displayAsString(this);
+ }
+
+
+}
67 src/com/stackexchange/codreview/rolfl/sudoku_wec3/GridToText.java
View
@@ -0,0 +1,67 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+
+public final class GridToText {
+
+ private static final char[][] symbols = {
+ "╔═╤╦╗".toCharArray(),
+ "║ │║║".toCharArray(),
+ "╟─┼╫╢".toCharArray(),
+ "╠═╪╬╣".toCharArray(),
+ "╚═╧╩╝".toCharArray(),
+ };
+
+ public static final String displayAsString(Grid grid) {
+ final int dimension = grid.getDimension();
+ final int blocksize = grid.getBlocksize();
+ final int[][] rows = new int[dimension][dimension];
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ rows[r][c] = grid.get(r, c).getValue();
+ }
+ }
+ return buildStringForGrid(dimension, blocksize, rows);
+ }
+
+ public static final String displayAsString(int[][]data) {
+ return buildStringForGrid(data.length, SudokuRules.getBlockSize(data.length), data);
+ }
+
+ private static final String buildStringForGrid(final int dimension, final int blocksize, final int[][]rows) {
+ final StringBuilder sb = new StringBuilder();
+ for (int r = 0; r < dimension; r++) {
+ if (r == 0) {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[0]));
+ } else if (r % blocksize == 0) {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[3]));
+ } else {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[2]));
+ }
+ sb.append(printSymbolLine(dimension, blocksize, rows[r], symbols[1]));
+ }
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[4]));
+ return sb.toString();
+ }
+
+ private static String printSymbolLine(int dimension, int blocksize, int[] values, char[] symbols) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(symbols[0]);
+ int vc = 0;
+ for (int b = 0; b < blocksize; b++) {
+ for (int c = 0; c < blocksize; c++) {
+ if (values == null) {
+ sb.append(symbols[1]).append(symbols[1]).append(symbols[1]).append(symbols[2]);
+ } else {
+ final int val = values[vc++];
+ char ch = SudokuRules.getSudokuDigit(val);
+ sb.append(symbols[1]).append(ch).append(symbols[1]).append(symbols[2]);
+ }
+ }
+ sb.setCharAt(sb.length() - 1, symbols[3]);
+ }
+ sb.setCharAt(sb.length() - 1, symbols[4]);
+ sb.append("\n");
+ return sb.toString();
+ }
+
+}
53 src/com/stackexchange/codreview/rolfl/sudoku_wec3/GroupRelated.java
View
@@ -0,0 +1,53 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.Arrays;
+
+public class GroupRelated {
+ private final Cell[] cells;
+ private final int id;
+ private final Grouping grouping;
+
+ public GroupRelated(Grouping grouping, int id, int dimension) {
+ cells = new Cell[dimension];
+ this.grouping = grouping;
+ this.id = id;
+ }
+
+ public final int getId() {
+ return id;
+ }
+
+ void setCell(final int pos, final Cell cell) {
+ if (cells[pos] != null) {
+ throw new IllegalStateException("Unable to set previoulsy-set cell " + pos);
+ }
+ cells[pos] = cell;
+ }
+
+ public final Cell getCell(int i) {
+ return cells[i];
+ }
+
+ public final Cell groupContains(final int digit) {
+ for (Cell c : cells) {
+ if (c.isSet() && digit == c.getValue()) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public final String toString() {
+ int[] vals = new int[cells.length];
+ for (int i = 0; i < vals.length; i++) {
+ vals[i] = cells[i].isSet() ? cells[i].getValue() : SudokuRules.BLANK;
+ }
+ return String.format("%s %d %s", grouping, id, SudokuRules.getSudokuDigits(vals));
+ }
+
+ public Cell[] getCells() {
+ return Arrays.copyOf(cells, cells.length);
+ }
+
+}
5 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Grouping.java
View
@@ -0,0 +1,5 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public enum Grouping {
+ Row, Column, Block;
+}
14 src/com/stackexchange/codreview/rolfl/sudoku_wec3/InvalidatesSudokuExcpeption.java
View
@@ -0,0 +1,14 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class InvalidatesSudokuExcpeption extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+
+ public InvalidatesSudokuExcpeption(String arg0) {
+ super(arg0);
+ }
+
+ public InvalidatesSudokuExcpeption(String arg0, Throwable arg1) {
+ super(arg0, arg1);
+ }
+}
9 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Row.java
View
@@ -0,0 +1,9 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class Row extends GroupRelated {
+
+ public Row(int id, int dimension) {
+ super(Grouping.Row, id, dimension);
+ }
+
+}
5 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SolutionListener.java
View
@@ -0,0 +1,5 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public interface SolutionListener {
+ public void solved(Cell cell);
+}
30 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SolvedListeningStrategy.java
View
@@ -0,0 +1,30 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+public abstract class SolvedListeningStrategy implements SolutionListener, SudokuStrategy {
+
+ private final Grid grid;
+
+ private final Queue<Cell> pending = new LinkedList<>();
+
+
+ public SolvedListeningStrategy(Grid grid) {
+ this.grid = grid;
+ grid.addSolutionListener(this);
+ }
+
+ @Override
+ public void solved(Cell cell) {
+ pending.add(cell);
+ }
+
+ protected Cell retrieveSolvedSince() {
+ return pending.poll();
+ }
+
+ public Grid getGrid() {
+ return grid;
+ }
+}
15 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Source.java
View
@@ -0,0 +1,15 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public enum Source {
+ Puzzle(true), Unknown(false), Player(false), Strategy(false), Guess(false), Force(false);
+
+ private boolean fixed;
+
+ Source(boolean fixed) {
+ this.fixed = fixed;
+ }
+
+ public boolean isFixed() {
+ return fixed;
+ }
+}
71 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyBruteForce.java
View
@@ -0,0 +1,71 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import com.stackexchange.codreview.rolfl.sudoku_wec3.brute.RecursiveBruteSolver;
+
+public class StrategyBruteForce implements SudokuStrategy {
+
+ private final Grid grid;
+ public StrategyBruteForce(final Grid grid) {
+ this.grid = grid;
+ }
+
+ @Override
+ public boolean strategise() {
+ System.out.println("BruteForcing Solution!!!!");
+ // ensure all references/solved things are sorted out:
+
+ // make sure the puzzle's elimination system is up to date...
+ StrategySolvedElimination strat = new StrategySolvedElimination(grid);
+ while (strat.strategise());
+
+ System.out.println(GridToText.displayAsString(grid));
+ final int dim = grid.getDimension();
+ int[][][] digits = new int[dim][dim][];
+ System.out.println(" int[][][] puzzle = new int[][][] {");
+ for (int r = 0; r < dim; r++) {
+ StringBuilder sb = new StringBuilder(" { ");
+ for (int c = 0; c < dim; c++) {
+ digits[r][c] = grid.get(r, c).getDigitSet().getRemaining();
+ sb.append("{ ");
+ for (int d : digits[r][c]) {
+ sb.append(d + ", ");
+ }
+ sb.append("},");
+ }
+ sb.append(" },");
+ System.out.println(sb.toString());
+ }
+ System.out.println(" };");
+
+ RecursiveBruteSolver solver = new RecursiveBruteSolver(dim, digits);
+ int[][][] sols = solver.solve();
+ if (sols.length == 0) {
+ throw new InvalidatesSudokuExcpeption("No solutions were forced!");
+ }
+ if (sols.length > 1) {
+ for (int[][] sol : sols) {
+ Grid tmp = new Grid(sol.length);
+ for (int r = 0; r < sol.length; r++) {
+ for (int c = 0; c < sol[r].length; c++) {
+ tmp.get(r, c).setValue(Source.Player, sol[r][c]);
+ }
+ }
+ System.out.println(GridToText.displayAsString(tmp));
+ }
+ throw new InvalidatesSudokuExcpeption("Multiple solutions were forced!");
+ }
+
+ for (int r = 0; r < dim; r++) {
+ for (int c = 0; c < dim; c++) {
+ if (!grid.get(r, c).isSet()) {
+ grid.get(r, c).setValue(Source.Force, sols[0][r][c]);
+ return true;
+ }
+ }
+ }
+
+
+ return false;
+ }
+
+}
71 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyHiddenTwinElimination.java
View
@@ -0,0 +1,71 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+
+public class StrategyHiddenTwinElimination implements SudokuStrategy {
+
+ private final Grid grid;
+ public StrategyHiddenTwinElimination(Grid grid) {
+ this.grid = grid;
+ }
+
+
+ @Override
+ public boolean strategise() {
+ // if two numbers appear in only two cells in a region,
+ // then any other numbers in those cells can be eliminated.
+ for (GroupRelated group : grid.getRelatedGroups()) {
+ for (int a = 0; a < grid.getDimension(); a++) {
+ if (group.groupContains(a) == null) {
+ for (int b = a + 1; b < grid.getDimension(); b++) {
+ if (group.groupContains(b) == null) {
+ if(processGroupPair(group, a, b)) {
+ // twin....
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+
+ private boolean processGroupPair(GroupRelated group, int a, int b) {
+ Cell[] cells = new Cell[2];
+ int cnt = 0;
+ for (Cell c : group.getCells()) {
+ if (!c.isSet() && c.canBe(a)) {
+ if (cnt >= cells.length) {
+ return false;
+ }
+ cells[cnt++] = c;
+ }
+ }
+ if (cnt < 2) {
+ // should never happen....
+ return false;
+ }
+ for (Cell c : group.getCells()) {
+ if (!c.isSet() && c.canBe(b)) {
+ if (!(cells[0] == c || cells[1] == c) ) {
+ return false;
+ }
+ }
+ }
+ boolean affected = false;
+ for (Cell cell : cells) {
+ for (int digit : cell.getDigitSet().getRemaining()) {
+ if (!(digit == a || digit == b)) {
+ System.out.println("StrategyHiddenTwinElimination eliminated " + digit + " from " + cell);
+ cell.eliminate(Source.Strategy, digit);
+
+ affected = true;
+ }
+ }
+ }
+ return affected;
+ }
+
+
+}
49 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategyOnlyPlaceInGroup.java
View
@@ -0,0 +1,49 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class StrategyOnlyPlaceInGroup implements SudokuStrategy {
+
+ private final Grid grid;
+ public StrategyOnlyPlaceInGroup(final Grid grid) {
+ this.grid = grid;
+ }
+
+ @Override
+ public boolean strategise() {
+ for (GroupRelated related : grid.getRelatedGroups()) {
+ if (findPotentialDigit(related)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean findPotentialDigit(GroupRelated related) {
+ for (int digit = 0; digit < grid.getDimension(); digit++) {
+ Cell cell = findPotentialCell(related, digit);
+ if (cell != null) {
+ cell.setValue(Source.Strategy, digit);
+ System.out.println("OnlyOneCanBe solved " + cell);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Cell findPotentialCell(GroupRelated related, int digit) {
+ Cell candidate = null;
+ for (Cell c : related.getCells()) {
+ if (c.isSet() && c.getValue() == digit) {
+ return null;
+ }
+ if (c.canBe(digit)) {
+ if (candidate != null) {
+ // cannot have two candidates with the same solution.
+ return null;
+ }
+ candidate = c;
+ }
+ }
+ return candidate;
+ }
+
+}
43 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategySolvedElimination.java
View
@@ -0,0 +1,43 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class StrategySolvedElimination extends SolvedListeningStrategy {
+
+ public StrategySolvedElimination(Grid grid) {
+ super(grid);
+ }
+
+ @Override
+ public boolean strategise() {
+ Cell cell = null;
+ Set<Cell> affects = new HashSet<>();
+
+ while( (cell = retrieveSolvedSince()) != null) {
+ affects.clear();
+ for (Cell r : cell.getRow().getCells()) {
+ if (!r.isSet()) affects.add(r);
+ }
+ for (Cell c : cell.getCol().getCells()) {
+ if (!c.isSet()) affects.add(c);
+ }
+ for (Cell b : cell.getBlock().getCells()) {
+ if (!b.isSet()) affects.add(b);
+ }
+ affects.remove(cell);
+ boolean anything = false;
+ for (Cell c : affects) {
+ if (c.eliminate(Source.Strategy, cell.getValue())) {
+ anything = true;
+ System.out.println("Elimination solved " + c);
+ }
+ }
+ if (anything) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
81 src/com/stackexchange/codreview/rolfl/sudoku_wec3/StrategySubGroupElimination.java
View
@@ -0,0 +1,81 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.util.Arrays;
+
+public class StrategySubGroupElimination implements SudokuStrategy {
+
+ private final Grid grid;
+ public StrategySubGroupElimination(Grid grid) {
+ this.grid = grid;
+ }
+
+
+ @Override
+ public boolean strategise() {
+ // every block has a set of rows and columns....
+ // if we can prove that, in a block, that a number can only appear in one
+ // of these rows, or columns, then we can eliminate that number from the rest
+ // of the row (or column);
+ for (int b = 0; b < grid.getDimension(); b++) {
+ Block block = grid.getBlock(b);
+ for (int digit = 0; digit < grid.getDimension(); digit++) {
+ if (processBlock(block, digit)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ private boolean processBlock(Block block, int digit) {
+ if (block.groupContains(digit) != null) {
+ // already a full solution for this digit.
+ return false;
+ }
+ Cell[] possibles = new Cell[grid.getDimension()];
+ int cnt = 0;
+ for (Cell c : block.getCells()) {
+ if (!c.isSet() && c.canBe(digit)) {
+ possibles[cnt++] = c;
+ }
+ }
+ if (cnt <= 0) {
+ return false;
+ }
+
+ GroupRelated sameroworcol = sameRowOrCol(Arrays.copyOf(possibles, cnt));
+ if (sameroworcol == null) {
+ return false;
+ }
+ boolean affect = false;
+ for (Cell src : sameroworcol.getCells()) {
+ if (!src.isSet() && src.getBlock() != block) {
+ if (src.eliminate(Source.Strategy, digit)) {
+ affect = true;
+ System.out.println("SubGroupElimination Eliminated " + digit + " on " + src);
+ }
+ }
+ }
+ return affect;
+ }
+
+
+ private GroupRelated sameRowOrCol(Cell[] cells) {
+ if (cells.length <= 1) {
+ return null;
+ }
+ Column col = cells[0].getCol();
+ Row row = cells[0].getRow();
+ for (Cell c : cells) {
+ if (row != null && c.getRow() != row) {
+ row = null;
+ }
+ if (col != null && c.getCol() != col) {
+ col = null;
+ }
+ }
+ return row == null ? col : row;
+ }
+
+}
68 src/com/stackexchange/codreview/rolfl/sudoku_wec3/Sudoku.java
View
@@ -0,0 +1,68 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+
+public class Sudoku {
+
+ public static void main(String[] args) throws IOException {
+
+ final Grid grid = buildGrid(args.length == 0 ? "input/SudokuExample.txt" : args[0]);
+
+ System.out.println(GridToText.displayAsString(grid));
+
+ SudokuSolver solver = new SudokuSolver(grid);
+ boolean solved = solver.solveAll();
+
+ if (solved) {
+ System.out.println("SOLVED!!!!!");
+ } else {
+ System.out.println("UN-SOLVED!!!!!");
+ }
+
+ System.out.println(GridToText.displayAsString(grid));
+
+
+
+ }
+
+ private static Grid buildGrid(String fname) throws IOException {
+ List<String> lines = Files.readAllLines(Paths.get(fname), StandardCharsets.UTF_8);
+ int dimension = lines.size();
+ int blocksize = SudokuRules.getBlockSize(dimension);
+ if ((blocksize * blocksize) != dimension) {
+ throw new IllegalArgumentException("Content width must be a square number (");
+ }
+ final char[] validchars = Arrays.copyOf(SudokuRules.DIGITCHARS, dimension);
+ Grid grid = new Grid(dimension);
+ int row = 0;
+ for (String line : lines) {
+ char[] chars = line.toCharArray();
+ if (chars.length != dimension) {
+ throw new IllegalStateException("Expect square grid, '" + line + "' is not valid (" + dimension + ")");
+ }
+ for (int c = 0; c < chars.length; c++) {
+ int digit = isValidChar(validchars, chars[c]);
+ if (digit >= 0) {
+ grid.get(row, c).setValue(Source.Puzzle, digit);
+ }
+ }
+ row++;
+ }
+ return grid;
+ }
+
+ public static final int isValidChar(char[] validchars, char c) {
+ for (int i = 0; i < validchars.length; i++) {
+ if (c == validchars[i]) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
64 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuRules.java
View
@@ -0,0 +1,64 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class SudokuRules {
+ public static final char[] DIGITCHARS = "123456789ABCFEDGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+ public static final int BLANK = -1;
+
+
+ public static final char getSudokuDigit(final int value) {
+ if (value < 0) {
+ return ' ';
+ }
+ return SudokuRules.DIGITCHARS[value];
+ }
+
+ public static final char[] getSudokuDigits(final int[] values) {
+ final char[] translation = new char[values.length];
+ for (int i = 0; i < values.length; i++) {
+ translation[i] = getSudokuDigit(values[i]);
+ }
+ return translation;
+ }
+
+ public static int getBlockSize(int dimension) {
+ final int blocksize = (int)Math.sqrt(dimension);
+ if ((blocksize * blocksize) == dimension) {
+ return blocksize;
+ }
+ throw new IllegalStateException("Illegal dimension size " + dimension);
+ }
+
+ public static boolean isValid(int[][] data) {
+ final int dim = data.length;
+ final int bs = getBlockSize(dim);
+ for (int r = 0; r < data.length; r++) {
+ for (int c = 0; c< data[r].length; c++) {
+ final int rlim = (1 + r / bs) * bs;
+ final int clim = (1 + c / bs) * bs;
+ for (int rc = r + 1; rc < dim; rc++) {
+ if (data[r][c] == data[rc][c]) {
+ throw new IllegalStateException(format(r,c,rc,c,data));
+ }
+ }
+ for (int cc = c + 1; cc < dim; cc++) {
+ if (data[r][c] == data[r][cc]) {
+ throw new IllegalStateException(format(r,c,r,cc,data));
+ }
+ if (cc < clim) {
+ for (int rc = r + 1; rc < rlim; rc++) {
+ if (data[r][c] == data[rc][cc]) {
+ throw new IllegalStateException(format(r,c,rc,cc,data));
+ }
+ }
+ }
+ }
+
+ }
+ }
+ return true;
+ }
+
+ private static final String format(int r, int c, int rc, int cc, int[][]data) {
+ return String.format("Data at (%d,%d) is same as data at (%d,%d)\n%s", r, c, rc, cc, GridToText.displayAsString(data));
+ }
+}
32 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuSolver.java
View
@@ -0,0 +1,32 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public class SudokuSolver {
+
+ final Grid grid;
+ final SudokuStrategy[] strategies;
+ public SudokuSolver(Grid grid) {
+ this.grid = grid;
+ strategies = new SudokuStrategy[] {
+ new StrategySolvedElimination(grid),
+ new StrategyOnlyPlaceInGroup(grid),
+ new StrategySubGroupElimination(grid),
+ new StrategyHiddenTwinElimination(grid),
+ new StrategyBruteForce(grid),
+ };
+ }
+
+ public boolean solveAll() {
+ boolean progress = true;
+ while (progress && !grid.isSolved()) {
+ progress = false;
+ for (SudokuStrategy strategy : strategies) {
+ if (strategy.strategise()) {
+ progress = true;
+ break;
+ }
+ }
+ }
+ return grid.isSolved();
+ }
+
+}
5 src/com/stackexchange/codreview/rolfl/sudoku_wec3/SudokuStrategy.java
View
@@ -0,0 +1,5 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3;
+
+public interface SudokuStrategy {
+ public boolean strategise();
+}
87 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/BruteMain.java
View
@@ -0,0 +1,87 @@
+ package com.stackexchange.codreview.rolfl.sudoku_wec3.brute;
+
+ public class BruteMain {
+
+ /**
+ * http://codereview.stackexchange.com/questions?sort=newest
+ * See above link
+ * @param args
+ */
+ public static void main(String[] args) {
+ int[][][] puzzle = new int[][][] {
+ { { 1, 4, 5, },{ 2, 5, 6, },{ 1, 2, 4, 5, 6, },{ 7, },{ 3, },{ 2, 4, 5, 6, },{ 0, 1, 6, },{ 0, 1, 6, },{ 8, }, },
+ { { 1, 3, 5, },{ 2, 3, 5, 6, 8, },{ 0, },{ 2, 5, 6, },{ 2, 5, 6, 8, },{ 2, 5, 6, 8, },{ 1, 6, 7, },{ 1, 6, 7, },{ 4, }, },
+ { { 7, },{ 6, 8, },{ 4, 6, 8, },{ 4, 6, },{ 1, },{ 0, },{ 3, },{ 5, },{ 2, }, },
+ { { 6, },{ 0, 2, 3, 5, },{ 7, },{ 0, 1, 2, 3, 4, 5, },{ 2, 4, 5, },{ 1, 2, 4, 5, },{ 1, 5, },{ 8, },{ 1, 3, 5, }, },
+ { { 0, 1, 3, 5, },{ 0, 2, 3, 5, 8, },{ 1, 2, 5, 8, },{ 0, 1, 2, 3, 4, 5, 6, },{ 2, 4, 5, 6, 8, },{ 1, 2, 4, 5, 6, 7, 8, },{ 1, 5, 6, 7, },{ 1, 3, 4, 6, 7, },{ 1, 3, 5, 7, }, },
+ { { 1, 3, 5, },{ 4, },{ 1, 5, 8, },{ 1, 3, 5, 6, },{ 5, 6, 8, },{ 1, 5, 6, 7, 8, },{ 2, },{ 1, 3, 6, 7, },{ 0, }, },
+ { { 4, 5, },{ 1, },{ 3, },{ 8, },{ 0, },{ 2, 4, 5, },{ 5, 7, },{ 2, 7, },{ 6, }, },
+ { { 8, },{ 0, 5, 6, 7, },{ 5, 6, },{ 1, 2, 5, 6, },{ 2, 5, 6, },{ 1, 2, 5, 6, },{ 4, },{ 0, 1, 2, 3, 7, },{ 1, 3, 5, 7, }, },
+ { { 2, },{ 0, 5, 6, },{ 4, 5, 6, },{ 1, 4, 5, 6, },{ 7, },{ 3, },{ 0, 1, 5, 8, },{ 0, 1, },{ 1, 5, }, },
+ };
+
+ RecursiveBruteSolver solver = new RecursiveBruteSolver(9, puzzle);
+ for (int[][] sol : solver.solve()) {
+ System.out.println("Solution:");
+ System.out.println(displayAsString(sol));
+ }
+ }
+
+ private static final char[][] symbols = {
+ "╔═╤╦╗".toCharArray(),
+ "║ │║║".toCharArray(),
+ "╟─┼╫╢".toCharArray(),
+ "╠═╪╬╣".toCharArray(),
+ "╚═╧╩╝".toCharArray(),
+ };
+
+ private static final char[] DIGITCHARS = "123456789ABCFEDGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
+
+ public static final char getSudokuDigit(final int value) {
+ if (value < 0) {
+ return ' ';
+ }
+ return DIGITCHARS[value];
+ }
+
+ public static final String displayAsString(int[][]data) {
+ return buildStringForGrid(data.length, (int)Math.sqrt(data.length), data);
+ }
+
+ private static final String buildStringForGrid(final int dimension, final int blocksize, final int[][]rows) {
+ final StringBuilder sb = new StringBuilder();
+ for (int r = 0; r < dimension; r++) {
+ if (r == 0) {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[0]));
+ } else if (r % blocksize == 0) {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[3]));
+ } else {
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[2]));
+ }
+ sb.append(printSymbolLine(dimension, blocksize, rows[r], symbols[1]));
+ }
+ sb.append(printSymbolLine(dimension, blocksize, null, symbols[4]));
+ return sb.toString();
+ }
+
+ private static String printSymbolLine(int dimension, int blocksize, int[] values, char[] symbols) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(symbols[0]);
+ int vc = 0;
+ for (int b = 0; b < blocksize; b++) {
+ for (int c = 0; c < blocksize; c++) {
+ if (values == null) {
+ sb.append(symbols[1]).append(symbols[1]).append(symbols[1]).append(symbols[2]);
+ } else {
+ final int val = values[vc++];
+ char ch = getSudokuDigit(val);
+ sb.append(symbols[1]).append(ch).append(symbols[1]).append(symbols[2]);
+ }
+ }
+ sb.setCharAt(sb.length() - 1, symbols[3]);
+ }
+ sb.setCharAt(sb.length() - 1, symbols[4]);
+ sb.append("\n");
+ return sb.toString();
+ }
+ }
123 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/BruteSolver.java
View
@@ -0,0 +1,123 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3.brute;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.stackexchange.codreview.rolfl.sudoku_wec3.DigitSet;
+
+public class BruteSolver {
+
+ private final DigitSet[][] digits;
+ private final int dimension;
+
+ public BruteSolver(int dimension, DigitSet[][] digits) {
+ this.digits = digits;
+ this.dimension = dimension;
+ }
+
+ public Solution[] solve() {
+ List<Solution> solutions = new ArrayList<>();
+ final int flatsize = dimension * dimension;
+ final int[][] followings = new int[flatsize][];
+
+ final DigitSet[] flattened = new DigitSet[flatsize];
+ final DigitSet[] stacksets = new DigitSet[flatsize];
+
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ int flat = r * dimension + c;
+ followings[flat] = buildFollowing(r, c);
+ flattened[flat] = digits[r][c];
+ stacksets[flat] = digits[r][c].duplicate();
+ }
+ }
+
+ final int[][] remaining = new int[flatsize][];
+ final int[] rempos = new int[flatsize];
+ rempos[0] = -1;
+ remaining[0] = flattened[0].getRemaining();
+
+ int depth = 0;
+ while (depth >= 0) {
+ if (depth == stacksets.length) {
+ // solution....
+ solutions.add(buildSolution(stacksets));
+ depth--;
+ } else {
+ if (rempos[depth] >= 0) {
+ // undo all stack mods
+ final int digit = remaining[depth][rempos[depth]];
+ for (int f = 0; f < followings[depth].length; f++) {
+ if (flattened[f].isPossible(digit)) {
+ stacksets[f].restore(digit);
+ }
+ }
+ }
+ int nxt = ++rempos[depth];
+ if (nxt >= remaining[depth].length) {
+ // run out of things at this level
+ for (int digit : remaining[depth]) {
+ stacksets[depth].restore(digit);
+ }
+ depth--;
+ } else {
+ int digit = remaining[depth][nxt];
+ stacksets[depth].coerce(digit);
+ boolean ok = true;
+ for (int f : followings[depth]) {
+ stacksets[f].eliminate(digit);
+ if (stacksets[f].isEmpty()) {
+ ok = false;
+ break;
+ }
+ }
+ if (ok) {
+ depth++;
+ rempos[depth] = -1;
+ remaining[depth] = stacksets[depth].getRemaining();
+ }
+
+ }
+ }
+ }
+ return solutions.toArray(new Solution[solutions.size()]);
+ }
+
+ private Solution buildSolution(DigitSet[] stacksets) {
+ int[][] grid = new int[dimension][dimension];
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ int flat = r * dimension + c;
+ if (stacksets[flat].isJustOne()) {
+ grid[r][c] = stacksets[flat].getRemaining()[0];
+ } else {
+ throw new IllegalStateException("Expecting position (" + r + "," + c + ") to have just one value but got " + stacksets[flat]);
+ }
+ }
+ }
+ return new Solution(grid);
+ }
+
+ private int[] buildFollowing(int row, int col) {
+ int[] folls = new int[dimension * 3];
+ final int innerbound = dimension / 3;
+ int cnt = 0;
+ int cb = ((1 + col / innerbound) * innerbound);
+ int rb = ((1 + row / innerbound) * innerbound);
+ for (int c = col + 1; c < dimension; c++) {
+ // rest of row.
+ folls[cnt++] = row * dimension + c;
+ }
+ for (int r = row + 1; r < dimension; r++) {
+ folls[cnt++] = r * dimension + col;
+ if (r < rb) {
+ for (int c = col + 1; c < cb; c++) {
+ folls[cnt++] = r * dimension + c;
+ }
+ }
+ }
+ return Arrays.copyOf(folls, cnt);
+ }
+
+}
109 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/NaiveBruteSolver.java
View
@@ -0,0 +1,109 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3.brute;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.stackexchange.codreview.rolfl.sudoku_wec3.DigitSet;
+
+public class NaiveBruteSolver {
+
+ private final DigitSet[][] digits;
+ private final int dimension;
+
+ public NaiveBruteSolver(int dimension, DigitSet[][] digits) {
+ this.digits = digits;
+ this.dimension = dimension;
+ }
+
+ private int[] buildFollowing(int row, int col) {
+ int[] folls = new int[dimension * 3];
+ final int innerbound = dimension / 3;
+ int cnt = 0;
+ int cb = ((1 + col / innerbound) * innerbound);
+ int rb = ((1 + row / innerbound) * innerbound);
+ for (int c = col + 1; c < dimension; c++) {
+ // rest of row.
+ folls[cnt++] = row * dimension + c;
+ }
+ for (int r = row + 1; r < dimension; r++) {
+ folls[cnt++] = r * dimension + col;
+ if (r < rb) {
+ for (int c = col + 1; c < cb; c++) {
+ folls[cnt++] = r * dimension + c;
+ }
+ }
+ }
+ return Arrays.copyOf(folls, cnt);
+ }
+
+ public Solution[] solve() {
+ List<Solution> solutions = new ArrayList<>();
+ final int flatsize = dimension * dimension;
+ final int[][] available = new int[flatsize][];
+ final int[] combination = new int[flatsize];
+ final int[][] followings = new int[flatsize][];
+
+ long combs = 1L;
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ int flat = r * dimension + c;
+ followings[flat] = buildFollowing(r, c);
+ available[flat] = digits[r][c].getRemaining();
+ combs *= available[flat].length;
+ System.out.printf("Flat %d rem %d combs %d\n", flat, available[flat].length, combs);
+ }
+ }
+
+ System.out.println("Bruting " + combs + " combinations");
+
+ boolean done = false;
+ long tries = 0L;
+ do {
+ if (++tries % 1000000 == 0) {
+ System.out.printf("Trying combination %d of %d (%.3f%%) with %d solutions so far\n", tries, combs, (100.0 * tries)/combs, solutions.size());
+ }
+ if (isValid(available, combination, followings)) {
+ solutions.add(buildSolution(available, combination));
+ System.out.println("Solved one way!");
+ }
+ // increment combination;
+ done = true;
+ for (int i = combination.length - 1; i >= 0; i--) {
+ if (available[i].length > 1) {
+ if (++combination[i] >= available[i].length) {
+ combination[i] = 0;
+ } else {
+ done = false;
+ break;
+ }
+ }
+ }
+ } while (!done);
+
+ return solutions.toArray(new Solution[solutions.size()]);
+ }
+
+ private Solution buildSolution(int[][] available, int[] combination) {
+ int[][] grid = new int[dimension][dimension];
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ int flat = r * dimension + c;
+ grid[r][c] = available[flat][combination[flat]];
+ }
+ }
+ return new Solution(grid);
+ }
+
+ private boolean isValid(int[][] available, int[] combination, int[][] related) {
+ for (int i = 0; i < available.length; i++) {
+ for (int f : related[i]) {
+ if (available[i][combination[i]] == available[f][combination[f]]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+}
292 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/RecursiveBruteSolver.java
View
@@ -0,0 +1,292 @@
+ package com.stackexchange.codreview.rolfl.sudoku_wec3.brute;
+
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.List;
+
+// import org.tuis.sudoku.DigitSet;
+
+ /**
+ * A class that is able to brute-force a solution for a Sudoku-like puzzle.
+ * <br>
+ * Due to the internal use of bit-shifting the largest sudoku puzzle this can solve is 25x25
+ * which should be more than enough. Sudoku-like puzzles are always square-number sized. 1x1, 4x4, 9x9, 16x16, 25x25, etc.
+ *
+ * @author rolfl
+ *
+ */
+ public class RecursiveBruteSolver {
+
+ // dimension - the width of the grid
+ private final int dimension;
+ // a simple bit-mask with 1 bit set for each possible value in each square.
+ // for a 4-size sudoku it will look like:
+ // 0b0001 0b0010 0b0100 0b1000
+ // for a 9-size sudoku like:
+ // 0b000000001 0b000000010 0b000000100 .....
+ private final int[] bitmask;
+ // if we flatten the grid, this is how big it is in 1 dimension
+ private final int flatsize;
+ // the array of flattened cell available digits
+ private final int[][] available;
+ // a complicated concept - the index into the flattened array of all
+ // squares that are in the same row/column/block, but also have a larger
+ // index in the flattened array.
+ private final int[][] followings;
+ // Some squares will have just one possible value, they do not need to be
+ // solved.
+ // this unknown array is the indices of the unsolved squares in the
+ // flattened array
+ private final int[] unknown;
+
+ // keep track of all recursive call counts
+ private long statistics = 0L;
+
+ /**
+ * Create a Brute-force solver that will determine all the solutions (if
+ * any) for a Sudoku grid (when you call the solve() method).
+ *
+ * @param dimension
+ * the side-size of the sudoku grid. The digits 2D array is
+ * expected to match the same dimensions.
+ * @param digits
+ * a 3D array containing the digits that are allowed at that
+ * position in the array. The actual digits available are
+ * expected to be 0-based. i.e. a regular 9-size Sudoku has the
+ * digits 1-9, this solver expects them to be represented as 0-8.
+ */
+ public RecursiveBruteSolver(int dimension, int[][][] digits) {
+ this.dimension = dimension;
+
+ // if we flatten the array....
+ this.flatsize = dimension * dimension;
+ available = new int[flatsize][];
+ followings = new int[flatsize][];
+
+ // how many digits are there...and what will the bit-mask be for each digit.
+ bitmask = new int[dimension];
+ for (int i = 0; i < dimension; i++) {
+ bitmask[i] = 1 << i;
+ }
+
+ // keep track of the unknown cells.
+ // make a generous-size array, we will trim it later.
+ int[] tmpunknown = new int[flatsize];
+ int tmpunknowncnt = 0;
+
+ // flatten out the grid.
+ for (int r = 0; r < dimension; r++) {
+ for (int c = 0; c < dimension; c++) {
+ int flatid = r * dimension + c;
+ // gets an array of digits that can be put in each square.
+ // for example, if a Sudoku square can have 3,5 or 6 this will return
+ // [2,4,5]
+ available[flatid] = Arrays.copyOf(digits[r][c], digits[r][c].length);
+ if (available[flatid].length > 1) {
+ tmpunknown[tmpunknowncnt++] = flatid;
+ }
+ }
+ }
+
+ // System.out.println("There are " + tmpunknowncnt + " unknown cells");
+
+ // Special note about `followings`
+ // A square in a Sudoku puzzle affects all other squares in the same row, column, and block.
+ // Only one square in the same row/block/column can have particular value.
+ // For this recursion we 'flatten' the grid, and process the grid in order.
+ // For each unsolved cell, we set the cell value, and then move on to the next unsolved cell
+ // but, we need to 'remove' our value from the possible values of other cells in the same row/column/block.
+ // Because of the way this solver progresses along the flattened array, we only need to remove it
+ // from cells that come **after** us in the flattened array (values before us already have a fixed value).
+ // So, buildFollowing() builds an array of indexes in the flattened array that are in the same row/column/block
+ // but also have a higher index in the flattened array.
+ for (int flat = 0; flat < available.length; flat++) {
+ if (available[flat] != null) {
+ followings[flat] = buildFollowing(flat / dimension, flat
+ % dimension);
+ }
+ }
+
+ // create the final copy of the unknown cells (cells which need to be solved).
+ unknown = Arrays.copyOf(tmpunknown, tmpunknowncnt);
+ }
+
+ public int[][][] solve() {
+
+ // following points to unknown subsequent values.....
+ final int[] combination = new int[flatsize];
+ // where to store the possible solutions we find
+ final List<int[][]> solutions = new ArrayList<>();
+
+ // this freedoms is an integer for each value.
+ // bits in the integer are set for each of the values the
+ // position can be. This is essentially a record of the state
+ // inside the recursive routine. For example, if setting some other
+ // cell in the same row/column/block to 5, and our 5-bit is set,
+ // then we will unset it here because we no longer have the freedom
+ // to be 5.
+ final int[] freedoms = new int[flatsize]; //Arrays.copyOf(resetmask, resetmask.length);
+ for (int flatid = 0; flatid < flatsize; flatid++) {
+ for (int b : available[flatid]) {
+ // set the degrees-of-freedom mask of this...
+ // what values can this cell take?
+ freedoms[flatid] |= bitmask[b];
+ }
+ }
+
+ // Do the actual recursion.
+ // combination contains pointers to which actual values are being used.
+ // freedom contans the possible states for all subsequent cells.
+ recurse(solutions, 0, combination, freedoms);
+
+ System.out.println("There were " + statistics + " recursive calls...");
+
+ // convert the list of Solutions back to an array
+ return solutions.toArray(new int[solutions.size()][][]);
+ }
+
+ /**
+ * Recursively solve the Sudoku puzzle.
+ * @param solutions where to store any found solutions.
+ * @param index The index in the 'unknown' array that points to the flat-based cell we need to solve.
+ * @param combination What the current combination of cell values is.
+ * @param freedoms The state of what potential values all cells can have.
+ */
+ private void recurse(final List<int[][]> solutions, final int index,
+ final int[] combination, final int[] freedoms) {
+ statistics++;
+ if (index >= unknown.length) {
+ // solution!
+ solutions.add(buildSolution(combination));
+ return;
+ }
+
+ // The basic algorithm here is: for our unsolved square, we set it to each of it's possible values in turn.
+ // then, for each of the values we can be:
+ // 1. we also find all 'related' squares, and remove our value
+ // from the degrees-of-freedom for the related squares
+ // (If I am 'x' then they cannot be). See special not about 'followings'
+ // 3. we keep track of which other squares we actually change the freedoms for.
+ // 4. we recurse to the next unsolved square.
+ // 5. when the recursion returns, we restore the freedoms we previously 'revoked'
+ // 6. we move on to the next value we can be (back to 1).
+ // 7. when we have run out of possible values, we return.
+ final int flat = unknown[index];
+ for (int a = available[flat].length - 1; a >= 0; a--) {
+ final int attempt = available[flat][a];
+ if ((freedoms[flat] & bitmask[attempt]) == 0) {
+ // this option excluded by previous restrictions....
+ // the original unsolved puzzle says we can be 'attempt', but
+ // higher levels of recursion have removed 'attempt' from our
+ // degrees-of-freedom.
+ continue;
+ }
+ // ok, is used to track whether we are still creating a valid Sudoku.
+ boolean ok = true;
+ // progress is used to forward, and then backtrack which following cells
+ // have been impacted.
+ // start at -1 because we pre-increment the progress.
+ int progress = -1;
+ // act has 1 bit representing each follower we act on.
+ long act = 0;
+ while (++progress < followings[flat].length) {
+ if (freedoms[followings[flat][progress]] == bitmask[attempt]) {
+ // we intend to remove the attempt from this follower's freedom's
+ // but that will leave it with nothing, so this is not possible to do.
+ // ok is false, so we will start a back-up
+ ok = false;
+ break;
+ }
+ // we **can** remove the value from this follower's freedoms.
+ // indicate that this follower is being 'touched'.
+ // act will have 1 bit available for each follower we touch.
+ act <<= 1;
+ // record the pre-state of the follower's freedoms.
+ final int pre = freedoms[followings[flat][progress]];
+ // if the follower's freedoms contained the value we are revoking, then set the bit.
+ act |= (freedoms[followings[flat][progress]] &= ~bitmask[attempt]) == pre ? 0 : 1;
+ }
+ if (ok) {
+ // we have removed our digit from all followers, and the puzzle is still valid.
+ // indicate our combination digit....
+ combination[flat] = a;
+ // find the next unsolved.
+ recurse(solutions, index + 1, combination, freedoms);
+ }
+ while (--progress >= 0) {
+ // restore all previously revoked freedoms.
+ if ((act & 0x1) == 1) {
+ freedoms[followings[flat][progress]] |= bitmask[attempt]; // & resetmask[flat]);
+ }
+ act >>= 1;
+ }
+ }
+
+ }
+
+ /**
+ * buildFollowing creates an array of references to other cells in the same
+ * row/column/block that also have an index **after** us in the flattened array system.
+ *
+ * @param row our row index
+ * @param col our column index
+ * @return an array of flattened indices that are in the same row/column/block as us.
+ */
+ private int[] buildFollowing(int row, int col) {
+ int[] folls = new int[dimension * 3]; // possible rows/columns/blocks - 3 sets of values.
+ final int innerbound = (int)Math.sqrt(dimension); // 3 for size 9, 2 for size 4, 4 for size 16, etc.
+ // cnt is used to count the valid following indices.
+ int cnt = 0;
+ // column-bound - last column in the same block as us.
+ int cb = ((1 + col / innerbound) * innerbound);
+ // row-bound - last row in the same block as us.
+ int rb = ((1 + row / innerbound) * innerbound);
+ // get all (unsolved) indices that follow us in the same row
+ for (int c = col + 1; c < dimension; c++) {
+ // rest of row.
+ if (available[row * dimension + c].length > 1) {
+ // only need to worry about unsolved followers.
+ folls[cnt++] = row * dimension + c;
+ }
+ }
+ // get all (unsolved) indices that follow us in the same column
+ for (int r = row + 1; r < dimension; r++) {
+ if (available[r * dimension + col].length > 1) {
+ // only need to worry about unsolved followers.
+ folls[cnt++] = r * dimension + col;
+ }
+ if (r < rb) {
+ // if we have not 'escaped' our block, we also find other cells in
+ // the same block, but not our row/column.
+ for (int c = col + 1; c < cb; c++) {
+ if (available[r * dimension + c].length > 1) {
+ // only need to worry about unsolved followers.
+ folls[cnt++] = r * dimension + c;
+ }
+ }
+ }
+ }
+ // return just the values that were needed as followers.
+ return Arrays.copyOf(folls, cnt);
+ }
+
+ /**
+ * Convert the valid combination of values back to a simple int[] grid.
+ * @param combination the combination of unsolved values that is a valid puzzle.
+ * @return A Solution object representing the solution.
+ */
+ private int[][] buildSolution(int[] combination) {
+ int[][] grid = new int[dimension][dimension];
+ for (int f = 0; f < combination.length; f++) {
+ grid[f / dimension][f % dimension] = available[f][combination[f]];
+ }
+ // double-check the validity of this solution (all sudoku basic rules are followed.
+ // throws exception if not.
+ // SudokuRules.isValid(grid);
+ // mechanism for printing out a grid.
+ // System.out.println("BruteForceRecursive found Solution:\n"
+ // + GridToText.displayAsString(grid));
+ return grid;
+ }
+
+ }
19 src/com/stackexchange/codreview/rolfl/sudoku_wec3/brute/Solution.java
View
@@ -0,0 +1,19 @@
+package com.stackexchange.codreview.rolfl.sudoku_wec3.brute;
+
+import java.util.Arrays;
+
+public class Solution {
+
+ private final int[][] solution;
+
+ public Solution(int[][] solution) {
+ this.solution = new int[solution.length][];
+ for (int i = 0; i < solution.length; i++) {
+ this.solution[i] = Arrays.copyOf(solution[i], solution[i].length);
+ }
+ }
+
+ public int[][] getSolution() {
+ return solution;
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.