Subversion Repositories programming

Rev

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