【问题标题】:creating a spiral array in python?在python中创建一个螺旋数组?
【发布时间】:2016-08-18 11:39:43
【问题描述】:

我和我的伙伴试图在 python 中创建一个有趣的游戏,其中输入到数组中的元素以螺旋方式访问。我尝试了几种方法,例如下面给出的一种方法 (source)。

def spiral(X, Y):
  x = y = 0
  dx = 0
  dy = -1
  for i in range(max(X, Y)**2):
    if (-X/2 < x <= X/2) and (-Y/2 < y <= Y/2):
        print (x, y)
        # DO STUFF...
    if x == y or (x < 0 and x == -y) or (x > 0 and x == 1-y):
        dx, dy = -dy, dx
    x, y = x+dx, y+dy

上述语句访问螺旋循环中的元素并为定义的数组 AE 打印它们。我想知道如何将给定的数组 AE 转换为螺旋数组

【问题讨论】:

  • 为什么这个问题会有反对票。
  • 你对螺旋阵列的尺寸有什么限制吗?是的,你想要它用于任何“k x n”数组吗?或者也许只适用于 k=n;或 k,n - 奇数,还是 k=n=5?如果 k 或/和 n 是偶数,你选择什么作为起点?

标签: python algorithm python-2.7


【解决方案1】:

开场白

该问题与以螺旋顺序打印数组的问题密切相关。事实上,如果我们已经有一个函数可以做到这一点,那么问题就相对简单了。

how to produce a spiral matrix 或如何以螺旋顺序排列loopprint 有大量资源。即便如此,我还是决定使用 numpy 数组编写自己的版本。这个想法不是原创的,但使用 numpy 使代码更简洁。

另一个原因是我发现的大多数生成螺旋矩阵的示例(包括问题和其他答案中的代码)仅处理奇数 n 的大小为 n x n 的方阵。在其他大小的矩阵中查找起点(或终点)可能很棘手。例如,对于 3x5 矩阵,它不能是中间单元格。下面的代码是通用的,起点(终点)的位置取决于函数spiral_xxx的选择。

代码

第一个函数按顺时针螺旋顺序展开数组:

import numpy as np

def spiral_cw(A):
    A = np.array(A)
    out = []
    while(A.size):
        out.append(A[0])        # take first row
        A = A[1:].T[::-1]       # cut off first row and rotate counterclockwise
    return np.concatenate(out)

根据我们从哪里开始以及如何旋转矩阵,我们可以用八种不同的方式编写这个函数。我将给出另一个,它与问题图像中的矩阵变换一致(稍后会很明显)。所以,更进一步,我将使用这个版本:

def spiral_ccw(A):
    A = np.array(A)
    out = []
    while(A.size):
        out.append(A[0][::-1])    # first row reversed
        A = A[1:][::-1].T         # cut off first row and rotate clockwise
    return np.concatenate(out)

它是如何工作的:

A = np.arange(15).reshape(3,5)
print(A)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

print(spiral_ccw(A))
[ 4  3  2  1  0  5 10 11 12 13 14  9  8  7  6]

请注意,结束(或开始)点不是中间单元格。此函数适用于所有类型的矩阵,但我们需要一个辅助函数来生成 螺旋索引

def base_spiral(nrow, ncol):
    return spiral_ccw(np.arange(nrow*ncol).reshape(nrow,ncol))[::-1]

例如:

print(base_spiral(3,5))
[ 6  7  8  9 14 13 12 11 10  5  0  1  2  3  4]

现在是两个主要功能。一个将矩阵转换为相同维度的螺旋形式,另一个还原转换:

def to_spiral(A):
    A = np.array(A)
    B = np.empty_like(A)
    B.flat[base_spiral(*A.shape)] = A.flat
    return B

def from_spiral(A):
    A = np.array(A)
    return A.flat[base_spiral(*A.shape)].reshape(A.shape)

示例

矩阵 3 x 5:

A = np.arange(15).reshape(3,5)
print(A)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

print(to_spiral(A))
[[10 11 12 13 14]
 [ 9  0  1  2  3]
 [ 8  7  6  5  4]]

print(from_spiral(to_spiral(A)))
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]]

来自问题的矩阵:

B = np.arange(1,26).reshape(5,5)
print(B)
[[ 1  2  3  4  5]
 [ 6  7  8  9 10]
 [11 12 13 14 15]
 [16 17 18 19 20]
 [21 22 23 24 25]]

print(to_spiral(B))
[[21 22 23 24 25]
 [20  7  8  9 10]
 [19  6  1  2 11]
 [18  5  4  3 12]
 [17 16 15 14 13]]

print(from_spiral(to_spiral(B)))
[[ 1  2  3  4  5]
 [ 6  7  8  9 10]
 [11 12 13 14 15]
 [16 17 18 19 20]
 [21 22 23 24 25]]

备注

如果您只打算使用固定大小的矩阵,例如 5x5,那么值得将函数定义中的 base_spiral(*A.shape) 替换为固定的索引矩阵,例如 Ind(其中 Ind = base_spiral(5,5))。

【讨论】:

    【解决方案2】:

    您可以通过从矩阵中心附近开始并始终向右转来构建螺旋,除非该元素已经被访问过:

    #!/usr/bin/env python
    NORTH, S, W, E = (0, -1), (0, 1), (-1, 0), (1, 0) # directions
    turn_right = {NORTH: E, E: S, S: W, W: NORTH} # old -> new direction
    
    def spiral(width, height):
        if width < 1 or height < 1:
            raise ValueError
        x, y = width // 2, height // 2 # start near the center
        dx, dy = NORTH # initial direction
        matrix = [[None] * width for _ in range(height)]
        count = 0
        while True:
            count += 1
            matrix[y][x] = count # visit
            # try to turn right
            new_dx, new_dy = turn_right[dx,dy]
            new_x, new_y = x + new_dx, y + new_dy
            if (0 <= new_x < width and 0 <= new_y < height and
                matrix[new_y][new_x] is None): # can turn right
                x, y = new_x, new_y
                dx, dy = new_dx, new_dy
            else: # try to move straight
                x, y = x + dx, y + dy
                if not (0 <= x < width and 0 <= y < height):
                    return matrix # nowhere to go
    
    def print_matrix(matrix):
        width = len(str(max(el for row in matrix for el in row if el is not None)))
        fmt = "{:0%dd}" % width
        for row in matrix:
            print(" ".join("_"*width if el is None else fmt.format(el) for el in row))
    

    例子:

    >>> print_matrix(spiral(5, 5))
    21 22 23 24 25
    20 07 08 09 10
    19 06 01 02 11
    18 05 04 03 12
    17 16 15 14 13
    

    【讨论】:

      【解决方案3】:

      这是一个使用itertools 的解决方案,几乎不需要数学,只是观察螺旋的样子。我认为它很优雅而且很容易理解。

      from math import ceil, sqrt
      from itertools import cycle, count, izip
      
      def spiral_distances():
          """
          Yields 1, 1, 2, 2, 3, 3, ...
          """
          for distance in count(1):
              for _ in (0, 1):
                  yield distance
      
      def clockwise_directions():
          """
          Yields right, down, left, up, right, down, left, up, right, ...
          """
          left = (-1, 0)
          right = (1, 0)
          up = (0, -1)
          down = (0, 1)
          return cycle((right, down, left, up))
      
      def spiral_movements():
          """
          Yields each individual movement to make a spiral:
          right, down, left, left, up, up, right, right, right, down, down, down, ...
          """
          for distance, direction in izip(spiral_distances(), clockwise_directions()):
              for _ in range(distance):
                  yield direction
      
      def square(width):
          """
          Returns a width x width 2D list filled with Nones
          """
          return [[None] * width for _ in range(width)]
      
      def spiral(inp):
          width = int(ceil(sqrt(len(inp))))
          result = square(width)
          x = width // 2
          y = width // 2
          for value, movement in izip(inp, spiral_movements()):
              result[y][x] = value
              dx, dy = movement
              x += dx
              y += dy
          return result
      

      用法:

      from pprint import pprint
      pprint(spiral(range(1, 26)))
      

      输出:

      [[21, 22, 23, 24, 25],
       [20, 7, 8, 9, 10],
       [19, 6, 1, 2, 11],
       [18, 5, 4, 3, 12],
       [17, 16, 15, 14, 13]]
      

      以下是缩短的相同解决方案:

      def stretch(items, counts):
          for item, count in izip(items, counts):
              for _ in range(count):
                  yield item
      
      def spiral(inp):
          width = int(ceil(sqrt(len(inp))))
          result = [[None] * width for _ in range(width)]
          x = width // 2
          y = width // 2
          for value, (dx, dy) in izip(inp,
                                      stretch(cycle([(1, 0), (0, 1), (-1, 0), (0, -1)]),
                                              stretch(count(1),
                                                      repeat(2)))):
              result[y][x] = value
              x += dx
              y += dy
          return result
      

      我忽略了您希望输入为 2D 数组这一事实,因为任何 1D 可迭代更有意义。如果需要,您可以轻松地展平输入二维数组。我还假设输出应该是一个正方形,因为我想不出你想要什么。如果正方形的长度是偶数并且输入太长,它可能会越过边缘并引发错误:同样,我不知道替代方案是什么。

      【讨论】:

        【解决方案4】:

        下面是转换的python3代码:

            [[0, 1, 2, 3, 4], 
            [5, 6, 7, 8, 9], 
            [10, 11, 12, 13, 14], 
            [15, 16, 17, 18, 19], 
            [20, 21, 22, 23, 24]]
        

            [[20, 19, 18, 17, 16], 
            [21, 6, 5, 4, 15], 
            [22, 7, 0, 3, 14], 
            [23, 8, 1, 2, 13], 
            [24, 9, 10, 11, 12]]
        

        您可以轻松地以您想要的方式更改实现...

            def spiral(X, Y):
                x = y = 0
                dx = 0
                dy = -1
                for i in range(max(X, Y) ** 2):
                    if (-X / 2 < x <= X / 2) and (-Y / 2 < y <= Y / 2):
                        yield x, y
                        # print(x, y)
                        # DO STUFF...
                    if x == y or (x < 0 and x == -y) or (x > 0 and x == 1 - y):
                        dx, dy = -dy, dx
                    x, y = x + dx, y + dy
        
            spiral_matrix_size = 5
            my_list = list(range(spiral_matrix_size**2))
            my_list = [my_list[x:x + spiral_matrix_size] for x in range(0, len(my_list), spiral_matrix_size)]
        
            print(my_list)
        
            for i, (x, y) in enumerate(spiral(spiral_matrix_size, spiral_matrix_size)):
                diff = int(spiral_matrix_size / 2)
                my_list[x + diff][y + diff] = i
        
            print(my_list)
        

        【讨论】:

          【解决方案5】:

          你可以用这样的东西填充一个数组:

          #!/usr/bin/python
          
          class filler:
              def __init__(self, srcarray):
                  self.size = len(srcarray)
                  self.array = [[None for y in range(self.size)] for y in range(self.size)]
                  self.xpos, self.ypos = 0, 0
                  self.directions = [self.down, self.right, self.up, self.left]
                  self.direction = 0
                  self.fill(srcarray)
          
              def fill(self, srcarray):
                  for row in reversed(srcarray):
                      for elem in reversed(row):
                          self.array[self.xpos][self.ypos] = elem
                          self.go_to_next()
          
              def check_next_pos(self):
                  np = self.get_next_pos()
                  if np[1] in range(self.size) and np[0] in range(self.size):
                      return self.array[np[0]][np[1]] == None
                  return False
          
              def go_to_next(self):
                  i = 0
                  while not self.check_next_pos() and i < 4:
                      self.direction = (self.direction + 1) % 4
                      i += 4
                  self.xpos, self.ypos = self.get_next_pos()
          
              def get_next_pos(self):
                  return self.directions[self.direction](self.xpos, self.ypos)
          
              def down(self, x, y):
                  return x + 1, y
          
              def right(self, x, y):
                  return x, y + 1
          
              def up(self, x, y):
                  return x - 1, y
          
              def left(self, x, y):
                  return x, y - 1
          
              def print_grid(self):
                  for row in self.array:
                      print(row)
          
          
          f = filler([[x+y*5 for x in range(5)] for y in range(5)])
          f.print_grid()
          

          这个输出将是:

          [24, 9, 10, 11, 12]
          [23, 8, 1, 2, 13]
          [22, 7, 0, 3, 14]
          [21, 6, 5, 4, 15]
          [20, 19, 18, 17, 16]
          

          【讨论】:

          • 排序数组的好方法,如果我想在开始时直接向右移动,请你帮忙看看我应该在哪里看。而不是向上然后向右
          • @Smple_V 只需更改self.direction(您想要的self.direction 元素的索引)和self.xposself.ypos 值。
          • 是的,我试过这样做,但我在单元格中没有得到任何内容
          • 仍然无法解决直接向右移动而不是向上然后向右移动的部分
          • @Smple_V 看看 if 如何填充 [[int]]。
          【解决方案6】:
          def counter(n):
            for i in range(1,n*n):
              yield i+1
          
          n = 11
          a = [[1 for x in range(n)] for y in range(n)]
          x = y = n//2
          val = counter(n)
          
          for i in range(2, n, 2):
            y += 1
            x -= 1
            for k in range(i):
               x += 1
               a[x][y] = next(val)
            for k in range(i):
               y -= 1
               a[x][y] = next(val)
            for k in range(i):
               x -= 1
               a[x][y] = next(val)
            for k in range(i):
               y += 1
               a[x][y] = next(val)
          
          for i in range(n):
            for j in range(n):
              print (a[i][j] , end="")
              print ("  " , end="")
            print("\n")
          

          【讨论】:

            【解决方案7】:

            我只是在做一些关于生成数组的各种螺旋索引的事情,我对answer of ptrj 添加了一些简单的修改以使函数更通用。修改后的函数支持从四个角开始分度,顺时针和逆时针方向。

            def spiral_ind(A,start,direction):
                if direction == 'cw':
                    if start == 'right top':
                        A = np.rot90(A)
                    elif start == 'left bottom':
                        A = np.rot90(A,k=3)
                    elif start == 'right bottom':
                        A = np.rot90(A,k=2)
                elif direction == 'ccw':
                    if start == 'left top':
                        A = np.rot90(A,k=3)
                    elif start == 'left bottom':
                        A = np.rot90(A,k=2)
                    elif start == 'right bottom':
                        A = np.rot90(A)
                out = []
                while(A.size):
                    if direction == 'cw':
                        out.append(A[0])
                        A = A[1:].T[::-1]
                    elif direction == 'ccw':
                        out.append(A[0][::-1])
                        A = A[1:][::-1].T
                return np.concatenate(out)    
            

            【讨论】:

              【解决方案8】:
              def spiral(m):
               a=[]
               t=list(zip(*m)) # you get the columns by zip function
              
               while m!=[]:
                if m==[]:
                  break
                m=list(zip(*t)) # zip t will give you same m matrix. It is necessary for iteration
                a.extend(m.pop(0)) # Step1 : pop first row
                if m==[]:
                  break
                t=list(zip(*m))
                a.extend(t.pop(-1)) # Step 2: pop last column
                if m==[]:
                  break
                m=list(zip(*t))
                a.extend(m.pop(-1)[::-1]) # Step 3: pop last row in reverse order
                if m==[]:
                  break
                t=list(zip(*m)) 
                a.extend(t.pop(0)[::-1]) # Step 4: pop first column in reverse order
               return a
              

              这个解是 O(n);只有一个while循环;速度更快,可用于更大维度的矩阵

              【讨论】:

                猜你喜欢
                • 2012-01-12
                • 1970-01-01
                • 2023-03-28
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多