Subversion Repositories programming

Rev

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
 
7
import copy
8
from Stack import Stack
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
 
40
		self.domain = [i for i in self.domain if i != value]
41
 
42
	def set_value (self, value):
43
		self.domain = [value]
44
 
45
	def is_singleton (self):
46
		if self.get_len() == 1:
47
			return True
48
 
49
		return False
50
 
51
	def is_empty (self):
52
		if self.get_len () == 0:
53
			return True
54
 
55
		return False
56
 
57
	def get_len (self):
58
		return len(self.domain)
59
 
60
	def get_value (self):
61
		"""Only works if this is a singleton"""
62
		if not self.is_singleton ():
63
			raise ValueError
64
 
65
		return self.domain[0]
66
 
67
#
68
# SudokuPuzzle class.
69
#
70
# This will hold all of the current domains for each position in a sudoku
71
# puzzle, and allow each value to be retrieved by its row,col pair.
72
# This access can be done by using SudokuPuzzle[0,0] to access the first
73
# element, and SudokuPuzzle[8,8] to access the last element.
74
#
75
 
76
class SudokuPuzzle (object):
77
 
78
	def __init__ (self, puzzle=None):
79
		"""Can possibly take an existing puzzle to set the state"""
80
		self.__state = []
81
 
82
		if puzzle:
83
			self.__state = puzzle.__state[:]
84
		else:
85
			for i in xrange(81):
86
				self.__state.append (SudokuDomain())
87
 
88
	def __getitem__ (self, key):
89
		row, col = key
90
		return self.__state [row*9+col]
91
 
92
	def __setitem__ (self, key, value):
93
		row, col = key
94
		self.__state [row*9+col] = value
95
		return value
96
 
97
	def __repr__ (self):
98
		s = ''
99
 
100
		for i in xrange (9):
101
			if i % 3 == 0:
102
				s += '=' * 25
103
				s += '\n'
104
 
105
			for j in xrange (9):
106
				if j % 3 == 0:
107
					s += '| '
108
 
109
				s += '%s ' % str(self[i,j])
110
 
111
			s += '|\n'
112
 
113
		s += '=' * 25
114
		return s
115
 
116
	def __iter__ (self):
117
		for e in self.__state:
118
			yield e
119
 
120
	def valid_index (self, index):
121
		if index < 0 or index > 8:
122
			return False
123
 
124
		return True
125
 
126
	def get_row (self, row, col):
127
		"""Return a list that represents the list that $row is a part of.
128
		This will exclude the element at $row."""
129
		if not self.valid_index (row) or not self.valid_index (col):
130
			raise ValueError # Bad index
131
 
132
		li = []
133
		for i in xrange(9):
134
			if i != col:
135
				li.append (self[row,i])
136
 
137
		return li
138
 
139
	def get_col (self, row, col):
140
		"""Return a list that represents the list that $col is a part of.
141
		This will exclude the element at $col."""
142
		if not self.valid_index (row) or not self.valid_index (col):
143
			raise ValueError # Bad index
144
 
145
		li = []
146
		for i in xrange(9):
147
			if i != row:
148
				li.append (self[i,col])
149
 
150
		return li
151
 
152
	def get_upper_left (self, row, col):
153
		"""Return the row and column of the upper left part of the small
154
		box that contains self[row,col]."""
155
		new_row = row / 3 * 3
156
		new_col = col / 3 * 3
157
 
158
		return (new_row, new_col)
159
 
160
	def get_small_square (self, row, col):
161
		"""Return a list that represents the small square that (row, col) is a
162
		member of. This will exclude the element at (row, col)."""
163
 
164
		(ul_row, ul_col) = self.get_upper_left (row, col)
165
		li = []
166
 
167
		for i in xrange(ul_row, ul_row+3):
168
			for j in xrange(ul_col, ul_col+3):
169
				if not (i == row and j == col):
170
					li.append (self[i,j])
171
 
172
		return li
173
 
174
	def prune (self, row, col, value):
175
		"""Remove all occurances of $value from all of the places
176
		it cannot be in sudoku for the element at (row, col)."""
177
 
178
		for e in self.get_row (row, col):
179
			e.remove_value (value)
180
 
181
		for e in self.get_col (row, col):
182
			e.remove_value (value)
183
 
184
		for e in self.get_small_square (row, col):
185
			e.remove_value (value)
186
 
187
	def puzzle_is_solved (self):
188
		for i in xrange(9):
189
			for j in xrange(9):
190
				if not self[i,j].is_singleton ():
191
					return False
192
 
193
		return True
194
 
195
	def puzzle_is_failed (self):
196
		for i in xrange(9):
197
			for j in xrange(9):
198
				if self[i,j].is_empty ():
199
					return True
200
 
201
		return False
202
 
203
	def solve (self):
204
		changed = True
205
 
206
		while changed:
207
			changed = False
208
			for i in xrange(9):
209
				for j in xrange(9):
210
					e = self[i,j]
211
					if e.is_singleton () and e.used == False:
212
						self.prune (i, j, e.get_value ())
213
						e.used = True
214
						changed = True
215
 
216
		if self.puzzle_is_failed ():
217
			print 'puzzle failed'
218
			return False
219
 
220
		if self.puzzle_is_solved ():
221
			return self
222
 
223
		size = 10000
224
		smallest_rc = (10, 10)
225
 
226
		for i in xrange(9):
227
			for j in xrange(9):
228
				e = self[i,j]
229
				if not e.is_singleton ():
230
					if e.get_len () < size:
231
						size = e.get_len ()
232
						smallest_rc = (i, j)
233
 
234
		print 'splitting at %s (size=%d)' % (smallest_rc, size)
235
 
236
		(r, c) = smallest_rc
237
		spl = self[r,c].get_len ()
238
 
239
		lhalf = self[r,c].domain[:spl/2]
240
		rhalf = self[r,c].domain[spl/2:]
241
 
242
		lcopy = copy.deepcopy (self)
243
		lcopy[r,c] = SudokuDomain (lhalf)
244
		leftsolve = lcopy.solve ()
245
 
246
		rcopy = copy.deepcopy (self)
247
		rcopy[r,c] = SudokuDomain (rhalf)
248
		rightsolve = rcopy.solve ()
249
 
250
		print 'UNABLE TO SOLVE'
251
		return False
252
 
253
 
254
 
255
def main ():
256
	s = SudokuPuzzle ()
257
 
258
	print s
259
 
260
	#e = raw_input ('Enter \'row col val\': ')
261
	#while e:
262
	#	r, c, v = [int(i) for i in e.split()]
263
	#	s[r, c].set_value (v)
264
	#
265
	#	e = raw_input ('Enter \'row col val\': ')
266
 
267
	print 'Enter a row at a time. Use \'e\' for empty squares.'
268
	for i in xrange(9):
269
		e = raw_input('line %d: ' % i)
270
		temp = e.split ()
271
 
272
		count = 0
273
		for j in temp:
274
			try:
275
				s[i,count].set_value (int(j))
276
			except:
277
				pass
278
 
279
			count += 1
280
 
281
 
282
	print s
283
 
284
	#print 'r,c: %s' % str((r, c))
285
	#print 'val: %s' % s[r,c]
286
	#print 'row: %s' % s.get_row (r, c)
287
	#print 'col: %s' % s.get_col (r, c)
288
	#
289
	#print 'upl: %s' % str(s.get_upper_left (r, c))
290
	#print 'ssq: %s' % s.get_small_square (r, c)
291
 
292
	print s.solve ()
293
 
294
if __name__ == '__main__':
295
	main ()