creating a spiral array in python?

前端 未结 8 616
感情败类
感情败类 2020-12-05 03:16

Me and my mate were trying to create a fun game in python where the elements entered in the array are accessed in a spiral manner. I have tried few methods like one given be

相关标签:
8条回答
  • 2020-12-05 03:50
    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
    

    This solution is O(n); just one while loop; much faster and can be used for much bigger dimensions of matrices

    0 讨论(0)
  • 2020-12-05 03:52

    I'm just doing something about generating various spiral indexing of a array and I add some simple modifications to the answer of ptrj to make the function more general. The modified function supports beginning the indexing from the four corners with clockwise and counter-clockwise directions.

    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 讨论(0)
  • 2020-12-05 03:55

    Below is python3 code which transforms:

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

    You can easily change implementation in such way how do you want...

        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)
    
    0 讨论(0)
  • 2020-12-05 03:57

    Here's a solution using itertools and virtually no maths, just observations about what the spiral looks like. I think it's elegant and pretty easy to understand.

    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
    

    Usage:

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

    Output:

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

    Here's the same solution shortened:

    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
    

    I've ignored the fact that you want the input to be a 2D array since it makes much more sense for it to be any 1D iterable. You can easily flatten the input 2D array if you want. I've also assumed the output should be a square since I can't think what you'd sensibly want otherwise. It may go over the edge and raise an error if the square has even length and the input is too long: again, I don't know what the alternative would be.

    0 讨论(0)
  • 2020-12-05 04:08

    You can build a spiral by starting near the center of the matrix and always turning right unless the element has been visited already:

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

    Example:

    >>> 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
    
    0 讨论(0)
  • 2020-12-05 04:09

    Introductory remarks

    The question is closely related to a problem of printing an array in spiral order. In fact, if we already have a function which does it, then the problem in question is relatively simple.

    There is a multitude of resources on how to produce a spiral matrix or how to loop or print an array in spiral order. Even so, I decided to write my own version, using numpy arrays. The idea is not original but use of numpy makes the code more concise.

    The other reason is that most of examples of producing a spiral matrix I found (including the code in the question and in the other answers) deal only with square matrices of size n x n for odd n. Finding the start (or end) point in matrices of other sizes may be tricky. For example, for a 3x5 matrix it can't be the middle cell. The code below is general and the position of the starting (ending) point depends on the choice of the function spiral_xxx.

    Code

    The first function unwraps an array in spiral order clockwise:

    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)
    

    We can write this function on eight different ways depending on where we start and how we rotate the matrix. I'll give another one, which is consistent (it will be evident later) with the matrix transformation in the image in the question. So, further on, I will be using this version:

    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)
    

    How it works:

    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]
    

    Note that the end (or start) point is not the middle cell. This function works for all type of matrices but we will need a helper function that generates spiral indices:

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

    For example:

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

    Now come the two main functions. One transforms a matrix to a spiral form of the same dimensions, the other reverts the transformation:

    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)
    

    Examples

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

    Matrix from the question:

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

    Remark

    If you are going to work only with fixed size matrices, for example 5x5, then it's worth replacing base_spiral(*A.shape) in definitions of the functions with a fixed matrix of indices, say Ind (where Ind = base_spiral(5,5)).

    0 讨论(0)
提交回复
热议问题