【问题标题】:Find the biggest square in numpy array在numpy数组中找到最大的正方形
【发布时间】:2017-11-09 15:42:49
【问题描述】:

我正在尝试解决这个算法问题:在 numpy 数组中找到只有一个值的最大正方形。

示例图片:

我的代码花费了太多时间。有没有办法提高速度?

import numpy as np
answer = 0
def allsame(board):
    memory = board[0,0]
    board = np.matrix(board)
    for i in range(board[0].size):
        for j in range(board[0].size):
            if board[i,j] != memory: return False
    return True

def findLargestSquare(board):
    global answer
    list = []

    a = np.matrix(board)
    if a[0].size == 1 or a[:,1].size==1: return answer
    if a[1].size < a[:,1].size: ran = a[1].size
    else: ran = a[:,1].size
    for i in range(ran+1):
        for j in range(ran+1):
           if a[i:j,i:j].size > 0 and allsame(a[i:j,i:j])==True:
                    if a[i:j,i:j].size > answer:
                       list.append(a[i:j,i:j].size)
                       answer = a[i:j,i:j].size

    findLargestSquare(a[1:])
    return findLargestSquare(a[:,1:])
    return answer


#testBoard = [['x','o','g'],['b','a','j'],['u','q','p']]
testBoard = [['X','O','O','O','X'],['X','O','O','O','O'],['X','X','O','O','O'],['X','X','O','O','O'],['X','X','X','X','X']]
print(findLargestSquare(testBoard))

我将我的代码更改为自卷积方法。 你能看看哪一部分是错的吗?

import numpy as np
import time
answer = 0


def findLargestSquare(board):
    global answer


    a = np.array(board)

    for k in reversed(range(a[0].size + 1)):
        conv_size = k
        for i in range(a[0].size - conv_size + 1):
            num = i
            for j in range(a[0].size - conv_size + 1):
                #print('i:', i, 'j:', j)
                print(a[i:i + conv_size, j:j + conv_size])
                #print('unique: ',np.unique(a[i:i+ conv_size,j:j+conv_size]).size)
                if(np.unique(a[i:i+ conv_size,j:j+conv_size]).size == 1):
                    #print("returning")
                    return len(a[i:i+ conv_size,j:j+conv_size])**2
                num = num + 1
                print("================")
    return len(a[i:i+ conv_size,j:j+conv_size])**2



# testBoard = [['x','o','g'],['b','a','j'],['u','q','p']]
testBoard = [['X', 'O', 'O', 'O', 'X'], ['X', 'O', 'O', 'O', 'O'], ['X', 'X', 'O', 'O', 'O'], ['X', 'X', 'O', 'O', 'O'],
             ['X', 'X', 'X', 'X', 'X']]


print(findLargestSquare(testBoard))

【问题讨论】:

  • 这是一个众所周知的DP 问题。 Link。在这里,您只需要调用此方法 2 次。第一次是O,下次是X

标签: python algorithm


【解决方案1】:

您可以在 O(n^2) 中执行此操作,而不是您当前的 O(n^4)allsame()O(n^2) 中并被称为 O(n^2) 次):

使用一个新的矩阵 best_size,这样best_size[i, j] 应该包含您原始棋盘中从(i, j) 开始的最大正方形的大小。按照此规则从末尾开始填充此矩阵:

def get_best_size(a, best_size, i, j):
    # TODO Handle boundaries: best_size = 1 there
    if not a[i, j] == a[i+1, j] == a[i, j+1]:
        return 1
    min_neighbor_best_size = min(best_size[i+1, j], best_size[i, j+1])
    if a[i, j] == a[i + min_neighbor_best_size , j + min_neighbor_best_size ]:
        return min_neighbor_best_size + 1
    else:
        return min_neighbor_best_size 

只要画出来就可以告诉你为什么这条规则有效。

然后,您只需从数组的末尾迭代到开头,并记住最好的数组:

best = 0
for i in range(ran,-1,-1):
    for j in range(ran,-1,-1):
        best_size[i, j] =  get_best_size(a, best_size, i, j)
        best = max(best, best_size[i, j])
return best

【讨论】:

    【解决方案2】:

    您的代码存在一些根本性错误,从某种意义上说,这不是您应该使用 Python 的方式。试着先阅读一些好的介绍和例子。我推荐这个book 和这些scipy lecture notes

    让我们从头开始

    都一样

    有多种方法可以检查数组是否具有所有相同的元素

    np.unique

    numpy 有一个方法可以返回unique 元素。如果所有元素都相同,则此数组的长度应为 1

    np.ones

    您可以取第一个元素,用np.ones 创建一个数组,然后将它与这个元素相乘。初始数组应该等于这个新数组

    findLargestSquare(板)

    这个方法到底应该做什么?

    global answer

    为什么是全球性的?

    ``findLargestSquare(a[1:])`

    这一行被调用,但你对返回值不做任何事情。它可能所做的只是改变全局答案

    列表

    这个list 会发生什么。 PS,不要给变量命名built-ins

    矩阵

    为什么使用a = np.matrix(board) 始终将您的董事会保留为numpy.array 将是最简单和最有效的。此外,01 的矩阵将比 X0 的高效得多

    .大小

    如果你想在两个方向之一的大小为1的情况下停止算法的递归,1 in a.shape

    替代方法

    如果你想在 0 的 numpy.array 中找到 1 上的正方形,你可以使用卷积

    数组创建

    a = np.zeros((10,10), dtype=int)
    ones = ((1, 1), (1, 2), (2, 1), (2, 2), (0, 0), (4, 5))
    for point in ones:
        a[point] = 1
    

    内核创建和卷积

    这使用卷积来搜索原始数组中是否存在 i by i 核

    def find_largest_square_helper(a):
        for i in range(2, min(a.shape)): # changed this to 2, because the 1 is trivial
            kernel = np.ones((i, i), dtype=int)
            if scipy.signal.convolve(a, kernel).max() != i**2:
                return(i-1)
    

    如果您期望大方块,您可以从最大的方块开始,然后像这样往下走:

    def find_largest_square_helper2(a):
        for i in range(min(a.shape), 0, -1):
            kernel = np.ones((i, i), dtype=int)
            if scipy.signal.convolve(a, kernel).max() == i**2:
                return(i)
    

    超过 1 个字符

    当您不仅在0s 的数组中查找1s 的最大正方形时,您还遍历数组中所有可能的字符,并在集合理解中调用此函数。然后在这个set中寻找最大值

    def find_largest_square(board):
        a = np.array(board)
        max_squares = {find_largest_square_helper(a==char) for char in np.unique(a)}
        return max(max_squares)
    

    【讨论】:

    • 你不应该以相反的顺序探索 i 的值吗?
    • @gdelab 确实,你可以两者都做,而且我发布的原版混合了 2 (这就是你在这里编辑时调整它时得到的,而不是测试调整
    • 如果您必须检查多个值的最大平方,您可以为每个值生成10 的矩阵,并调用此方法对于所有这些值
    • 我用自卷积方法修改了第二个代码。但它需要太长时间并且给出错误的答案。你能看看吗?
    【解决方案3】:

    第二次尝试的改进

    import numpy as np
    import time
    answer = 0
    
    
    def findLargestSquare(board):
        global answer
    
    
        a = np.array(board)
    
        for k in reversed(range(a[0].size + 1)):
            conv_size = k
            for i in range(a[0].size - conv_size + 1):
                num = i
                for j in range(a[0].size - conv_size + 1):
                    #print('i:', i, 'j:', j)
                    print(a[i:i + conv_size, j:j + conv_size])
                    #print('unique: ',np.unique(a[i:i+ conv_size,j:j+conv_size]).size)
                    if(np.unique(a[i:i+ conv_size,j:j+conv_size]).size == 1):
                        #print("returning")
                        return len(a[i:i+ conv_size,j:j+conv_size])**2
                    num = num + 1
                    print("================")
        return len(a[i:i+ conv_size,j:j+conv_size])**2
    

    answer

    是不必要的,无处使用。当然不需要在算法中使用global

    变量的重命名

    conv_size = knum = i 是不必要的。如果你想这样命名它们,那么在 for 循环中这样做,num 甚至不会在算法的其余部分中使用

    range(a[0].size)

    这每次都会创建一个新数组(或view,我不确定100%),如果a[1] 更短,则会出现问题。 max_size = min(a.shape) 更干净

    双for循环

    不用循环两次,可以使用itertools.product生成子正方形的边坐标

    for size in reversed(range(2,  max_size + 1)):
        k = max_size = size + 1
        for i, j in itertools.product(range(k), repeat=2):
    

    返回一个生成器,其中包含 5 的结果:

    [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 0), (1, 1), (1, 2 ), (1, 3), (1, 4), (2, 0), (2, 1), (2, 2), (2, 3), (2, 4), (3, 0), (3, 1), (3, 2), (3, 3), (3, 4), (4, 0), (4, 1), (4, 2), (4, 3), (4 , 4)]

    支票本身

    if(np.unique(a[i:i+ conv_size,j:j+conv_size]).size == 1):
                        #print("returning")
        return len(a[i:i+ conv_size,j:j+conv_size])**2
    

    无需重复创建arrayview 就可以做得更干净

    sub_square = a[i:i+size, j:j+size]
    if len(np.unique(sub_square)) == 1:
        return len(sub_square) ** 2
            # calculations
    

    结合

    import itertools
    import numpy as np
    
    def findLargestSquare(board):
        a = np.array(board)
        max_size = min(a.shape)
        for size in reversed(range(2,  max_size + 1)):
    #         print('looking for identical squares of size: ', size)
            k = max_size - size + 1
            for i, j in itertools.product(range(k), repeat=2):
    #             print('checking position: ', (i, j))
                sub_square = a[i:i+size, j:j+size]
                count = len(np.unique(sub_square))
    #             print(count, ' elements in: \n', sub_square)
                if count == 1:
                    return len(sub_square) ** 2
                    # if you also want to know the position, you could do
                    # return len(sub_square) ** 2 ,  (i, j)
    #             print("================")
        return 1 # no squares found
    

    【讨论】:

    • 这个解决方案的效率比DP低:在每个位置,你必须计算值的数量-> O(n^4)
    • 我知道,我仍然认为我的另一个答案中列出的卷积方法更好,但这是 OP 的第二个答案,有一些(小)改进,应该可以加快更大阵列的解决方案
    • np.unique 方法的替代方法是为每个子正方形生成另一个具有所有相同元素的子正方形(原始子正方形的 [0,0])并进行比较到这个子广场,但我怀疑这会更快
    【解决方案4】:

    我修正了第一个答案:

    def get_best_size(a, best_size, i, j, x):
        if i+1 >= a.shape[0] or j+1 >= a.shape[1]:
            return 1
        if not (a[i, j] == a[i+1, j] == a[i, j+1] == x):
            return 1
        min_neighbor_best_size = int(min(best_size[i+1, j], best_size[i, j+1]))
        if a[i, j] == a[i + min_neighbor_best_size , j + min_neighbor_best_size ]:
            return min_neighbor_best_size + 1
        else:
            return min_neighbor_best_size
    
    # looking for largest X block in A
    def doGetBest(a, x):
        best_size = np.ones(a.shape)
        best = 1
        best_pair = (0,0)
        for i in range(a.shape[0]-1,-1,-1):
            for j in range(a.shape[1]-1,-1,-1):
                best_size[i, j] =  get_best_size(a, best_size, i, j, x)
                if best < best_size[i, j]:
                    best = best_size[i, j]
                    best_pair = (i,j)
    
        return best, best_pair
    

    【讨论】:

      【解决方案5】:

      由于没有人提供DP方法的例子,这里是:

      def findLargestSquare(M): 
          n = len(M) 
          S = [[0 for _ in range(n)] for _ in range(n)] 
          max_s = 0
          for i in range(1, n): 
              for j in range(1, n): 
                  if M[i][j] == 1: 
                      S[i][j] = min(S[i][j-1], S[i-1][j], S[i-1][j-1]) + 1
                      if S[i][j] > max_s:
                          max_s = S[i][j]
          return max_s
      

      作为比较,这是我在求职面试中提出的一种蛮力方法(对于 500x500 矩阵,它慢了大约 100 倍):

      def findLargestSquare(arr):
          if sum([sum(row) for row in arr]) == 0:  
              return 0
          n = len(arr)
          for k in range(n, 1, -1):  #check every kxk square from k=n to k=2
              for i in range(n-k+1):
                  #for every row i check every starting position j from j=0 to j=n-k
                  j_next = 0
                  for j in range(n-k+1):
                      if j > j_next:
                          #break as soon as an element of this row segment is not 1:
                          count = 0
                          for p in range(j, j+k, 1):
                              if arr[i][p] != 1:
                                  j_next = p
                                  break
                          else:  ##check the rows below:
                              keep_going = True
                              for r in range(i+1, i+k, 1):
                                  if keep_going:
                                      for p in range(j, j+k, 1):
                                          if arr[r][p] != 1:
                                              keep_going = False
                                              j_next = p
                                              break
                                      else:
                                          count += 1
                                      if count == k-1: #square is found
                                          return k
          else:
              return 1
      

      【讨论】:

        猜你喜欢
        • 2019-09-10
        • 2011-12-06
        • 1970-01-01
        • 1970-01-01
        • 2014-04-03
        • 2014-04-26
        • 2013-06-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多