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 calledChecking rows:All rows are correctChecking columns:All columns are correctChecking blocks:All blocks are correctThese are the function definitions:import numpy as npdef 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:        print('All columns are correct')      # Check if all nxn blocks are correct    print('Checking blocks:')    blocks_are_correct = check_blocks(board)    if not blocks_are_correct:        print('There are incorrect blocks on the board')        return    else:        print('All blocks are correct')def check_blocks(board):    size_of_blocks = int(np.sqrt(board.shape[1]))   # Number of cols/rows in each sub-block       indices = np.arange(0,size_of_blocks)           # Create a vector [0,1,..n] that represents                                                    # the row and columns indices of each block    is_permutation = True                           # Initialize is_permutation to True    for x in range(0,size_of_blocks):               # Double loop over rows and columns        for y in range(0,size_of_blocks):            rid = indices + y*size_of_blocks        # row index[0,1,..,n] then [0,1]+1*n...            cid = indices + x*size_of_blocks        # column index            sub_block = board[np.ix_(rid,cid)]      # Subtract sub-blocks using np.ix_            flat_block = sub_block.flatten()        # flatten the block and pass            is_permutation = check_if_all_numbers(flat_block)   # and pass it to check_if_all_numbers            if not is_permutation:                return is_permutation                    return is_permutationdef check_if_all_numbers(numbers):    all_numbers = np.arange(1,numbers.size+1)   # List of numbers 1-N    sorted_numbers = np.sort(numbers)           # Sort numbers    is_permutation = np.array_equal(sorted_numbers, all_numbers) # Check if all numbers are present    return is_permutationdef check_rows(board):                  # Initialize is_permutation to True    is_permutation = True    for n in range(0,board.shape[1]):   # Loop over the rows/columns        is_permutation = check_if_all_numbers(board[n]) # Check if all numbers are present        if not is_permutation:            return is_permutation    return is_permutationThis is saved as sudoku_checker.py. I have the following lines in the bottom of the file. board = [[1,2,3,4],[4,3,1,2],[2,1,4,3],[3,4,2,1]]main_function(board)board = [[9,4,2,7,6,1,8,5,3],[3,8,7,5,9,2,6,4,1],[6,1,5,8,3,4,2,9,7], \[2,6,3,1,4,7,5,8,9],[8,7,1,9,2,5,3,6,4],[4,5,9,3,8,6,1,7,2], \[7,9,6,2,1,8,4,3,5],[5,2,8,4,7,3,9,1,6],[1,3,4,6,5,9,7,2,8]]main_function(board)I'm using Spyder, with Python 2.7. The program is called by simply clicking Run. To be honest, I don't know how to call if from the command prompt.How can I improve this code. I'm a beginner in Python and I'm looking for any improvements. I've used a lot of Numpy in the code. Would it be better to do this in plain Python, or is Numpy a good choice? If so, how? I tried this at first but failed, and went for Numpy instead.Should I have used plain Python and set's instead? Is the way I've divided the code into separate functions good? What about the indexing in check_blocks?Any general tips regarding best practice etc.?
Check if a Sudoku board is filled out correctly
python;performance;beginner;python 2.7;sudoku
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.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))