컴퓨터 공부/🐍 Python

Rotating 2D matrix - 90, 180, 270

letzgorats 2024. 11. 23. 23:57

Python으로 2D 행렬을 90도, 180도, 270도 회전시키는 방법

 

알고리즘 문제를 풀 때, 행렬을 회전시켜야 하는 경우를 종종 마주한다. 특히 이미지 처리, 게임 개발, 데이터 분석 등에서 2D 행렬을 회전하는 작업은 매우 유용한데, 이번 포스팅에서는 Python으로 2차원 행렬을 90도, 180도, 270도(왼쪽으로 90도) 회전시키는 방법을 정리해보려고 한다. 


1. 기본 개념 : 행렬의 회전

2D 행렬이란 숫자 또는 다른 데이터로 이루어진 2차원 배열이다. 예를 들어, 다음과 같은 3x4 행렬이 있다고 가정해보자.

 

- 초기 행렬(n=3,m=4)

matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

 

- 90도 시계방향(clockwise) 회전 결과(n=3,m=4 → n=4,m=3)

matrix = [
    [9, 5, 1],
    [10, 6, 2],
    [11, 7, 3],
    [12, 8, 4]
]

 

- 180도 회전 결과(n=3,m=4 → 그대로)

matrix = [
    [12, 11, 10, 9],
    [8, 7, 6, 5],
    [4, 3, 2, 1]
]

 

- 270도 시계방향(clockwise) 회전 결과 (= 90도 반시계방향(counterclockwise) 회전 결과)(n=3,m=4 → n=4,m=3)

matrix = [
    [4, 8, 12],
    [3, 7, 11],
    [2, 6, 10],
    [1, 5, 9]
]

2. 행렬 회전 방법

방법 1) 전치행렬(Transpose) + 행렬 뒤집기

: 90도 회전은 전치행렬을 구한 뒤, 각 을 뒤집는 작업으로 구현할 수 있다.

 

1. 전치 행렬(Transpose) : 행과 열을 뒤바꾼 행렬을 말한다. (= 대각선을 기준으로 각 요소들을 뒤바꾼 행렬)

2. 행 뒤집기 : 전치된 행렬의 각 행을 역순으로 재배열한다.

def roatate_90_clockwise(matrix):

    # 1. 전치 행렬을 만든다.(Transpose the matrix)
    transposed = list(zip(*matrix))
    
    # 2. 각 행을 뒤집어 90도 회전 결과를 만든다.(Reverse each row)
    return [list(row[::-1] for row in transposed]

matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

print(rotate_90_clockwise(matrix))

'''
출력결과
[
    [9, 5, 1],
    [10, 6, 2],
    [11, 7, 3],
    [12, 8, 4]
]
'''

 

똑같은 방법이지만, 구현을 아래와 같이 할 수도 있다.

def roatate_90_clockwise(matrix):
    n = len(matrix)
    m = len(matrix[0])
    
    # 1. 전치 행렬을 만든다.(Transpose the matrix)
    transposed_matrix = [[matrix[j][i] for j in range(n)] for i in range(m)]
    
    # 2. 각 행을 뒤집어 90도 회전 결과를 만든다.(Reverse each row)
    rotated_matrix = [[row[i] for i in range(m - 1, -1, -1)] for row in transposed_matrix]
    
    return rotated_matrix

matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

print(rotate_90_clockwise(matrix))

'''
출력결과
[
    [9, 5, 1],
    [10, 6, 2],
    [11, 7, 3],
    [12, 8, 4]
]
'''

방법 2) 인덱스를 직접 조작

: 회전을 수학적으로 이해하려면 행렬의 새로운 인덱스 규칙을 찾는 방법을 고려할 수 있다.

# n은 기존 행렬의 행 길이
# m은 기존 행렬의 열 길이
1. 90도 회전

: (i,j) → (j,n-1-i)


2. 180도 회전

: (i,j) → (n-1-i,m-1-j)


3. 270도 회전

: (i,j) → (m-1-j,i)

 

※ 행렬 회전의 규칙

 

2차원 행렬 회전의 핵심은, 기존 행렬의 (i,j) 위치를 새로운 행렬에서 어떻게 변환할 것인지를 이해하는 것이다.

행렬의 길이(n=행의 길이)와 폭(m=열의 길이)가 어떻게 변하는지가 회전마다 다르다. 이를 기반으로 규칙을 정리해보자.

 

1. 90도 회전(시계방향)

 

새로운 행 : 기존 열(j)이 그대로 새로운 행이 된다.

새로운 열 : 기존 행(i)은 뒤집어서 새로운 열이 된다.

인덱스 변환 : (i, j) → (j,n-1-i)

 

기존행렬에서 행과 열이 바뀌고, 행(i)의 순서는 역순이 된다.

행렬의 크기도 (n,m) → (m,n) 으로 바뀐다.

def rotate_90_clockwise(matrix):
    n, m = len(matrix), len(matrix[0])
    return [[matrix[n-1-j][i] for j in range(n)] for i in range(m)]

 

빠른 팁

: 행,열을 (n,m)→(m,n) 으로 바꿔준다. (바깥쪽 m, 안쪽 n)

: 이 때, m 에 해당하는 인덱스가 i, n 에 해당하는 인덱스가 j 이다. (바깥쪽 i, 안쪽 j)

: 새로운 행렬의 행의 위치열을 조작한 값이 들어가야 하므로, n-1-j 를 써준다. (matrix[n-1-j])

: 새로운 행렬의 열의 위치에는 행이 그대로 들어가야 하므로 i 를 써준다. (matrix[n-1-j][i])


2. 180도 회전

 

새로운 행 : 기존 행(i)은 역순으로 뒤집혀 새로운 행이 된다.

새로운 열 : 기존 열(j)도 역순으로 뒤집혀 새로운 열이 된다.

인덱스 변환 : (i, j) → (n-1-i,m-1-j)

 

기존행렬에서 행과 열의 순서 모두 뒤집혀야 한다.

행렬의 크기는 (n,m) → (n,m) 으로 그대로 유지된다.

def rotate_180(matrix):
    n, m = len(matrix), len(matrix[0])
    return [[matrix[n-1-i][m-1-j] for j in range(m)] for i in range(n)]

 

빠른 팁

: 행,열을 (n,m) 으로 그대로 써준다. (바깥쪽 n, 안쪽 m)

: 이 때, n 에 해당하는 인덱스가 i, m 에 해당하는 인덱스가 j 이다. (바깥쪽 i, 안쪽 j)

: 새로운 행렬의 행의 위치행을 조작한 값이 들어가야 하므로, n-1-i 를 써준다. (matrix[n-1-i])

: 새로운 행렬의 열의 위치에는 열을 조작한 값이 들어가야 하므로 m-1-j 를 써준다. (matrix[n-1-i][m-1-i])


3. 270도 회전(시계방향) = 90도 회전(반시계 방향)

 

새로운 행 : 기존 열(j)은 역순으로 뒤집혀 새로운 행이 된다.

새로운 열 : 기존 행(i)이 그대로 새로운 열이 된다.

인덱스 변환 : (i, j) → (m-1-j,i)

 

기존행렬에서 행과 열이 서로 바뀌고, 열(j)의 순서는 역순이 된다.

행렬의 크기도 (n,m) → (m,n) 으로 바뀐다.

(즉, 90도 회전(시계방향)과 마찬가지로 행과 열의 크기가 바뀌지만, 인덱스 역순이 다르다.)

def rotate_270_clockwise(matrix):
    n, m = len(matrix), len(matrix[0])
    return [[matrix[j][m-1-i] for j in range(n)] for i in range(m)]

 

빠른 팁

: 행,열을 (n,m)→(m,n) 으로 바꿔준다. (바깥쪽 m, 안쪽 n)

: 이 때, m 에 해당하는 인덱스가 i, n 에 해당하는 인덱스가 j 이다. (바깥쪽 i, 안쪽 j) 

: 새로운 행렬의 행의 위치 열이 그대로 들어가야 하므로, j 를 써준다. (matrix[j])

: 새로운 행렬의 열의 위치에는 행을 조작한 값이 들어가야 하므로 m-1-i 를 써준다. (matrix[j][m-1-i])

 

 

→ (정리) 바깥쪽 인덱스가 i, 안쪽 인덱스가 j 인 것은 고정인데, 길이가 n인지, m인지를 상황에 따라 바꾸면 되고, 각 상황에 따라 인덱스 조절을 하면 된다.


matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12]
]

print("90도 회전:", rotate_90_clockwise(matrix))
print("270도 회전:", rotate_270_clockwise(matrix))
print("180도 회전:", rotate_180(matrix))

 

90도 회전:
[
    [9, 5, 1],
    [10, 6, 2],
    [11, 7, 3],
    [12, 8, 4]
]

270도 회전:
[
    [4, 8, 12],
    [3, 7, 11],
    [2, 6, 10],
    [1, 5, 9]
]

180도 회전:
[
    [12, 11, 10, 9],
    [8, 7, 6, 5],
    [4, 3, 2, 1]
]

방법 3) numpy로 간단히 해결

: 파이썬의 numpy 라이브러리를 사용하면 쉽게 행렬을 회전할 수 있다.(단, 알고리즘 문제를 풀 때는 numpy 사용 불가)

import numpy as np

def rotate_with_numpy(matrix, k):
    # np 형태로 변환
    np_matrix = np.array(matrix)
    # k=1: 90도, k=2: 180도, k=3: 270도
    return np.rot90(np_matrix, -k).tolist()

print(rotate_with_numpy(matrix, 1))  # 90도
print(rotate_with_numpy(matrix, 2))  # 180도
print(rotate_with_numpy(matrix, 3))  # 270도

위의 행렬 회전 방식들을 잘 이해하면, 직사각형뿐 아니라 정사각형 행렬에서도 모두 동일한 코드로 회전을 구현할 수 있다.

m = len(matrix[0]) 와 같은 방식으로 동적으로 열의 길이를 계산하기 때문에 행렬형태에 구애받지 않기 때문이다. 원리를 이해하자!

반응형