下面的代码采用大小为NxN的填充数独板(带有nxn的子块),并检查解决方案是否正确。该函数调用main_functioncheck_rows。两者中的第一个用于检查电路板的行和列。第二个用于检查nxn个子块。

check_blocks将一个数字向量作为输入,并检查该向量是否包含所有数字check_if_all_numbers1 2 ... ncheck_rows都调用此函数。将板子作为输入,将其划分为块,然后使用check_blocks检查每个块是否包含所有数字check_rows。一旦找到不正确的行,列或块,1 2 ... n将停止。对于正确的电路板,该功能的输出应如下所示:

Function main_function() is called

Checking rows:
All rows are correct
Checking columns:
All columns are correct
Checking blocks:
All blocks are correct


这些是函数定义: >
保存为“ sudoku_checker.py”。我在文件底部有以下几行。

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:
        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_permutation

def 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_permutation

def 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_permutation


我正在使用Spyder和Python 2.7。只需单击“运行”即可调用该程序。老实说,我不知道如何从命令提示符下调用。

如何改进此代码。我是Python的初学者,我正在寻找任何改进。


我在代码中使用了很多Numpy。用纯Python这样做会更好,还是Numpy是一个不错的选择?如果是这样,怎么办?我起初尝试了此方法,但失败了,取而代之的是Numpy。
我应该使用普通的Python和check_if_all_numbers代替吗?
将代码划分为单独的函数的方式好吗?
关于check_blocks中的索引又如何?
有关最佳做法等方面的任何一般性提示?

评论

您是否考虑过仅使用Python的内置set操作来检查集合是否相等,而不是执行所有np.sort ... np.array_equal的工作?它会更短,更简单(并且可能更快)。

如果使用块为2×3的6×6网格会发生什么?

@MathiasEttinger,它不适用于6x6网格。 “ ..with nxn的子块”。我知道将其适应这种情况非常容易(以block_size作为输入,并使用它在check_blocks中创建两个带有索引的不同向量。但是,为了保持这种相对简单,我没有这样做。我是个初学者,我认为最好学习简单的方法,一旦掌握了正确的技术,将其扩展到涵盖更多案例将很容易。

#1 楼

由于使用的是numpy,因此可以利用其扩展的切片功能轻松提取数据。例如,使用以下数组:

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]])


还可以使用reshape(使用-1或合适的大小)将块转换为行:函数来检查一行数据,您可以在行,列或整形的块上使用它。

此函数应该非常简单,您要做的就是确保该行中的所有元素截然不同或换句话说就是独特的。这会导致numpy.unique:不幸的是,numpy.unique修改了原始顺序,因此例如numpy.unique(array) == array便无法检查您是否具有正确的元素。但是,您可以比较每个数组的大小(shape s):

>>> 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])


剩下要做的就是为此函数提供正确的值:
print消息,而应具有更高级的控制流来授权调用者。我建议使用异常:

>>> 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



在评论中,@ OscarSmith建议使用.flat而不是.reshape()。但是由于flat是迭代器,因此您必须将其转换为数组,这样is_valid才会崩溃:

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'


评论


\ $ \ begingroup \ $
这看起来很不错,很容易=)非常容易阅读和理解。我看到您在主函数内部有循环,而不是将其拆分为几个函数。那是故意的吗?您为什么不希望为行和块使用单独的功能?我觉得通常最常见的提示几乎与任务无关,都是:“将其拆分为功能”。 (IMO,我认为用这种方式更容易阅读,但是我觉得它与我在其他地方看到的内容背道而驰。例如,最后一个循环可能在函数def validate_blocks内部,这将使主函数更整洁。
\ $ \ endgroup \ $
– Steewie Griffin
16年7月8日在10:46

\ $ \ begingroup \ $
这取决于几个因素。其中最重要的是提高/降低可读性和实用性。通常建议将巨大的功能拆分为更细小的部分,以便:1.增强对顶级算法的理解,并2.提取出多余的部分。它们中没有一个真正适用于此。实际上,情况恰恰相反,因为块部分依赖于较早计算出的值,所以您将不得不重新计算它们(meh)或将它们作为参数传递(这会使函数本身的用处不大)。
\ $ \ endgroup \ $
–301_Moved_Permanently
16年7月8日在10:58

\ $ \ begingroup \ $
很有道理。 :)
\ $ \ endgroup \ $
– Steewie Griffin
16年7月8日在11:14

\ $ \ begingroup \ $
我要说的一件事是使用.flat而不是重塑。更清楚了。
\ $ \ endgroup \ $
–奥斯卡·史密斯(Oscar Smith)
16年7月10日在18:17

\ $ \ begingroup \ $
@OscarSmith .flat是一个迭代器,不能用作替换数组的对象。但我将其添加为替代方法。
\ $ \ endgroup \ $
–301_Moved_Permanently
16年7月10日在19:14

#2 楼

首先是一些一般性建议。

您应将代码行限制为80个字符或更少。按照官方的python样式指南进行练习,它将使您成为更好的pythonista。目前,至少至少要通过说明性的命名约定来阅读各部分。

我强烈建议您避免使用numpy,因为根据首页,


NumPy是使用Python进行科学计算的基本软件包。


它是为处理大型数值数据集而提高效率和易于使用的。我不会将您的数据集本身称为“数字”,因为您没有对数字进行任何算术运算,它们只是用作每个块中可能出现的不同唯一值的名称。此类数据的单词是“标称”。 />
# All the logic pulled out of main and put into the checker functions
def main_function(board):
    print('Checking rows:')
    # Remove the boolean variable and just check the function's return
    # Also check the positive condition first to eliminate "not"
    if check_rows(board):
        print('All rows are correct')
    else:
        print('There are incorrect rows on the board')
        return

    print('Checking columns:')
    # Put the transpose operation inside a function called "check_colunms"
    if check_columns(board):
        print('All columns are correct')  
    else:
        print('There are incorrect columns on the board')
        return

    # Check if all nxn blocks are correct
    print('Checking blocks:')
    if check_blocks(board):
        print('All blocks are correct')
    else:
        print('There are incorrect blocks on the board')
        return

# Just a useful helper
def required_nums(board):
    return range(1, len(board[0]) + 1)

def check_rows(board):
    # Check that for all rows on board, all required numbers are present
    return all(all(n in row for n in required_nums(board)) for row in board)

def check_columns(board):
    return check_rows(transpose(board))

# Plagiarized this one right from stackoverflow from a google search for
#   "transpose python 2d list"
def transpose(board):
    return zip(*board)

# Now we want to do the block check logic simply just like the column case
def check_blocks(board):
    return check_rows(view_nsquares_as_rows(board))

# A helper just to retrieve a subsquare from our perfect-square sudoku board.
# In an NxN board we have N subsquare-blocks.
def get_nsquare(board, index):
    size = len(board[0])
    # No need to use a library to take square root
    subsize = int(size**(0.5))
    # Get block coordinates using standard array coordinate deflattening trick
    x, y = index % subsize, index / subsize # Note this is integer division
    # Multiply-out to get the subsquare bounds
    x1, x2 = subsize * x, subsize * (x+1)
    y1, y2 = subsize * y, subsize * (y+1)
    return [row[x1:x2] for row in board[y1:y2]]

# This is like a transpose, but putting each block into a row instead
#   of putting each column into a row.
def view_nsquares_as_rows(board):
    return [flatten_2d(get_nsquare(board, i)) for i in range(len(board[0]))]

# One more helper to flatten a 2d list into a single row, also
#  plagiarized from the first google result.
def flatten_2d(list2d):
    return [item for sublist in list2d for item in sublist]


评论


\ $ \ begingroup \ $
但是,我确实认为这些语句有点让人眼花:乱:返回[list2d中的sublist的项目,sublist中的项目的项目]。几乎就像我必须把它写在纸上才能看到正在发生的事情...
\ $ \ endgroup \ $
– Steewie Griffin
16年7月8日在10:30

\ $ \ begingroup \ $
只需将其视为嵌套在一行中的for循环即可。或者,如果您是数学家,请像设置生成器符号一样阅读它。学习曲线有些许变化,但如果使用正确,列表理解既是惯用的也是有用的。当然,如果您滥用它们,它们将很难阅读,但是根据经验,如果您可以将其放在一行中(具有描述性的变量名),那么熟悉该结构的人应该能够像阅读它们一样容易嵌套的for循环。
\ $ \ endgroup \ $
–机器向往
16年7月8日在10:49

\ $ \ begingroup \ $
@StewieGriffin python#5的Zen:扁平比嵌套更好
\ $ \ endgroup \ $
–机器向往
16年7月8日在15:06

\ $ \ begingroup \ $
我建议set(row)== required_nums(board)而不是all(required_nums(board)中n为n的行),其中required_nums返回一个集合。
\ $ \ endgroup \ $
– njzk2
16年7月9日在3:45

\ $ \ begingroup \ $
这是一个很好的建议,
\ $ \ endgroup \ $
–机器向往
16年7月10日在18:43

#3 楼

我没有使用Python的经验,但是我将把check_if_all_numbers函数的执行方式更改为:数字)×5个基本操作
要使用9×2个操作创建数组(不计算数组管理),使用log(9)×9个操作对数组进行排序,并使用9个操作比较数组。 br />
示例:

def check_if_all_numbers(numbers):

    bincheck = 0
    goal = 0

    for n in numbers
        bincheck = bincheck | (1 << (n-1)) # set the n'th bit of binCheck to 1
        goal = goal << 1 | 1 # set one more bit to 1 in goal

    return (bincheck == goal) # check all numbers


另一个示例:

With Numbers = 982 134 675 (correct)
Bincheck = 0b 0000 0000 0000, Goal = 0b 0000 0000 0000 #Init
Bincheck = 0b 0001 0000 0000, Goal = 0b 0000 0000 0001 #Add 9
Bincheck = 0b 0001 1000 0000, Goal = 0b 0000 0000 0011 #Add 8
Bincheck = 0b 0001 1000 0010, Goal = 0b 0000 0000 0111 #Add 2
Bincheck = 0b 0001 1000 0011, Goal = 0b 0000 0000 1111 #Add 1
Bincheck = 0b 0001 1000 0111, Goal = 0b 0000 0001 1111 #Add 3
Bincheck = 0b 0001 1000 1111, Goal = 0b 0000 0011 1111 #Add 4
Bincheck = 0b 0001 1010 1111, Goal = 0b 0000 0111 1111 #Add 6
Bincheck = 0b 0001 1110 1111, Goal = 0b 0000 1111 1111 #Add 7
Bincheck = 0b 0001 1111 1111, Goal = 0b 0001 1111 1111 #Add 5
Bincheck == Goal > it's ok


评论


\ $ \ begingroup \ $
聪明,是的。它肯定会快得多=)我不确定我是否会直接跳入操作变量中的位。我想我可能会从学习基本的“ Pythonic”方法而不是“棘手/聪明”方法中受益。 +1,我可能实际上会使用这一天。
\ $ \ endgroup \ $
– Steewie Griffin
16年7月8日在9:47

\ $ \ begingroup \ $
使用位集的巧妙技巧。但是请注意,1 << -1将因ValueError:负移位计数而崩溃。例如,如果您使用0表示一个空正方形,那可能是个问题。我建议def check_if_all_numbers(numbers):return reduce(operator.ior,(1 << n代表n个数字))==(1 << 10)-2。
\ $ \ endgroup \ $
– 200_success
16年7月10日在9:24

#4 楼

main函数将打印发现的问题,这意味着您无法重用其结果。它只能是程序的终点。

我建议您返回适当的常量,例如SudokuState.ROW_INVALIDSudokuState.BLOCK_INVALID。薄包装器可以通过字典将这些常数转换为打印值。
如果所有行中的每个1-9数字恰好包含一次,则有效”

评论


\ $ \ begingroup \ $
我将研究第二段中我要如何做。不过,它看起来很简单,所以它应该不太困难=)我尝试使用所有方法,但无法使其正常工作,所以我改变了方法。那是在我使用flatten之前,也许我可以将其与我现在拥有的代码一起使用=)
\ $ \ endgroup \ $
– Steewie Griffin
16年7月8日在9:50

\ $ \ begingroup \ $
@StewieGriffin不难,请记住使用生成器理解(如果您想自己解决它,请不要提前阅读)返回all(check_if_all_numbers(board [n])for range(0,board。形状[1]))
\ $ \ endgroup \ $
– Caridorc
16年7月8日在10:38

#5 楼

如果您想使用numpy,则可以利用它的许多功能使您的代码更加流畅:

import numpy as np

def is_good_sudoku(board):
    board = np.asarray(board)
    assert board.ndim == 2  # Input must be a two-dimensional table.
    N = board.shape[0]
    assert board.shape[1] == N  # Input must be square.
    n = int(np.sqrt(N))
    assert n * n == N  # Input size must be a perfect square.

    numbers_1_N = np.arange(1, N + 1)

    # Check rows and columns
    if (not np.all(np.sort(board, axis=1) == numbers_1_N) or
            not np.all(np.sort(board.T, axis=1) == numbers_1_N)):
        return False

    # Check blocks
    blocks = board.reshape(n, n, n, n).transpose(0, 2, 1, 3).reshape(N, N)
    return np.all(np.sort(board.T, axis=1) == numbers_1_N)


我假设这不是公共函数,因此它永远都不会收到形状不正确的输入,因此使用assert来记录期望值,而不是像验证用户输入时那样引发适当的错误。 ,并为该函数指定一个相应的名称,以便您可以使用该代码编写类似于if is_good_sudoku(board): ...的代码。对NumPy并不熟悉,但实际上并不能保证在使用它的代码中大量使用它。通过将形状为(N, N)的数组与形状为(N,)的数组进行比较来完成检查。由于广播,NumPy将后者视为也具有(N, N)形状,所有行都与提供的(N,)形状数组相同。
重塑为4D以提取块。刚开始时这有点儿弯腰,但是一旦掌握了它,它就非常有用。通过将原始板调整为(n, n, n, n),我们基本上将其变成了(n, n)的方形表,每个项目本身就是(n, n)的方形表。然后我们重新排列轴,以便在取消重塑时,每一行都变成我们感兴趣的块之一。这将复制整个数组(第一次重塑只是创建了相同内存的视图),因此对于大型阵列,可能效率不高。但是对于典型的9x9数独,您甚至不必担心。


#6 楼

查看当前答案,似乎还没有讨论检查Sudoku板的行,列和较大正方形的汇总方法。如果我们放弃幼稚的方法,而是假设Sudoku将始终在9x9的1-9网格上播放,那么我们知道所有行,列和正方形的总和应为45。

因此,对于表示行,列或正方形(3x3)的任何numpy.array,我们可以使用Numpy的sum函数并检查sum是否等于45。如果我们将其变成一个函数,它可能看起来像this:

import numpy

def check_array(structure, valid = 45):
    """
    Verifies that a sudoku column, row, or square is valid (assumes a 9x9 grid that requires the unique values 1 - 9 in each array).
    @param
        structure  – numpy.array – required  – a col, row, or square representation to validate
        valid      – integer     – optional  – valid sum value
    @return
        result     – boolean                 – is the array valid?
    """
    result = numpy.sum(array) == valid
    return result


这将用作验证每种电路板结构是否有效的通用方法。

或者,不使用Numpy,我们也可以使用集合来表示行,列和正方形,并编写类似的函数来执行检查:

def check_set(structure, valid = {1, 2, 3, 4, 5, 6, 7, 8, 9}):
    """
    Verifies that a sudoku column, row, or square is valid (assumes a 9x9 grid that requires the unique values 1 - 9 in each array).
    @param
        structure  – set         – required  – a col, row, or square representation to validate
        valid      – set         – optional  – set representation of valid row, col, or square
    @return
        result     – boolean     – is the array valid?
    """
    result = len(array & valid) == len(valid)
    return result


当然,要使用此功能,您必须将行,列和正方形作为集合键入,这使这种方法的相关性降低,除非您有目的地追求仅Python路由。

最后,假设我们不了解Sudoku的玩法,我们可以结合以下方法来解决附带的情况(有多种总计方式为45):

def check_set(structure, valid = {1, 2, 3, 4, 5, 6, 7, 8, 9}):
    """
    Verifies that a sudoku column, row, or square is valid (assumes a 9x9 grid that requires the unique values 1 - 9 in each array).
    @param
        structure  – set         – required  – a col, row, or square representation to validate
        valid      – set         – optional  – set representation of valid row, col, or square
    @return
        result     – boolean     – is the array valid?
    """
    if (len(array & valid) == len(valid)) and (sum(array) == sum(valid)):
        return true
    else:
        return false


注意:索引和错误处理将在这些功能之外进行。

评论


\ $ \ begingroup \ $
在最后一个解决方案中,否,因为该集合将仅包含唯一值,因此其长度为1并返回false。 @StewieGriffin
\ $ \ endgroup \ $
–Greenstick
16年7月10日在22:43