Last active
February 15, 2020 23:44
-
-
Save michalwa/5d58f4c748ce680474bbed6d1c4e7c5c to your computer and use it in GitHub Desktop.
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
from random import choice | |
from itertools import chain | |
def is_tuple(value, shape=None): | |
""" Checks if the given value is a tuple and optionally checks its shape | |
specified by the `shape` parameter """ | |
if type(value) == tuple: | |
if shape is None: | |
return True | |
if type(shape) != tuple: | |
raise ValueError('Shape must be either None or a tuple of types') | |
if len(shape) == len(value): | |
check_type = lambda a, b: type(a) == b if type(b) != tuple else is_tuple(a, b) | |
if all(check_type(a, b) for a, b in zip(value, shape)): | |
return True | |
return False | |
class Board: | |
def __init__(self, size): | |
self.size = size | |
self.cells = [[None for _ in range(size)] for _ in range(size)] | |
@property | |
def cols(self): | |
return [*self.cells] | |
@property | |
def rows(self): | |
return [*zip(*self.cells)] | |
@property | |
def values(self): | |
return [*chain.from_iterable(self.cells)] | |
def __getitem__(self, index): | |
if is_tuple(index, (int, int)): | |
return self.cells[index[0]][index[1]] | |
else: | |
raise IndexError('Board index must be a pair of ints, ' + str(index) + ' given') | |
def __setitem__(self, index, value): | |
if is_tuple(index, (int, int)): | |
self.cells[index[0]][index[1]] = value | |
else: | |
raise IndexError('Board index must be a pair of ints, ' + str(index) + ' given') | |
def solve(self, col=0, row=0): | |
next_col, next_row = (col + 1) % self.size, row + 1 if col == self.size - 1 else row | |
is_last = next_row == self.size | |
if self[col, row] is None: | |
choices = set(range(1, self.size + 1)) - set(self.cols[col]) - set(self.rows[row]) | |
for n in choices: | |
self[col, row] = n | |
if is_last or self.solve(next_col, next_row): | |
return True | |
else: | |
self[col, row] = None | |
return False | |
return is_last or self.solve(next_col, next_row) | |
def print(self): | |
# Find the longest string value of a cell for alignment | |
maxlen = max(map(lambda n: len(str(n)) if n is not None else 0, self.values)) | |
cellstr = lambda c: str(c) if c is not None else '-' | |
for row in self.rows: | |
print(' '.join(cellstr(cell).rjust(maxlen) for cell in row)) | |
if __name__ == '__main__': | |
size = int(input('Board size?: ')) | |
if size <= 0: | |
raise ValueError('Size must be greater than 0') | |
num_random = int(input('Number of initially randomized cells?: ')) | |
if num_random < 0: | |
raise ValueError('Number must not be negative') | |
board = Board(size) | |
empty = [(col, row) for col in range(size) for row in range(size)] | |
for _ in range(num_random): | |
col, row = choice(empty) | |
empty.remove((col, row)) | |
vals = set(range(1, size + 1)) - set(board.cols[col]) - set(board.rows[row]) | |
board[col, row] = choice(list(vals)) | |
print('\nInitial board:') | |
board.print() | |
if board.solve(): | |
print('\nSolved board:') | |
board.print() | |
else: | |
# This will be reached at some point if the board is initially invalid | |
# but after a tremendous amount of time... | |
print('\nBoard could not be solved.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment