Allegro.cc - Online Community

Allegro.cc Forums » Game Design & Concepts » Sudoku

This thread is locked; no one can reply to it. rss feed Print
 1   2 
Sudoku
kentl
Member #2,905
November 2002

So you've built a Sudoku-solver which doesn't need to do any form of search? In each step it always knows what to do without looking at future steps (ie seatch)? Care to fill us in on how it works? And perhaps point us to your solver if its available on the net? It would be interesting material.

Joshua Rogers
Member #2,499
June 2002
avatar

I'm not sure that I fully understand your question, but yes, I have built a solver. For each square it keeps track of what it can be and what it can't be. Simple deduction yields the answers. Much in the same way that a human would. No guessing is needed.

If you would like the program, just email me at joshuaarogers at gmail dot com. (Notice there is an a between joshua and rogers). I hope that it helps. Thanks.

---

Now approaching 0.1 forum posts per year!

Evert
Member #794
November 2000
avatar

My solver works by operating on bits: for each of the squares, bits are set for which numbers `may be' there. For each column, row and mini-block we have the same. A sequence of logical ands and xors then eliminates impossible combinations, after which bitshifts and logical ors construct the new row and column bitfields, whichare used in the next iteration. It gets a bit more complicated if the constraints are non-local, in which case simple shifts no longer do the trick, at least not easily.
The advantage of the method is that it eliminates all unknowns that it can in one sweep, and automatically applies `difficult' constraints as well as easier ones. It will either solve the puzzle in a split second in fewer than ten iterations, or fail in a similar number of iterations.

Carrus85
Member #2,633
August 2002
avatar

Well, it turns out that it happens quite often that you cannot solve a puzzle via pure "flagging" alone. Take this example:

000|200|304
061|300|092
302|050|000
---+---+---
420|805|100
108|000|207
005|9X2|046
---+---+---
000|090|701
610|004|980
509|001|000

It becomes quite obvious that the X should be the number 1. Unfortunately, simply "flagging" what a blank can/cannot be doesn't provide you with a definate answer in this case, without doing a block uniqueness test. (Since it is the only square in the given block that can be the value 1, it must be the value 1). Unfortunately, by simply marking the squares that cannot be one from the row/column/coexist on same block rules, you cannot deduce this without another step.

Of course, this is easily fixed by occasionally doing a uniqueness test for all of the squares and updating them accordingly. But then you run into puzzles like this: (websudoku evil puzzle #8,873,319,999)

040|050|000
600|000|005
020|000|867
---+---+---
000|001|300
090|384|050
006|700|000
---+---+---
387|000|010
500|000|002
000|070|090

The algorithm so far can only fill in the puzzle to this point, then dies because of a lack of information:

040|050|000
600|000|005
025|000|867
---+---+---
050|001|300
090|384|050
036|705|000
---+---+---
387|000|010
509|000|002
000|070|090

So, no, you cannot solve the puzzle simply by "flagging" possible solutions using the core rules.

Here is the python program I've been using so far to solve the puzzles:

1#!/usr/bin/python -OO
2 
3# Sudoku Solver
4# By Caleb Deveraux aka Carrus85
5 
6# Note: Currently doesn't solve the more difficult puzzles!
7 
8import sys
9 
10def block_iterator(coords, excludeSelf = False):
11 """
12 Generates an in iterator over all of the elements of the block containing
13 coords
14 """
15 row, column = coords
16
17 row_bounds, column_bounds = (0,3), (0,3)
18
19 if row > 2 and row < 6:
20 row_bounds = (3,6)
21 elif row > 5:
22 row_bounds = (6,9)
23 
24 if column > 2 and column < 6:
25 column_bounds = (3,6)
26 elif column > 5:
27 column_bounds = (6,9)
28 
29 for i in range(row_bounds[0], row_bounds[1]):
30 for j in range(column_bounds[0], column_bounds[1]):
31 if (i,j) == coords and excludeSelf:
32 continue
33 yield i,j
34 
35def puzzle_iterator():
36 """
37 Generates an iterator over the entire puzzle
38 """
39 for i in range(9):
40 for j in range(9):
41 yield i,j
42 
43def single_flag_iterator(flags):
44 """
45 Iterates over flags, returning coordinates for any flags
46 that only can take on one possible value
47 """
48 for i in puzzle_iterator():
49 if len( lookup(i, flags) ) == 1:
50 yield i
51 
52def row_iterator(coords, excludeSelf = False):
53 """
54 Generates an iterator over the all of the items in coords' row
55 """
56 for i in range(9):
57 if (i,coords[0]) == coords and excludeSelf:
58 continue
59 yield coords[0],i
60 
61def column_iterator(coords, excludeSelf = False):
62 """
63 Generates an iterator over all of the items in coords' column
64 """
65 for i in range(9):
66 if (i,coords[0]) == coords and excludeSelf:
67 continue
68 yield i, coords[1]
69 
70def lookup(coords, where):
71 """
72 Performs a basic lookup when given a coords tuple (row, column)
73 """
74 return where[coords[0]][coords[1]]
75 
76def lookup_set(coords, where, what):
77 """
78 Performs a lookup, setting the value of coords to what
79 """
80 where[coords[0]][coords[1]] = what
81 
82def unique_entries(coords, flags):
83 """
84 Returns the unqiue entries for a given coordinate on the board that
85 no other entry in block containing said coord shares
86 """
87 return reduce( lambda x,y: x-y, [lookup(i, flags) for i in block_iterator(coords, True)], lookup(coords, flags) )
88 
89def uniqueify_block(coords, flags):
90 """
91 Iterates over an entire block, performing uniqueness tests
92 """
93 for i in block_iterator(coords):
94 temp = unique_entries(i,flags)
95 if len(temp) == 1: # if it turns out we can only take on one value due to uniqueness requirements,
96 lookup_set(i, flags, temp) # set our set variable to a set containing only this value
97 
98def uniqueify_all(flags):
99 """
100 Quickly updates all of the items in every block using the uniqueness test
101 """
102 uniqueify_block( (0,0), flags ) # Top-Left block
103 uniqueify_block( (0,3), flags ) # Top-Center block
104 uniqueify_block( (0,6), flags ) # Top-Right block
105
106 uniqueify_block( (3,0), flags ) # Left block
107 uniqueify_block( (3,3), flags ) # Center block
108 uniqueify_block( (3,6), flags ) # Right block
109 
110 uniqueify_block( (6,0), flags ) # Bottom-Left block
111 uniqueify_block( (6,3), flags ) # Bottom-Center block
112 uniqueify_block( (6,6), flags ) # Bottom-Right block
113 
114 
115def mark(coords, flags, value):
116 """
117 Marks rows, columns and block entries so they cannot become
118 value.
119 """
120 for i in row_iterator(coords): # Mark Rows
121 lookup(i, flags).discard(value)
122 
123 for i in column_iterator(coords): # Mark Columns
124 lookup(i, flags).discard(value)
125 
126 for i in block_iterator(coords): # Mark Block
127 lookup(i, flags).discard(value)
128 
129def update_flags(puzzle, flags):
130 """
131 Updates all flags once. Called only to initialize the flags
132 to a default state.
133 """
134 for i in puzzle_iterator():
135 value = lookup(i, puzzle)
136 if value: # if this point on the puzzle has been specified
137 lookup_set(i, flags, set()) # assign it the empty set
138 mark(i, flags, value) # and update markings
139 uniqueify_all(flags)
140 
141 
142def solve(puzzle):
143 """
144 Solves the given puzzle.
145 0's are treated as empty blanks.
146
147 Note: May return an unsolved puzzle with the current algorithm
148 """
149 flags = [[set((1,2,3,4,5,6,7,8,9)) for a in xrange(9)] for b in xrange(9)]
150 
151 update_flags(puzzle, flags) # initialize flags
152 
153 filter1 = lambda x: len(x) == 1
154 reduce1 = lambda x,y: filter(filter1, x) + filter(filter1, y)
155
156 while len(reduce(reduce1, flags)): # while flags with one possible value exist
157 for i in single_flag_iterator(flags): # for every flag with one possible value
158 value = lookup(i, flags).pop() # retrieve and remove that possible value from the set
159 lookup_set( i, puzzle, value ) # set the value in the puzzle
160 mark(i, flags, value) # update flag markings
161 uniqueify_all(flags) # update all flags using the uniqueness tests
162 
163 filter2 = lambda x: x == 0
164 reduce2 = lambda x,y: filter(filter2, x) + filter(filter2, y)
165
166 if len(reduce(reduce2, puzzle)): # returns false if the puzzle isn't compelte at this point
167 return False
168
169 return True
170
171
172
173if __name__ == '__main__':
174 puzzle = [[int(x) for x in raw_input()] for a in xrange(9)] # Read puzzle from stdin
175 if not solve(puzzle) and len(sys.argv) > 1 and sys.argv[1] == '-v': # if using the -v switch
176 print "Cannot find solution to puzzle. I got this far: " # print warning message
177
178 for i in puzzle:
179 print "%d%d%d%d%d%d%d%d%d" % tuple(i) # output puzzle information

I'm going to look into the more advanced solution methods and add them to the program.

EDIT:

Yes, if the constrant's are not local, it is quite difficult to solve the puzzle using bits (or sets, as I have in the above example) alone without adding some more "interesting" logic.

EDIT2:

As for the more simple puzzle, it looks like this above program takes only 3 passes to solve it (three rounds through the while loop in the solve function) :o. And it only takes 3 passes before it realizes it cannot complete the harder puzzle as well. :)

spunit262
Member #5,545
February 2005
avatar

Could someone translate that program into C so I can read it.

Carrus85
Member #2,633
August 2002
avatar

Well, you could always just learn python. It is great for just hacking up stuff. I will admit though, some of the stuff I used in the above program is rather odd (yield, lambda's, [foo for bar in foobar()], reduce, filter)... 8-). I'll be the first to admit some of those functions don't apply cleanly in C though (you can do a lot of them in C++ using the STL). (No, I'm not saying you can't do them in C, I'm saying that you can't do them this easily in C)

Evert
Member #794
November 2000
avatar

Quote:

So, no, you cannot solve the puzzle simply by "flagging" possible solutions using the core rules.

That's what I meant by saying you need non-local constraints.
The good thing about the algorithm is that it is fast if it works. I should really find the time to update my little solver...

kentl
Member #2,905
November 2002

Quote:

The good thing about the algorithm is that it is fast if it works. I should really find the time to update my little solver...

What you get which can solve all Sudoku's is a good search with back-tracking. I think you could use an A* and measure the distance to the goal by the number of local constraints. So that it gets "spot on" if there are only local constraints all the way to the solved puzzle. In that case you will also get an optimal search (as it's one of the features of a correctly implemented A*).

Simon Parzer
Member #3,330
March 2003
avatar

Quote:

The good thing about the algorithm is that it is fast if it works. I should really find the time to update my little solver...

It's a SUDOKU. Even an unoptimized brute force solver needs less than one second for one of the harder puzzles (which your solver cannot even solve). I don't say that it is a sophisticated solution, but it works perfectly, and coding it takes about half an hour.

 1   2 


Go to: