If you are interested in a logarithmic solution, you may have heard of accurate coverage. Given the set Y of a subset of complete x and X, there is a subset of Y y*, which makes y* a division of X.
Here is an example of Python writing.
X = {1, 2, 3, 4, 5, 6, 7}y = { ' A ': [1, 4, 7], ' B ': [1, 4], ' C ': [4, 5, 7], ' D ': [3, 5, 6], ' E ': [2, 3 , 6, 7], ' F ': [2, 7]}
The only solution to this example is [' B ', ' D ', ' F '].
The exact coverage problem is NP-complete: There is no one fast enough way to find the answer within a reasonable time, meaning polynomial time. The x algorithm is invented and implemented by Gaudena. He proposed an efficient implementation technique called the dance chain, which uses a doubly linked list to represent the matrix of the problem.
However, the dance chain can be quite cumbersome to implement and not easy to write correctly. The next step is to show the magic of Python! One day I decided to use Python to write the x algorithm, and I came up with an interesting dance chain variant.
Algorithm
The main idea is to use a dictionary instead of a doubly linked list to represent a matrix. We've got Y. From it we can quickly access the column elements of each row. Now we also need to generate a reverse table of rows, in other words, to quickly access row elements from a column. For this purpose, we convert x into a dictionary. In the above example, it should be written as
X = { 1: {' A ', ' B '}, 2: {' E ', ' F '}, 3: {' d ', ' e '}, 4: {' A ', ' B ', ' C '}, 5: {' C ', ' d '}, 6: {' d ', ' E '}, 7: {' A ', ' C ', ' e ', ' F '}}
Sharp-eyed readers can note that this is slightly different from the representation of Y. In fact, we need to be able to quickly delete and add rows to each column, which is why we use collections. On the other hand, Gartner does not mention this, and virtually all the lines in the algorithm remain unchanged.
The following is the code for the algorithm.
def solve (x, Y, solution=[]): if not X: yield list (solution) else: c = min (x, Key=lambda C:len (X[c]))
for r in List (X[c]): solution.append (r) cols = select (x, Y, R) for s in solve (x, Y, solution): yield s
deselect (x, Y, R, cols) solution.pop () def select (x, Y, R): cols = [] for J in Y[r]: for i in X[j]: For K in y[i]: if k! = J: x[k].remove (i) Cols.append (X.pop (j)) return cols def deselect (X, Y, R , cols): for J in reversed (Y[r]): x[j] = Cols.pop () for I in X[j]: for K in Y[i]: if k! = j:
x[k].add (i)
It's really only 30 lines!
Format input
Before we solve the actual problem, we need to convert the input to the format described above. Can be handled as simple as this
x = {J:set (filter (lambda i:j in y[i], Y)) for J in X}
But it's too slow. If the size of X size is M,y is n, then the iteration count is m*n. In this example the Sudoku lattice size is N, which takes n^5 times. We have a better way.
X = {J:set () for J in X}for I in Y: for J in Y[i]: x[j].add (i)
This is still the complexity of O (m*n), but it is the worst case scenario. On average, it performs much better because it does not need to traverse all of the space bits. In the case of Sudoku, there are exactly 4 entries per line in the matrix, regardless of size, so it has n^3 complexity.
Advantages
- Simple: There is no need to construct complex data structures, all of which are available in the structure Python.
- Readability: The first example above is directly transcribed from the Wikipedia paradigm!
- Flexibility: It can be easily extended to solve Sudoku.
Solving Sudoku
What we need to do is to describe Sudoku as an accurate coverage problem. Here is the complete Sudoku code, which can handle any size, 3x3,5x5, even 2x3, all code less than 100 lines, and contains doctest! (Thanks to Winfried Plappert and David Goodger's comments and suggestions)