HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMajor

Check if a Sudoku board is filled out correctly

Submitted by: @import:stackexchange-codereview··
0
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.

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 correct


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

Solution

Since you are 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.shape


All 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.shape

Context

StackExchange Code Review Q#134240, answer score: 23

Revisions (0)

No revisions yet.