[Python] 3차원 배열 구현

Choi Jimin·2023년 10월 13일

Python

목록 보기
7/8

어제 복습 겸 'BOJ.7569 토마토' 문제를 풀다가 풀이는 맞는 것 같은데 계속 이상한 결과가 나와서 30분 넘게 허비하였다.
그 원인이 3차원 배열을 정의할 때 얕은 복사를 간과하였던 것인데, 또 3차원 배열을 이용하는 문제가 나왔을 때 실수하지 않도록 포스팅으로 정리해보고자 한다.


3차원 배열 구현 방법

🧐 Q. 높이(h), 세로(n), 가로(m)가 주어졌을 때, 모든 칸이 0인 3차원 배열을 만들어보자. 그리고 h = 2, n = 3, m = 5일 때 [1][1][2] 원소값을 1로 바꿔보자.

🍎 방법 1.

# 3차원 배열 구현
arr = []
for i in range(h):
	tmp1 = []
	for j in range(n):
		tmp2 = []
		for k in range(m):
			tmp2.append(0)
		tmp1.append(tmp2)
	arr.append(tmp1)

가장 근본적이며 직관적인 방법이다. 3중 for문을 이용하여 0을 하나씩 집어넣어 구현하였다.

🍎 방법 2.

# 3차원 배열 구현
arr = []
for i in range(h):
	tmp = []
	for j in range(n):
		tmp.append([0] * m)
	arr.append(tmp)

'방법 1'에 비해 조금 더 간단해진 방법이다. 2중 for문을 이용하여 [0] * m을 생성해 집어넣어 구현하였다.
반복문보다 곱하기 계산이 더 빠르기 때문에 이 구현 방법이 더 효율적이다.

🍎 방법 3.

# 3차원 배열 구현
arr = [[[0 for k in range(m)] for j in range(n)] for i in range(h)]

'방법 1'을 oneline 코딩한 방법이다.

🍎 방법 4.

# 3차원 배열 구현
arr = [[[0] * m for j in range(n)] for i in range(h)]

'방법 2'를 oneline 코딩한 방법이다.

🍎 방법 5.

# 3차원 배열 구현
arr = [[[0] * m] * n for _ in range(h)]
arr = [[[0] * m] * n] * h

반복문을 줄이고 곱하기 연산을 늘려 구현한 방법이다.

🍎 방법 6.

# 3차원 배열 구현
arr = [[[0] * m] * n] * h

반복문을 한 번 더 줄이고 곱하기 연산을 늘려 구현한 방법이다.


여기까지 총 5가지 3차원 배열 구현 방법을 알아보았다.
이제 h = 2, n = 3, m = 5일 때 [1][1][2] 원소값을 1로 바꿔보자. 이 연산은 모든 방법에서 동일하다.

# [1][1][2] 원소값 변경
arr[1][1][2] = 1

이렇게 하면 배열은 어떤 모양이 될까? 5가지 구현 방법 모두 동일하게 [1][1][2] 원소만 1이고 나머지는 0인 3차원 배열이 될까? 그렇지않다. 자세한 내용은 아래에서 확인해보자.


🍏 방법 5, 6의 문제점

'방법 5'를 이용해 구현한 후 arr[1][1][2] = 1을 실행하면 arr는 다음과 같은 모양이 된다.

[[[0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0]],
 [[0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0]]]

이유는 [[0] * m]라는 리스트를 얕은 복사했기 때문이다!
곱하기 연산([[0] * m] * n에서 * n)으로 인해 같은 높이(h)에 있는 2차원 배열의 모든 행은 같은 메모리 주소를 참조하게 된다.
따라서 arr[1][1][2] = 1를 하면 arr[1][0][2]도 1로, arr[1][2][2]도 1로 변한다.

'방법 6'도 마찬가지 이유로 arr[1][1][2] = 1을 실행하면 arr는 다음과 같은 모양이 된다.

[[[0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0]],
 [[0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0],
  [0, 0, 1, 0, 0]]]

[0] * m 자체는 원시타입인 숫자 0을 복사하는 것이기 때문에, 한 열의 원소만 변경한다면 해당 원소값만 바뀐다.



얕은 복사와 깊은 복사 개념을 알면 당연한 내용이지만 파이썬이 익숙하지 않으면 충분히 실수할 수 있는 부분이다.
나는 문제의 풀이 방식에만 신경쓰느라 별 생각 없이 위의 '방법 5'처럼 구현해놓고 답이 제대로 나오지 않아 엄청 당황했었다.

위에서도 말했듯이 다시는 이런 실수를 하지 않기 위해 이렇게 포스팅으로 정리를 해보았다.

0개의 댓글