1 from weakref
import proxy
3 symbols
= [str(x
+ 1) for x
in range(9)] + [chr(x
+ 97) for x
in xrange(26)]
6 class CellGroup(object):
7 """Represents any group of cells in a grid."""
14 # XXX inherited __init__
16 def find_value(self
, value
):
17 """Returns the cells that can be a specific value."""
19 for cell
in self
.cells
:
20 if value
in cell
._values
:
21 possible_cells
.append(cell
)
23 if len(possible_cells
) == 0:
28 def resolve_uniques(self
):
29 for value
in xrange(self
._grid
._size
):
30 # XXX cache values that are taken care of
31 possible_cells
= self
.find_value(value
)
33 if len(possible_cells
) > 1:
37 target_cell
= possible_cells
[0]
38 if target_cell
.solved
:
40 # XXX this is what cache is for
43 # Only cell in the group that can be value
44 target_cell
.set(value
)
48 def _get_box_row(self
):
49 return self
._pos
// self
._grid
._box_width
50 box_row
= property(_get_box_row
)
52 def _get_box_column(self
):
53 return self
._pos % self
._grid
._box_height
54 box_column
= property(_get_box_column
)
57 # XXX generator + docstring
59 for row
in xrange(self
._grid
._box_height
):
60 for col
in xrange(self
._grid
._box_width
):
61 cell_row
= row
+ self
.box_row
* self
._grid
._box_height
62 cell_col
= col
+ self
.box_column
* self
._grid
._box_width
63 cells
.append(self
._grid
.cell(cell_row
, cell_col
))
65 cells
= property(_get_cells
)
67 def __init__(self
, grid
, position
):
68 self
._grid
= proxy(grid
)
74 # XXX generator + docstring
76 for col
in xrange(self
._grid
._size
):
77 cells
.append(self
._grid
.cell(self
._pos
, col
))
79 cells
= property(_get_cells
)
81 def __init__(self
, grid
, position
):
82 self
._grid
= proxy(grid
)
86 class Column(CellGroup
):
88 # XXX generator + docstring
90 for row
in xrange(self
._grid
._size
):
91 cells
.append(self
._grid
.cell(row
, self
._pos
))
93 cells
= property(_get_cells
)
95 def __init__(self
, grid
, position
):
96 self
._grid
= proxy(grid
)
101 """Represents a single cell/value within a sudoku grid."""
105 def _get_solved(self
):
106 """True iff this cell has been solved."""
107 return len(self
._values
) == 1
108 solved
= property(_get_solved
)
110 def _get_value(self
):
111 """Returns this cell's value, if it has one known."""
113 return self
._values
[0]
115 value
= property(_get_value
)
118 """Returns the Row object associated with this cell."""
119 return self
._grid
._rows
[self
._row
]
120 row
= property(_get_row
)
122 def _get_column(self
):
123 """Returns the Column object associated with this cell."""
124 return self
._grid
._columns
[self
._col
]
125 column
= property(_get_column
)
128 """Returns the Box object associated with this cell."""
129 # Some actual math required here!
130 # Row 0..2 -> box 0..2
131 # Col 0..2 -> box 0, 3, 6 (box col 0)
132 box_row
= self
._row
// self
._grid
._box_height
133 box_col
= self
._col
// self
._grid
._box_width
134 box_idx
= box_row
* self
._grid
._box_height
+ box_col
135 return self
._grid
._boxes
[box_idx
]
136 box
= property(_get_box
)
138 def __init__(self
, grid
, row
, column
):
139 self
._grid
= proxy(grid
)
142 self
._values
= range(self
._grid
.size
)
143 self
._normalized
= False
145 def set_naively(self
, value
):
146 """Sets the value of this cell, WITHOUT eliminating the value from
147 every other cell in its row/column/box.
150 self
._values
= [value
]
152 def set(self
, value
):
153 """Sets the value of this cell and adjusts the grid accordingly."""
154 self
.set_naively(value
)
155 self
._normalized
= False
161 """Checks to see if this cell has only one possible value left. If
162 so, sets that as its value and eliminates it from every related cell.
163 This method is exhaustive; that repeated calls should have no effect.
170 # Set this now just in case of infinite looping
171 self
._normalized
= True
174 # Don't know the value yet
178 for group_type
in 'row', 'column', 'box':
179 group
= getattr(self
, group_type
)
180 for cell
in group
.cells
:
183 cell
.eliminate(self
.value
)
186 def eliminate(self
, value
):
187 """Eliminates the given value as a possibility for this cell."""
188 if value
in self
._values
:
189 self
._values
.remove(value
)
191 if len(self
._values
) == 0:
192 # XXX give me a real exception here
195 self
._normalized
= False
200 """Stringification for pretty-printing."""
201 if self
.value
!= None:
202 return symbols
[self
.value
]
210 """Represents a Sudoku grid."""
214 def _cellidx(self
, row
, col
):
215 """Hashes a row and column into a flat array index."""
216 return row
* self
._size
+ col
220 def _get_box_height(self
):
221 return self
._box_height
222 box_height
= property(_get_box_height
)
224 def _get_box_width(self
):
225 return self
._box_width
226 box_width
= property(_get_box_width
)
230 size
= property(_get_size
)
232 def _get_cell_groups(self
):
233 return self
._rows
+ self
._columns
+ self
._boxes
234 cell_groups
= property(_get_cell_groups
)
238 def __init__(self
, box_height
=3, box_width
=None):
240 box_width
= box_height
242 self
._box_height
= box_height
243 self
._box_width
= box_width
244 self
._size
= box_height
* box_width
246 self
._rows
= [Row(self
, i
) for i
in xrange(self
._size
)]
247 self
._columns
= [Column(self
, i
) for i
in xrange(self
._size
)]
248 self
._boxes
= [Box(self
, i
) for i
in xrange(self
._size
)]
250 self
._cells
= range(self
._size
** 2)
251 for row
in xrange(self
._size
):
252 for col
in xrange(self
._size
):
253 self
._cells
[self
._cellidx(row
, col
)] \
254 = Cell(self
, row
, col
)
256 def from_lists(self
, *rows
):
257 for row
in xrange(self
._size
):
258 for col
in xrange(self
._size
):
259 value
= rows
[row
][col
]
262 self
.cell(row
, col
).set_naively(value
- 1)
265 def cell(self
, row
, column
):
266 return self
._cells
[self
._cellidx(row
, column
)]
272 """Returns True iff the grid is solved. Raises an exception if an
273 integrity problem is found, such as a value appearing twice in a row.
279 """Attempts to solve the grid."""
280 # XXX track how many cells are changed and repeat as appropriate
282 # Step 0: Normalize cells, i.e. find any that can only be one value
283 self
.normalize_cells()
285 # Step 1: Find values that can only go in one cell in a group
286 for group
in self
.cell_groups
:
287 group
.resolve_uniques()
290 def normalize_cells(self
):
291 """Normalizes every cell in the grid."""
292 for cell
in self
._cells
:
297 """Pretty-printing."""
299 for box
in xrange(self
._box_height
):
300 for col
in xrange(self
._box_width
):
305 for row
in xrange(self
._size
):
306 if row % self
._box_height
== 0:
310 for col
in xrange(self
._size
):
311 if col % self
._box_width
== 0:
313 res
+= str(self
.cell(row
, col
))
327 [ 0, 0, 0, 6, 9, 0, 0, 0, 0 ],
328 [ 9, 0, 5, 0, 0, 8, 7, 6, 0 ],
329 [ 0, 0, 4, 0, 0, 1, 0, 2, 0 ],
330 [ 6, 0, 0, 0, 5, 0, 0, 0, 3 ],
331 [ 3, 8, 0, 0, 0, 0, 0, 4, 9 ],
332 [ 7, 0, 0, 0, 3, 0, 0, 0, 2 ],
333 [ 0, 7, 0, 9, 0, 0, 3, 0, 0 ],
334 [ 0, 2, 3, 1, 0, 0, 4, 0, 8 ],
335 [ 0, 0, 0, 0, 8, 3, 0, 0, 0 ],