[python] zip() 으로 배열 회전

shelly·2020년 9월 8일
0

노가다 회전

def rotate(key, N):

    def getNewValue(i, j, x, y):
        key[j , N-i-1] = key[i ,j]
        if (i == x and j == y): return
        getNewValue(N-j-1, i, x, y)

    for i in range(0, int(N/2)):
        for j in range(i, N-i-1):
            print(i,j )
            tmp = key[i,j]
            getNewValue(N-1-j, i, i, j)
            key[j, N-1-i] = tmp

코딩테스트 문제를 풀다가 배열을 회전해야하는 문제가 있었다. 그래서 이 악물고 직접 코딩했다. 짧지만 알아보기 어렵다. 이 코드를 작성할 때 다음과 같은 규칙을 발견하여 코딩했다.

오리지널 배열   회전 후 배열
(0,0)(0,1)(0,2)    (2,0)(1,0)(0,0)
(1,0)(1,1)(1,2)    (2,1)(1,1)(0,1)
(2,0)(2,1)(2,2)    (2,2)(1,2)(0,2)

규칙 1. 회전 후 행의 위치 = 회전하기 전 열의 위치
규칙 2. 회전 후 열의 위치 = 배열 길이 - 회전 전 행 위치 - 1

위의 규칙을 사용하여 한 자리씩 수정해나갔다. 그런데, 아무리 생각해도 내가 아닌 다른 사람이 저 코드를 보면 이해하기 까다로울 것 같다. 그래서 조금 더 깔끔한 코드가 없을까 찾아보았다.

python에는 역시 없는게 없었다.

ZIP을 사용한 깔끔한 회전

def zip_rotate(original):
    rotated = np.array(list(zip(*original[::-1])))
    return rotated

단 한줄로 끝났다. 대단하다. python.

하나씩 살펴보자!

[ : : -1]

기본 형식은 A[a:b:c] 이다. 이는 A배열의 a위치부터 b위치 미만까지 c의 간격으로 선택한다는 것을 의미한다.

A = [0, 1, 2, 3, 4, 5]
print(A[0:4:2])

따라서 위의 코드를 실행하면 [0,2] 가 출력된다.

original = [[1,2],[2,3],[3,4]]
original = original[::-1]
print(original)

즉, 위의 코드는 original의 모든 범위를 -1간격으로 선택하라는 의미이다. 1을 한다면 0 1 2 3 순으로 가겠지만, -1을 한다면 -1 -2 -3 -4 순으로 가는 것이다.

결국 위의 코드 결과 [[3,4],[2,3],[1,2]] 가 출력된다.


zip()

zip은 파라미터로 입력받은 iterable elements 들을 순서대로 묶어주는 내장함수이다. 라고 말하지만, 직접 코드로 이해하는 것이 훨씬 빠르니 바로 예시 코드로 넘어가겠다.

iterable elements 란 반복 가능한 객체를 의미한다. 즉 배열과 같이 반복문을 사용하여 데이터를 순회하며 접근할 수 있는 객체이다.

동일한 크기의 배열

number_list = [1, 2, 3]
str_list = ['one', 'two', 'three']

# Two iterables are passed
result = zip(number_list, str_list)

# Converting itertor to set
result_set = set(result)
print(result_set)

number_list와 str_list는 동일한 개수이다. 이 두 배열을 zip 함수의 파라미터로 넘겨주면 아래와 같은 결과값이 나온다.

[(1, 'one'), (2, 'two'), (3, 'three')]

즉, number_list[i]str_list[i] 를 묶어 튜플 형태로 반환해준 것이다.

zip에서 반환된 값을 그대로 출력하면 정상적으로 출력되지 않는다. 때문에 set 함수를 통해 iterable element 형태로 바꿔주는 것이다.

서로 다른 크기의 배열

numbersList = [1, 2, 3]
str_list = ['one', 'two']
numbers_tuple = ('ONE', 'TWO', 'THREE', 'FOUR')

# Notice, the size of numbersList and numbers_tuple is different
result = zip(numbersList, numbers_tuple)

# Converting to set
result_set = set(result)
print(result_set)

result = zip(numbersList, str_list, numbers_tuple)

# Converting to set
result_set = set(result)
print(result_set)

위 코드의 결과값은 다음과 같다.
{(1, 'ONE'), (2, 'TWO'), (3, 'THREE')}
{(1, 'one', 'ONE'), (2, 'two', 'TWO')}

서로 다른 크기의 배열을 파라미터로 넣었을 때는 최소 크기에 맞춰서 묶음 후 반환한다.

주의!
zip이 반환해주는 튜플에는 순서가 없다. 때문에 같은 코드일지라도 실행할 때 마다 튜플의 순서가 뒤바뀌어 있다. 만약, 일정한 값을 원한다면, set 함수 대신 list를 사용하면 된다.

result_set = list(result)


첫 번째 줄 출력은 set으로 변환하여서 순서가 계속 변한다.
반면, 두 번째 출력은 list로 변환하여 순서가 변하지 않는다.

zip()을 사용한 Unzip

coordinate = ['x', 'y', 'z']
value = [3, 4, 5]

result = zip(coordinate, value)
result_list = list(result)
print(result_list)

c, v =  zip(*result_list)
print('c =', c)
print('v =', v)

위 코드의 결과값은 다음과 같다.
[('x', 3), ('y', 4), ('z', 5)]
c = ('x', 'y', 'z')
v = (3, 4, 5)

즉, 파라미터 앞에 * 를 붙이면 다시 여러 개의 iterable elements로 나누어지는 것이다. 만약 unzip을 계속한다면?

coordinate = ['x', 'y', 'z']
value = [3, 4, 5]

result = zip(coordinate, value)
result_list = list(result)


for i in range(0,4):
	result_list=  list(zip(*result_list))
	print(result_list)

위 코드의 결과는 아래와 같다.

[('x', 'y', 'z'), (3, 4, 5)]
[('x', 3), ('y', 4), ('z', 5)]
[('x', 'y', 'z'), (3, 4, 5)]
[('x', 3), ('y', 4), ('z', 5)]

[ : : -1] 과 unzip을 함께 사용한다면

rotated = np.array(list(zip(*original[::-1])))

이 한줄이면 배열 회전이 된다. 설명을 위해 조금 나눠서 코드를 작성해보자.

original = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
for i in range(1, 5):
    print("iterate : ", i)

    tailToHead=original[::-1]
    print(tailToHead)
    original = list(zip(*tailToHead))
    print("rotate")
    print(original)

위 코드의 결과값은 다음과 같다.

iterate :  1
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[::-1]
[[7, 8, 9], [4, 5, 6], [1, 2, 3]]
rotate
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]

iterate :  2
[(7, 4, 1), (8, 5, 2), (9, 6, 3)]
[::-1]
[(9, 6, 3), (8, 5, 2), (7, 4, 1)]
rotate
[(9, 8, 7), (6, 5, 4), (3, 2, 1)]

iterate :  3
[(9, 8, 7), (6, 5, 4), (3, 2, 1)]
[::-1]
[(3, 2, 1), (6, 5, 4), (9, 8, 7)]
rotate
[(3, 6, 9), (2, 5, 8), (1, 4, 7)]

iterate :  4
[(3, 6, 9), (2, 5, 8), (1, 4, 7)]
[::-1]
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
rotate
[(1, 2, 3), (4, 5, 6), (7, 8, 9)]

이건 진짜 본인이 스스로 이해해야 한다. 위에서 언급한 코드의 개념과 위의 과정을 잘 살펴서 이해하자 ! :-)!!!


+) 위의 표현은 회전이 되었음을 잘 확인하기 어려울 수 있으니 배열의 형태로 나타내었다.

iterate :  1
[[1 2 3]
 [4 5 6]
 [7 8 9]]
 
[::-1]
[[7 8 9]
 [4 5 6]
 [1 2 3]]
 
rotate
[[7 4 1]
 [8 5 2]
 [9 6 3]]
 
iterate :  2
[[7 4 1]
 [8 5 2]
 [9 6 3]]
 
[::-1]
[[9 6 3]
 [8 5 2]
 [7 4 1]]
 
rotate
[[9 8 7]
 [6 5 4]
 [3 2 1]]
 
iterate :  3
[[9 8 7]
 [6 5 4]
 [3 2 1]]
 
[::-1]
[[3 2 1]
 [6 5 4]
 [9 8 7]]
 
rotate
[[3 6 9]
 [2 5 8]
 [1 4 7]]
 
iterate :  4
[[3 6 9]
 [2 5 8]
 [1 4 7]]
 
[::-1]
[[1 4 7]
 [2 5 8]
 [3 6 9]]
 
rotate
[[1 2 3]
 [4 5 6]
 [7 8 9]]

0개의 댓글