Subversion Repositories programming

Rev

Rev 408 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
405 ira 1
#!/usr/bin/env python
2
 
3
__author__    = "Ira W. Snyder (devel@irasnyder.com)"
4
__copyright__ = "Copyright (c) 2006, Ira W. Snyder (devel@irasnyder.com)"
5
__license__   = "GNU GPL v2 (or, at your option, any later version)"
6
 
408 ira 7
from PyCompat import *
8
 
406 ira 9
import sys
405 ira 10
import copy
408 ira 11
import time
405 ira 12
 
13
#
14
# Domain class.
15
#
16
# This will hold the domain for each place in the SudokuPuzzle class.
17
#
18
 
19
class SudokuDomain (object):
20
 
21
	DEFAULT = [1, 2, 3, 4, 5, 6, 7, 8, 9]
22
 
23
	def __init__ (self, domain=None):
24
		self.domain = self.DEFAULT[:]
25
		self.used = False
26
 
27
		if domain:
28
			self.domain = domain
29
 
30
	def __repr__ (self):
31
		if len(self.domain) == 0:
32
			return 'E'
33
		elif len(self.domain) == 1:
34
			return str(self.domain[0])
35
		else:
36
			return ' '
37
 
38
	def remove_value (self, value, strict=False):
39
		if strict:
40
			if value not in self.domain:
41
				raise ValueError
42
 
407 ira 43
		if value not in self.domain:
44
			return False
45
		else:
46
			self.domain = [i for i in self.domain if i != value]
47
			return True
405 ira 48
 
49
	def set_value (self, value):
50
		self.domain = [value]
51
 
52
	def is_singleton (self):
53
		if self.get_len() == 1:
54
			return True
55
 
56
		return False
57
 
58
	def is_empty (self):
59
		if self.get_len () == 0:
60
			return True
61
 
62
		return False
63
 
64
	def get_len (self):
65
		return len(self.domain)
66
 
67
	def get_value (self):
68
		"""Only works if this is a singleton"""
69
		if not self.is_singleton ():
70
			raise ValueError
71
 
72
		return self.domain[0]
73
 
74
#
75
# SudokuPuzzle class.
76
#
77
# This will hold all of the current domains for each position in a sudoku
78
# puzzle, and allow each value to be retrieved by its row,col pair.
79
# This access can be done by using SudokuPuzzle[0,0] to access the first
80
# element, and SudokuPuzzle[8,8] to access the last element.
81
#
82
 
83
class SudokuPuzzle (object):
84
 
407 ira 85
	def __init__ (self, puzzle=None, printing_enabled=True):
405 ira 86
		"""Can possibly take an existing puzzle to set the state"""
87
		self.__state = []
88
 
89
		if puzzle:
90
			self.__state = puzzle.__state[:]
91
		else:
92
			for i in xrange(81):
93
				self.__state.append (SudokuDomain())
94
 
407 ira 95
		self.printing_enabled = printing_enabled
96
 
405 ira 97
	def __getitem__ (self, key):
98
		row, col = key
99
		return self.__state [row*9+col]
100
 
101
	def __setitem__ (self, key, value):
102
		row, col = key
103
		self.__state [row*9+col] = value
104
		return value
105
 
106
	def __repr__ (self):
107
		s = ''
108
 
109
		for i in xrange (9):
110
			if i % 3 == 0:
111
				s += '=' * 25
112
				s += '\n'
113
 
114
			for j in xrange (9):
115
				if j % 3 == 0:
116
					s += '| '
117
 
118
				s += '%s ' % str(self[i,j])
119
 
120
			s += '|\n'
121
 
122
		s += '=' * 25
123
		return s
124
 
125
	def __iter__ (self):
126
		for e in self.__state:
127
			yield e
128
 
129
	def valid_index (self, index):
130
		if index < 0 or index > 8:
131
			return False
132
 
133
		return True
134
 
135
	def get_row (self, row, col):
136
		"""Return a list that represents the list that $row is a part of.
137
		This will exclude the element at $row."""
138
		if not self.valid_index (row) or not self.valid_index (col):
139
			raise ValueError # Bad index
140
 
141
		li = []
142
		for i in xrange(9):
143
			if i != col:
144
				li.append (self[row,i])
145
 
146
		return li
147
 
148
	def get_col (self, row, col):
149
		"""Return a list that represents the list that $col is a part of.
150
		This will exclude the element at $col."""
151
		if not self.valid_index (row) or not self.valid_index (col):
152
			raise ValueError # Bad index
153
 
154
		li = []
155
		for i in xrange(9):
156
			if i != row:
157
				li.append (self[i,col])
158
 
159
		return li
160
 
161
	def get_upper_left (self, row, col):
162
		"""Return the row and column of the upper left part of the small
163
		box that contains self[row,col]."""
164
		new_row = row / 3 * 3
165
		new_col = col / 3 * 3
166
 
167
		return (new_row, new_col)
168
 
169
	def get_small_square (self, row, col):
170
		"""Return a list that represents the small square that (row, col) is a
171
		member of. This will exclude the element at (row, col)."""
172
 
173
		(ul_row, ul_col) = self.get_upper_left (row, col)
174
		li = []
175
 
176
		for i in xrange(ul_row, ul_row+3):
177
			for j in xrange(ul_col, ul_col+3):
178
				if not (i == row and j == col):
179
					li.append (self[i,j])
180
 
181
		return li
182
 
183
	def prune (self, row, col, value):
184
		"""Remove all occurances of $value from all of the places
185
		it cannot be in sudoku for the element at (row, col)."""
186
 
187
		for e in self.get_row (row, col):
407 ira 188
			if e.remove_value (value):
189
				self.print_domain_changed (e, value)
405 ira 190
 
191
		for e in self.get_col (row, col):
407 ira 192
			if e.remove_value (value):
193
				self.print_domain_changed (e, value)
405 ira 194
 
195
		for e in self.get_small_square (row, col):
407 ira 196
			if e.remove_value (value):
197
				self.print_domain_changed (e, value)
405 ira 198
 
199
	def puzzle_is_solved (self):
200
		for i in xrange(9):
201
			for j in xrange(9):
202
				if not self[i,j].is_singleton ():
203
					return False
204
 
205
		return True
206
 
207
	def puzzle_is_failed (self):
208
		for i in xrange(9):
209
			for j in xrange(9):
210
				if self[i,j].is_empty ():
211
					return True
212
 
213
		return False
214
 
407 ira 215
	def print_domain_changed (self, value, removed_val):
216
		if not self.printing_enabled:
217
			return
218
 
219
		for i in xrange(9):
220
			for j in xrange(9):
221
				if self[i,j] is value:
222
					print 'removed %d from (%d, %d) -> %s' % \
223
						(removed_val, i, j, value.domain)
224
 
225
	def print_generic (self, s):
226
		if not self.printing_enabled:
227
			return
228
 
229
		print s
230
 
231
 
406 ira 232
def solve (puzzle):
408 ira 233
 
234
	# Print the puzzle we're trying to solve
235
	puzzle.print_generic ('\nTrying to solve:')
236
	puzzle.print_generic ('%s\n' % str(puzzle))
237
 
406 ira 238
	changed = True
405 ira 239
 
407 ira 240
	# The main Arc Consistency Algorithm implementation
406 ira 241
	while changed:
242
		changed = False
243
		for i in xrange(9):
244
			for j in xrange(9):
245
				e = puzzle[i,j]
246
				if e.is_singleton () and e.used == False:
247
					puzzle.prune (i, j, e.get_value ())
248
					e.used = True
249
					changed = True
405 ira 250
 
407 ira 251
					# Check if we failed during the last pruning pass
252
					if puzzle.puzzle_is_failed ():
253
						puzzle.print_generic ('Puzzle failed, null entry created!')
254
						return False
405 ira 255
 
407 ira 256
	# Check if we're finished with the puzzle
406 ira 257
	if puzzle.puzzle_is_solved ():
407 ira 258
		puzzle.print_generic ('Puzzle finished! wheee!')
406 ira 259
		return puzzle
405 ira 260
 
407 ira 261
	### Find the smallest node in the puzzle. The smallest node
262
	### is the best cantidate to split.
406 ira 263
	size = sys.maxint
264
	smallest_rc = (10, 10)
405 ira 265
 
406 ira 266
	for i in xrange(9):
267
		for j in xrange(9):
268
			e = puzzle[i,j]
269
			if not e.is_singleton ():
270
				if e.get_len () < size:
271
					size = e.get_len ()
272
					smallest_rc = (i, j)
405 ira 273
 
407 ira 274
	### SPLIT TIME!
406 ira 275
	(r, c) = smallest_rc
276
	spl = puzzle[r,c].get_len ()
405 ira 277
 
406 ira 278
	lhalf = puzzle[r,c].domain[:spl/2]
279
	rhalf = puzzle[r,c].domain[spl/2:]
405 ira 280
 
407 ira 281
	# Split Message
282
	puzzle.print_generic ('splitting at %s (size=%d) -> %s and %s' % \
283
			(smallest_rc, size, lhalf, rhalf))
284
 
285
	# Solve the "left" half
406 ira 286
	lcopy = copy.deepcopy (puzzle)
287
	lcopy[r,c] = SudokuDomain (lhalf)
407 ira 288
	leftsolve = solve(lcopy)
405 ira 289
 
407 ira 290
	# If it solved correctly, then return it back up
406 ira 291
	if leftsolve:
292
		return leftsolve
405 ira 293
 
407 ira 294
	# Solve the "right" half
406 ira 295
	rcopy = copy.deepcopy (puzzle)
296
	rcopy[r,c] = SudokuDomain (rhalf)
407 ira 297
	rightsolve = solve (rcopy)
405 ira 298
 
407 ira 299
	# If it solved correctly, then return it back up
406 ira 300
	if rightsolve:
301
		return rightsolve
405 ira 302
 
407 ira 303
	# Both splits at this level failed, time to work our way back up the tree
304
	puzzle.print_generic ('Both splits at this level failed, back up!')
406 ira 305
	return False
405 ira 306
 
406 ira 307
 
308
 
405 ira 309
def main ():
310
	s = SudokuPuzzle ()
311
 
312
	print 'Enter a row at a time. Use \'e\' for empty squares.'
313
	for i in xrange(9):
314
		e = raw_input('line %d: ' % i)
315
		temp = e.split ()
316
 
317
		count = 0
318
		for j in temp:
319
			try:
320
				s[i,count].set_value (int(j))
321
			except:
322
				pass
323
 
324
			count += 1
325
 
408 ira 326
	tstart = time.time ()
327
	solution = solve (s)
328
	tend = time.time ()
407 ira 329
	print '\nThe solution is:'
408 ira 330
	print str(solution)
331
	print
332
	print 'Took %s seconds to solve' % str(tend - tstart)
405 ira 333
 
334
if __name__ == '__main__':
335
	main ()