patternpythonMajor
Check if a Sudoku board is filled out correctly
Viewed 0 times
correctlyfilledchecksudokuoutboard
Problem
The code below takes a filled out Sudoku board of size NxN, with sub-blocks of nxn, and checks if the solution is correct.
The
These are the function definitions:
```
import numpy as np
def main_function(board):
print('Function main_function() is called\n')
# If the input is of type "list", convert it to a numpy array
# Otherwise keep it as it is
if type(board) == list:
board = np.asarray(board)
print('Checking rows:')
rows_are_correct = check_rows(board)
if not rows_are_correct:
print('There are incorrect rows on the board')
return
else:
print('All rows are correct')
print('Checking columns:')
# Transpose board and do the same operations as with the columns
transposed_board = board.transpose()
cols_are_correct = check_rows(transposed_board)
if not cols_are_correct:
print('There are incorrect columns on the board')
return
else:
prin
main_function takes a board as input. This function calls check_rows and check_blocks. The first of the two is used to check both the rows and columns of the board. The second one is used to check the nxn sub-blocks.check_if_all_numbers takes a vector of numbers as input and checks if the vector contains all numbers 1 2 ... n. This function is called by both check_rows and check_blocks check_rows takes a board as input, loops over each of the rows and checks whether it contains all numbers 1 2 ... n using check_if_all_numbers.check_blocks takes a board as input, divides it into blocks and checks if each block contains all numbers 1 2 ... n using check_if_all_numbers.The
main_function will stop once it finds an incorrect row, columns or block. For a correct board, the output from the function will look like this:Function main_function() is called
Checking rows:
All rows are correct
Checking columns:
All columns are correct
Checking blocks:
All blocks are correctThese are the function definitions:
```
import numpy as np
def main_function(board):
print('Function main_function() is called\n')
# If the input is of type "list", convert it to a numpy array
# Otherwise keep it as it is
if type(board) == list:
board = np.asarray(board)
print('Checking rows:')
rows_are_correct = check_rows(board)
if not rows_are_correct:
print('There are incorrect rows on the board')
return
else:
print('All rows are correct')
print('Checking columns:')
# Transpose board and do the same operations as with the columns
transposed_board = board.transpose()
cols_are_correct = check_rows(transposed_board)
if not cols_are_correct:
print('There are incorrect columns on the board')
return
else:
prin
Solution
Since you are using
You can easily extract a row, a colomn or a block:
You can also convert a block to a line using
This means that you only need one function to check for a line of data and you can use it on rows, columns or reshaped blocks.
This function should be pretty simple, all you need to do is to ensure that all elements in the line are distincts; or in other words unique. This lead to
Unfortunately,
All is left to do is providing the right values to this function:
Now, as suggested by others, you should not
You could, of course, define your own exceptions to allow for a better granularity. Usage is like:
In a comment, @OscarSmith suggest using
numpy, you can take advantage of their extended slicing capabilities to easily extract data. For example, using the following array:array = numpy.array([
[9, 1, 4, 5, 7, 3, 8, 2, 6],
[5, 3, 2, 4, 8, 6, 1, 7, 9],
[9, 1, 3, 4, 2, 5, 8, 6, 7],
[7, 1, 8, 2, 9, 3, 4, 5, 6],
[9, 7, 1, 3, 2, 6, 5, 8, 4],
[5, 3, 1, 7, 4, 2, 9, 8, 6],
[1, 8, 6, 5, 3, 9, 2, 4, 7],
[7, 1, 5, 9, 2, 4, 6, 8, 3],
[1, 3, 7, 5, 8, 4, 2, 9, 6]])You can easily extract a row, a colomn or a block:
>>> array[3, :]
array([7, 1, 8, 2, 9, 3, 4, 5, 6])
>>> array[:, 1]
array([1, 3, 1, 1, 7, 3, 8, 1, 3])
>>> array[:3, :3]
array([[9, 1, 4],
[5, 3, 2],
[9, 1, 3]])You can also convert a block to a line using
reshape (either using -1 or the right size):>>> array[:3, :3].reshape(-1)
array([9, 1, 4, 5, 3, 2, 9, 1, 3])
>>> array[:3, :3].reshape(9)
array([9, 1, 4, 5, 3, 2, 9, 1, 3])This means that you only need one function to check for a line of data and you can use it on rows, columns or reshaped blocks.
This function should be pretty simple, all you need to do is to ensure that all elements in the line are distincts; or in other words unique. This lead to
numpy.unique:>>> numpy.unique(array[2, :])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.unique(array[:, 2])
array([1, 2, 3, 4, 5, 6, 7, 8])Unfortunately,
numpy.unique modifies the original ordering, so you can't have, for instance numpy.unique(array) == array to check if you have the right elements. You can, however, compare the sizes (shapes) of each array:def is_valid(line):
return numpy.unique(line).shape == line.shapeAll is left to do is providing the right values to this function:
def validate_sudoku(grid):
board = numpy.asarray(grid)
size, check_size = board.shape
assert(size == check_size)
for i in range(size):
if not is_valid(board[i, :]):
print 'Row', i+1, 'is invalid'
return
if not is_valid(board[:, i]):
print 'Column', i+1, 'is invalid'
return
block_size = int(size**.5)
for i in range(size):
block_row, block_column = divmod(i, block_size)
block = board[block_row*block_size:(block_row+1)*block_size, block_column*block_size:(block_column+1)*block_size]
if not is_valid(block.reshape(size)):
print 'Block', i+1, 'is invalid'
return
print 'Board is valid'Now, as suggested by others, you should not
print messages but instead have more advanced control flow to empower the caller. I suggest using exceptions:def validate_sudoku(grid):
board = numpy.asarray(grid)
size, check_size = board.shape
assert(size == check_size)
for i in range(size):
if not is_valid(board[i, :]):
raise ValueError('Row {} is invalid'.format(i+1))
if not is_valid(board[:, i]):
raise ValueError('Column {} is invalid'.format(i+1))
block_size = int(size**.5)
for i in range(size):
block_row, block_column = divmod(i, block_size)
block = board[block_row*block_size:(block_row+1)*block_size, block_column*block_size:(block_column+1)*block_size]
if not is_valid(block.reshape(size)):
raise ValueError('Block {} is invalid'.format(i+1))You could, of course, define your own exceptions to allow for a better granularity. Usage is like:
try:
validate_sudoku(my_grid)
except ValueError as e: # Or whatever custom error
print(e)
else:
print('Sudoku is correct')In a comment, @OscarSmith suggest using
.flat instead of .reshape(). But since flat is an iterator, you'd have to convert it to an array so is_valid doesn't crash:if not is_valid(numpy.asarray(block.flat)):
raise ValueError('Block {} is invalid'.format(i+1))Code Snippets
array = numpy.array([
[9, 1, 4, 5, 7, 3, 8, 2, 6],
[5, 3, 2, 4, 8, 6, 1, 7, 9],
[9, 1, 3, 4, 2, 5, 8, 6, 7],
[7, 1, 8, 2, 9, 3, 4, 5, 6],
[9, 7, 1, 3, 2, 6, 5, 8, 4],
[5, 3, 1, 7, 4, 2, 9, 8, 6],
[1, 8, 6, 5, 3, 9, 2, 4, 7],
[7, 1, 5, 9, 2, 4, 6, 8, 3],
[1, 3, 7, 5, 8, 4, 2, 9, 6]])>>> array[3, :]
array([7, 1, 8, 2, 9, 3, 4, 5, 6])
>>> array[:, 1]
array([1, 3, 1, 1, 7, 3, 8, 1, 3])
>>> array[:3, :3]
array([[9, 1, 4],
[5, 3, 2],
[9, 1, 3]])>>> array[:3, :3].reshape(-1)
array([9, 1, 4, 5, 3, 2, 9, 1, 3])
>>> array[:3, :3].reshape(9)
array([9, 1, 4, 5, 3, 2, 9, 1, 3])>>> numpy.unique(array[2, :])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.unique(array[:, 2])
array([1, 2, 3, 4, 5, 6, 7, 8])def is_valid(line):
return numpy.unique(line).shape == line.shapeContext
StackExchange Code Review Q#134240, answer score: 23
Revisions (0)
No revisions yet.