복붙노트

[PYTHON] 파이썬에서 나선형 배열을 만드는가?

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를 어떻게 나선형으로 변환 할 수 있는지 알고 싶습니다.

해결법

  1. ==============================

    1.매트릭스의 중심 근처에서 시작하여 요소가 이미 방문되지 않은 이상 항상 우회전하여 나선형을 만들 수 있습니다.

    매트릭스의 중심 근처에서 시작하여 요소가 이미 방문되지 않은 이상 항상 우회전하여 나선형을 만들 수 있습니다.

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

    2.이 질문은 배열을 나선형 순서로 인쇄하는 문제와 밀접한 관련이 있습니다. 사실, 우리가 이미 그것을하는 함수를 가지고 있다면 문제의 문제는 상대적으로 간단합니다.

    이 질문은 배열을 나선형 순서로 인쇄하는 문제와 밀접한 관련이 있습니다. 사실, 우리가 이미 그것을하는 함수를 가지고 있다면 문제의 문제는 상대적으로 간단합니다.

    나선형 행렬을 생성하는 방법이나 나선형 순서로 배열을 루프하거나 인쇄하는 방법에 대한 많은 자료가 있습니다. 그렇다고하더라도, 나는 numpy 배열을 사용하여 내 자신의 버전을 작성하기로 결정했습니다. 아이디어는 원래는 아니지만 numpy를 사용하면 코드가 간결 해집니다.

    다른 이유는 내가 찾은 나선형 행렬을 생성하는 대부분의 예제는 (문제의 코드와 다른 해답을 포함하여) odd 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)
    

    시작 위치와 행렬 회전 방법에 따라이 함수를 8 가지 방법으로 작성할 수 있습니다. 나는 또 다른 것을 주겠다. 문제의 이미지에서 매트릭스 변환을 통해 일관성이있다. 그래서, 더 나아가, 나는이 버전을 사용할 것이다.

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

  3. ==============================

    3.아래는 변환하는 python3 코드입니다.

    아래는 변환하는 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)
    
  4. ==============================

    4.다음은 itertools와 사실상 아무런 수학도 사용하지 않고 나선형이 어떻게 보이는지에 대한 관찰입니다. 우아하고 이해하기 쉽다고 생각합니다.

    다음은 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
    

    필자는 입력이 2 차원 배열이되기를 원한다는 사실을 무시했습니다. 왜냐하면 입력이 1D 반복 가능하다는 것이 훨씬 더 의미가 있기 때문입니다. 원하는 경우 입력 2D 배열을 쉽게 플랫화할 수 있습니다. 나는 또한 당신이 다른 것을 바라는 것을 생각할 수 없기 때문에 결과물이 사각형이어야한다고 생각했습니다. 사각형이 길이가 길고 입력이 너무 길면 가장자리가 넘어져 오류가 발생할 수 있습니다. 다시 말해서 나는 대안이 무엇인지 알지 못합니다.

  5. ==============================

    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]
    
  6. ==============================

    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. from https://stackoverflow.com/questions/36834505/creating-a-spiral-array-in-python by cc-by-sa and MIT license