在Python中创建螺旋数组?

26

我和我的朋友正在尝试用Python创建一个有趣的游戏,其中输入的元素以螺旋的方式访问数组。我已经尝试了一些方法,例如下面提供的方法(来源)。

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转换为螺旋数组。

Array AE


6
为什么这个问题会有负评分? - rambalachandran
你对螺旋数组的维度有任何限制吗?也就是说,您想要适用于任何“k x n”数组吗?或者只适用于k=n;或k,n-奇数,或k=n=5?如果k或/和n是偶数,您选择什么作为起点? - ptrj
9个回答

21

引言

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

有许多资源介绍 如何生成螺旋矩阵 或者如何 循环 或者 按螺旋顺序打印 数组。即使如此,我决定编写自己的版本,使用numpy数组。这个想法并不是原创的,但使用numpy可以使代码更加简洁。

另一个原因是,我发现大多数生成螺旋矩阵的示例(包括问题和其他答案中的代码)仅处理奇数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))。


19

你可以通过从矩阵中心开始并总是向右转,除非该元素已被访问,来构建一个螺旋:

#!/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

2
以下是将内容转换为Python3代码的示例:
```python ```
    [[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]]

to

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

2
这是使用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可迭代对象。如果需要,您可以轻松地将输入的2D数组展平。我还假设输出应该是一个正方形,因为我无法想象其他合理的选择。如果正方形的长度为偶数且输入过长,则可能超出边缘并引发错误:同样,我不知道其他选择。


0
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")

0
你可以像这样用数组填充一些东西:
#!/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
@Smple_V 只需更改 self.direction(您想要的self.direction元素的索引)以及self.xposself.ypos值即可。 - vmonteco
是的,我尝试过这样做,但是单元格中返回了None。 - Smple_V
仍然无法解决直接向右移动而不是先向上再向右的部分。 - Smple_V
@Smple_V 看看它如何填充[[int]]。 - vmonteco
显示剩余3条评论

0

我有一个相关的问题:我有两个数字高程模型,可能没有完全对齐。为了检查它们错位了多少个单元格,我想要一个(x,y)偏移元组列表,从最小的偏移开始。我通过编写一个螺旋行走的代码来解决这个问题,可以创建任意大小的正方形螺旋线。它可以顺时针或逆时针行走。下面是有注释的代码。与其他一些解决方案类似的想法,但是注释更多,重复的代码更少。

    #Generates a list of offsets to check starting with the smallest offsets
    #first.  Seed the list with (0,0)
    to_check = [(0,0)]
    #Current index of the "walker"
    cur_ind = np.array([0,0])
    #Direction to start move along the sides
    move_step = 1
    #Controls the direction of the spiral
    #any odd integer = counter clockwise
    #any even integer = clockwise
    ctr = 0
    #The size of each side of the spiral to be created
    size = 5
    
    #Iterate the through the number of steps to take along each side
    for i in range(1,size+1):
        #Toggle the direction of movement along the sides
        move_step *= -1
        #Step along each of the two sides that has the same number of 
        #elements
        for _ in range(2):
            #Increment the counter (changes whether the x or y index in 
            #cur_ind is incremented)
            ctr += 1
            for ii in range(i):
                #Move the "walker" in the direction indicated by move_step
                #along the side indicated 
                cur_ind[ctr%2] += move_step
                #Add the current location of the water to the list of index
                #tuples
                to_check.append((cur_ind[0],cur_ind[1]))
    #Truncate the list to just the indices to create the spiral size 
    #requested
    to_check = to_check[:size**2]
        
    #Check that the spiral is working
    #Create an empty array
    arr = np.zeros([size,size])*np.nan
    ctr = 1               
    #for each x,y offset pair:
    for dx,dy in to_check:
        #Starting at the approximate center of the array, place the ctr
        #at the index indicated by the offset
        arr[int(size/2)+dx,int(size/2)+dy]=ctr
        ctr+=1
    
    print(arr)

最后几行只是显示螺旋图案:
[[13. 14. 15. 16. 17.]
 [12.  3.  4.  5. 18.]
 [11.  2.  1.  6. 19.]
 [10.  9.  8.  7. 20.]
 [25. 24. 23. 22. 21.]]

0

我正在做一些关于生成数组各种螺旋索引的事情,并对 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)    

0
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循环;速度更快,可以用于更大的矩阵维度


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接