Created
November 24, 2015 03:37
-
-
Save MetroWind/5a3c6ca086d91a202010 to your computer and use it in GitHub Desktop.
A library to work with Sudokus.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import copy | |
class Sudoku(object): | |
"""The Sudoku class. Zero means blank.""" | |
Numbers = set(range(1, 10)) | |
def __init__(self, sudoku=None): | |
if sudoku: | |
self.Data = copy.deepcopy(sudoku.Data) | |
else: | |
self.Data = [] | |
for i in range(9): | |
self.Data.append([0,]*9) | |
def __getitem__(self, idx): | |
Row, Col = idx | |
return self.Data[Row][Col] | |
def __setitem__(self, idx, v): | |
Row, Col = idx | |
self.Data[Row][Col] = v | |
return v | |
def getRow(self, i): | |
return self.Data[i] | |
def getCol(self, i): | |
return [row[i] for row in self.Data] | |
def getBlock(self, row, col, flatten=False): | |
Result = [] | |
for iRow in range(3): | |
Row = [] | |
for iCol in range(3): | |
Row.append(self[3 * row + iRow, 3 * col + iCol]) | |
Result.append(Row) | |
if flatten: | |
return Result[0] + Result[1] + Result[2] | |
else: | |
return Result | |
def __str__(self): | |
Lines = [] | |
for Row in range(9): | |
if Row % 3 == 0: | |
Lines.append("+---+---+---+") | |
Line = [] | |
for Col in range(9): | |
if Col % 3 == 0: | |
Line.append('|') | |
n = self[Row, Col] | |
if n == 0: | |
Line.append(' ') | |
else: | |
Line.append(str(n)) | |
Line.append('|') | |
Lines.append(''.join(Line)) | |
Lines.append("+---+---+---+") | |
return '\n'.join(Lines) | |
@classmethod | |
def validateSeq(cls, xs, allow_blank=False): | |
Result = None | |
if allow_blank: | |
Xs = [x for x in xs if x > 0] | |
Set = set(Xs) | |
Result = (len(Xs) == len(Set) and Set.issubset(cls.Numbers)) | |
else: | |
Set = set(xs) | |
Result = (Set == cls.Numbers) | |
return Result | |
@classmethod | |
def validateBlock(cls, blk, allow_blank=False): | |
return cls.validateSeq(blk[0] + blk[1] + blk[2], allow_blank) | |
def validate(self, allow_blank=False): | |
# Validate rows | |
for i in range(9): | |
if not self.validateSeq(self.getRow(i), allow_blank): | |
return False | |
# Validate columns | |
for i in range(9): | |
if not self.validateSeq(self.getCol(i), allow_blank): | |
return False | |
# Validate blocks | |
for row in range(3): | |
for col in range(3): | |
if not self.validateBlock(self.getBlock(row, col), allow_blank): | |
return False | |
return True | |
def validateSite(self, row, col, allow_blank=False): | |
BlockRow = row // 3 | |
BlockCol = col // 3 | |
return self.validateSeq(self.getRow(row), allow_blank) and \ | |
self.validateSeq(self.getCol(col), allow_blank) and \ | |
self.validateSeq(self.getBlock(BlockRow, BlockCol, True)) | |
def sitePossibilities(self, row, col): | |
"""Return all possible number for the specified site. | |
""" | |
CanRow = self.Numbers - set([x for x in self.getRow(row) if x > 0]) | |
CanCol = self.Numbers - set([x for x in self.getCol(col) if x > 0]) | |
CanBlock = self.Numbers - set([x for x in self.getBlock(row // 3, col // 3, True) if x > 0]) | |
return CanRow & CanCol & CanBlock | |
def findStartingRow(self): | |
"""Return the index of the row that has least blanks (but has at least | |
one). | |
""" | |
StartingRow = 0 | |
Blanks = 10 | |
for Row, i in zip(self.Data, range(9)): | |
n = Row.count(0) | |
if n > 0: | |
if n < Blanks: | |
Blanks = n | |
StartingRow = i | |
return StartingRow | |
def solve(self): | |
# Fill in the 1st number | |
for Row, iRow in zip(self.Data, range(9)): | |
for n, iCol in zip(Row, range(9)): | |
if n == 0: | |
Possibilities = self.sitePossibilities(iRow, iCol) | |
if not Possibilities: | |
return False | |
for N in Possibilities: | |
self[iRow, iCol] = N | |
if self.solve(): | |
return True | |
else: | |
self[iRow, iCol] = 0 | |
return False | |
return True |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment