sudoku v1.0.1 Sudoku.Strategy

Routines to implement algorithmic solving of Sudoku puzzles. See: http://www.stolaf.edu/people/hansonr/sudoku/12rules.htm

Essentially all rules boil down to:

  1. naked singles
  2. hidden singles 3,4. locked candidates
  3. naked tuples
  4. hidden tuples
  5. grid analysis (X-wings, Swordfish, etc.)

Summary

Functions

Apply inferences to board. This is the process of taking a given inference and a board structure and updating the board to show the implications of the inference

Find all combinations of size ‘num’ from the list ‘to_pick_from’

Find eliminations through Grid Analysis

Guess at a candidate to eliminate from a square

Find hidden singles

Find locked candidates

Find naked singles

Find naked tuples (and hidden tuples)

Examine a sudoku board and return either:

  • {:invalid, true} - (ie no candidates left for a given square and no backtracking opportunity)
  • {:backtrack, :contradition} - implies we must have a backtracking option, but we have reached an invalid board state
  • {:solved, true} - (ie all squares have a solution)
  • An inference rule which allows one or more eliminations to take place
  • A guess - no rule based inferences can be found, so make a guess *

Many strategies generate inferences that whilst true, may not advance the solution

Entry point to solve a sudoku board

Advance the board by one inference step (including guessing)

Iteratively compute a next inference to advance the board (including guessing) and then apply that inference to advance the board state

Functions

apply_inference(board, arg)

Apply inferences to board. This is the process of taking a given inference and a board structure and updating the board to show the implications of the inference.

Note: This may leave to an invalid board (so always check it’s valid after applying any inference)

We understand the following inference rules:

  • :naked_singles
  • :hidden_singles
  • :locked_candidates
  • :naked_tuples
  • :grid_analysis
  • :guess

and also the backtrack inference which simply returns the board to the previous state

  • :backtrack
apply_single_inference(board, atom, arg3)
assign_candidates(squares, candidates)
combination(num, to_pick_from)

Find all combinations of size ‘num’ from the list ‘to_pick_from’

filter_for_unsolved_squares(board)
find_grid_candidates(board)

Find eliminations through Grid Analysis

Formally:

7. grid analysis (X-Wings, Swordfish, etc.)

‘’’ 7r. (r_n ^ c_n ^ k) ^ !(r_n ^ !c_n ^ k) —> !(!r_n ^ c_n ^ k) 7c. (r_n ^ c_n ^ k) ^ !(!r_n ^ c_n ^ k) —> !(r_n ^ !c_n ^ k) ‘’’

(7r) says, “If a candidate k is possible in the intersection of n rows and n columns but is not possible elsewhere in those n rows, then it is also not possible elsewhere in those n columns.”

(7c) says the same, but reversing rows and columns.

find_grid_candidates(squares, candidate)
find_grid_candidates_in_group(squares, candidate, group_fn_1, group_fn_2, label)
find_guess(board)

Guess at a candidate to eliminate from a square.

Considered a last ditch strategy, elimination through constraints is considered more elegant. Strategy is simply to select the square with the least candidates remaining.

find_hidden_singles(board)

Find hidden singles

A hidden single is where there is only a single position possible for a given symbol within a unit (but other options might be possible for that square). Therefore the given symbol must occupy this square.

Formally

2. hidden singles:

2r. (r ^ c ^ k) ^ !(r ^ !c ^ k) --> !(r ^ c ^ !k)
2c. (r ^ c ^ k) ^ !(!r ^ c ^ k) --> !(r ^ c ^ !k)
2b. (b ^ (r ^ c) ^ k) ^ !(b ^ !(r ^ c) ^ k) --> !(b ^ (r ^ c) ^ !k)

(2r) says, “If a candidate k is possible in a certain intersection of row (and column (i.e., a cell) but is not possible elsewhere in that row, then (no other candidates are possible in the that cell.”

(2c) says the same for “elsewhere in that column.”

(2b) says the same for “elsewhere in that block.”

Replacing either the r or the c with b gives us locked candidate rules:

find_hidden_singles(squares, candidate)
find_hidden_singles(squares, candidate, group_fn, label)
find_is_invalid_or_backtrack(board)
find_is_solved(board)
find_locked_candidates(board)

Find locked candidates

A locked candidate is where there exists a box with two/three candidates in a row/col. a) If they don’t exist elsewhere on the row, then they cannot be elsewhere in the box b) If they don’t exist elsewhere in the box, then they cannot be elsewhere on the row/col

Intuitively: a) If the candidate were elsewhere in the box, it could therefore not be in this row/col, hence we would have a contradiction because it would now not be possible anywhere on that row/col

b) If the candidate were elsewhere in the row/col, it could therefore not be in this box, hence we would have a contradiction because it would now not be possible anywhere in this box

Formally

3. locked candidates, type 1:

3r. (r ^ b ^ k) ^ !(r ^ !b ^ k) --> !(!r ^ b ^ k)
3c. (c ^ b ^ k) ^ !(c ^ !b ^ k) --> !(!c ^ b ^ k)

(3r) says, “If a candidate k is possible in a certain intersection of row and block but is not possible elsewhere in that row, then it is also not possible elsewhere in that block.”

(3c) says the same for columns.

4. locked candidates, type 2:

4r. (r ^ b ^ k) ^ !(!r ^ b ^ k) --> !(r ^ !b ^ k)
4c. (c ^ b ^ k) ^ !(!c ^ b ^ k) --> !(c ^ !b ^ k)

(4r) says, “If a candidate k is possible in a certain intersection of row and block but is not possible elsewhere in that block, then it is also not possible elsewhere in that row.”

(4c) says the same, but for columns.

This basic logic generalizes to ALL of the standard types of analysis. Here X_n means a “exactly n of X”, where X=r is row, X=c is column, and X=k is candidate.

find_locked_candidates(squares, candidate)
find_locked_candidates_1(squares, candidate, group_fn, label)
find_locked_candidates_2(squares, candidate, filter_fn, label)
find_naked_singles(board)

Find naked singles

A naked symbol is where there is only a single candidate option for a given square Essentially this is a formal assignment of this option to this square

Formally

1. naked singles:

1r. (r ^ c ^ k) ^ !(r ^ c ^ !k) --> !(r ^ !c ^ k)
1c. (r ^ c ^ k) ^ !(r ^ c ^ !k) --> !(!r ^ c ^ k)
1b. (b ^ (r ^ c) ^ k) ^ !(b ^ (r ^ c) ^ !k) --> !(b ^ !(r ^ c) ^ k)

(1r) says, “If a candidate k is possible in a certain intersection of row and column (i.e., a cell), and no other candidates are possible in that cell, then k is not possible elsewhere in that row.”

(1c) says the same for “that column.”

(1b) says the same for “that block.”

find_naked_tuples(board)

Find naked tuples (and hidden tuples)

A naked tuple is where there are only N choices for N squares in some unit. These choices must therefore belong only within these N squares and so can be removed from all other squares within the rest of the unit

Note: some choices might only be valid in some squares.

Intuitively we can see that if there are only N (total) choices for N squares, then those choices cannot exist elsewhere, if they did we would have N-1 choices for N squares

Note: Hidden tuples are simply the reverse of naked tuples, ie a hidden pair is simply a naked tuple of order N-2

Formally:

5. naked tuples (includes Rules 1r, 1c, and 1b):

5r. (r ^ c_n ^ k_n) ^ !(r ^ c_n ^ !k_n) --> !(r ^ !c_n ^ k_n)
5c. (c ^ r_n ^ k_n) ^ !(c ^ r_n ^ !k_n) --> !(c ^ !r_n ^ k_n)
5b. (b ^ (r ^ c)_n ^ k_n) ^ !(b ^ (r ^ c)_n ^ !k_n) --> !(b ^ !(r ^ c)_n ^ k_n)

(5r) says, “If n candidates are possible in a set of n columns of a given row, and no other candidates are possible and in those n cells, then those n candidates are not possible elsewhere in that row.”

(5c) says the same for columns. (3b) says the same for blocks.

6. hidden tuples (includes Rules 2r, 2c, and 2b):

6r. (r ^ c_n ^ k_n) ^ !(r ^ !c_n ^ k_n) --> !(r ^ c_n ^ !k_n)
6c. (c ^ r_n ^ k_n) ^ !(c ^ !r_n ^ k_n) --> !(c ^ r_n ^ !k_n)
6b. (b ^ (r ^ c)_n ^ k_n) ^ !(b ^ !(r ^ c)_n ^ k_n) --> !(b ^ (r ^ c)_n ^ !k_n)

(6r) says, “If n candidates are possible in a set of n columns of a given row, and those n candidates are not possible elsewhere in that row, then no other candidates are possible in those n cells.”

(6c) says the same for columns. (6b) says the same for blocks.

Note that exchanging k_n and !k_n and exchanging c_n and !c_n in 6r gives an alternative statement of 5r:

5r’. (r ^ !c_n ^ !k_n) ^ !(r ^ c_n ^ !k_n) —> !(r ^ !c_n ^ k_n)

This says: “If other than n candidates are possible in other than n cells of a row, and those other candidates are not possible in this set of n cells, then this set of n candidates is not possible in those other cells. (5r) says the same thing, but in a simpler fashion. What this exchanging shows is that naked tuples are the same as hidden tuples, just seen from different perspectives.

find_naked_tuples_in_group(squares, group_fn, label)
find_next_step(board)

Examine a sudoku board and return either:

  • {:invalid, true} - (ie no candidates left for a given square and no backtracking opportunity)
  • {:backtrack, :contradition} - implies we must have a backtracking option, but we have reached an invalid board state
  • {:solved, true} - (ie all squares have a solution)
  • An inference rule which allows one or more eliminations to take place
  • A guess - no rule based inferences can be found, so make a guess *
generate_all_combinations(list)
group_by_box(square)
group_by_x(square)
group_by_y(square)
n_candidates_in_n_squares?(arg)
other_squares_in_unit(board, other_squares, unit_type)
simplify_inferences(inferences, rule_type, board)

Many strategies generate inferences that whilst true, may not advance the solution

Here we filter out any inferences which don’t advance the board state

solve(initial)

Entry point to solve a sudoku board

Pass in either:

  • a bitstring where 0, dot, or space is a blank and any other symbol is a board symbol
  • a Sudoku.Board type

Returns: {status, inferences, final_board}

Note: In the event of a non unique solution, the solver will stop at the first solution found. To continue simply extract the state from the final_board.backtrack and pass this back to solve/1 to get more solutions (or :invalid if no more solutions to be found)

squares_in_single_box?(squares)
squares_in_single_col?(squares)
squares_in_single_row?(squares)
step_board(board)

Advance the board by one inference step (including guessing).

Computes the next inference and then applies it to the board state to get a new state.

Returns:

  • {{:finished, why}, final_board}
  • {{:step, step_details}, next_board}
step_until_finished(board, steps)

Iteratively compute a next inference to advance the board (including guessing) and then apply that inference to advance the board state.

Stops when we either solve the board, or run out of ways to advance, eg the board is invalid

unique_candidates(squares)