기본 콘텐츠로 건너뛰기

방송대 파이썬프로그래밍기초 15강: 2차원 리스트의 행·열 구조와 중첩 순회

0-썸네일-요약노트-파이썬프로그래밍기초-15강

방송대 파이썬프로그래밍기초 15강: 2차원 리스트의 행·열 구조와 중첩 순회

도시 사이 거리나 게임판처럼 값이 행과 열에 놓이면 인덱스 하나만으로는 위치를 표현하기 어렵습니다. 2차원 리스트를 단순히 ‘대괄호가 두 겹인 자료’로 외우지 않고, 바깥 리스트가 행을 보관하고 각 행이 열의 값을 보관하는 포함 관계로 해석해 봅니다. 이 구조를 알면 data[행][열]의 접근 순서, 동적 생성과 중첩 반복문의 실행을 스스로 추적할 수 있습니다.

표의 한 칸은 행과 열이라는 두 좌표가 필요하다

일주일의 날짜만 순서대로 저장하거나 학생 한 명의 점수를 나열한다면 1차원 리스트로 충분하다. 그러나 여러 도시를 출발지와 도착지로 교차한 거리표에서는 값 하나를 찾기 위해 ‘어느 출발 도시인가’와 ‘어느 도착 도시인가’를 함께 알아야 한다. 이처럼 두 방향의 위치가 필요한 데이터를 표현할 때 리스트 안에 리스트를 넣은 2차원 구조가 자연스럽다.

예를 들어 서울·대전·광주의 거리만 떼어 내면 각 행은 한 출발 도시에서 세 도착 도시까지의 거리를 나타낸다.

distance = [
    [0, 151, 290],
    [151, 0, 178],
    [290, 178, 0]
]

첫 행은 서울 출발, 둘째 행은 대전 출발, 셋째 행은 광주 출발이다. 각 행 안의 첫째·둘째·셋째 값은 차례로 서울·대전·광주 도착에 대응한다. 따라서 서울에서 광주까지의 값은 행 0, 열 2에 있는 distance[0][2], 즉 290이다.

좌표 번역 규칙: 문제의 세로 기준을 첫 번째 인덱스인 행으로, 가로 기준을 두 번째 인덱스인 열로 번역한다. “출발 도시가 행, 도착 도시가 열”이라는 자료의 의미를 먼저 확정해야 숫자 인덱스를 뒤바꾸지 않는다.

2차원 리스트는 바깥 리스트가 여러 행 리스트를 참조하는 구조다

파이썬 리스트는 숫자나 문자열뿐 아니라 다른 리스트도 원소로 저장할 수 있다. 2차원 리스트에서 바깥 리스트의 각 원소는 하나의 행 리스트다. 행 리스트의 각 원소가 실제 표의 셀 값이 된다.

표현선택되는 대상거리표에서의 의미
distance바깥 리스트 전체전체 거리표
distance[1]두 번째 행 리스트대전에서 출발하는 거리 묶음
distance[1][3]두 번째 행의 네 번째 값대전에서 대구까지의 거리
len(distance)바깥 리스트의 원소 수행의 개수
len(distance[1])선택한 행의 원소 수두 번째 행의 열 개수

distance[1][3]은 두 인덱스를 동시에 처리하는 특별한 한 번의 연산이 아니다. 먼저 distance[1]이 행 리스트 [151, 0, 178, 148, ...]를 반환하고, 이어서 그 결과에 [3]을 적용해 148을 선택한다. 왼쪽 대괄호부터 한 단계씩 읽으면 접근 원리가 선명해진다.

행 인덱스와 열 인덱스는 모두 0부터 시작한다

강의자료의 거리표에는 서울, 대전, 광주, 대구, 울산, 전주, 순천, 부산의 여덟 도시가 같은 순서로 행과 열에 배치된다. 첫 도시 서울은 인덱스 0, 마지막 도시 부산은 인덱스 7이다.

cities = ["서울", "대전", "광주", "대구", "울산", "전주", "순천", "부산"]

distance = [
    [0, 151, 290, 282, 396, 210, 320, 390],
    [151, 0, 178, 148, 259, 90, 235, 260],
    [290, 178, 0, 219, 327, 105, 93, 267],
    [282, 148, 219, 0, 87, 198, 218, 130],
    [396, 259, 327, 87, 0, 308, 199, 65],
    [210, 90, 105, 198, 308, 0, 141, 265],
    [320, 235, 93, 218, 199, 141, 0, 199],
    [390, 260, 267, 130, 65, 265, 199, 0]
]

departure = cities.index("대전")
destination = cities.index("대구")
print(distance[departure][destination])

cities.index("대전")은 1, cities.index("대구")는 3이므로 최종 접근은 distance[1][3]이 되고 148이 출력된다. 도시 이름과 숫자 인덱스를 별도 리스트로 연결하면 ‘1행 3열’이라는 위치가 실제 의미로 번역된다.

행·열 뒤집기 오류: distance[destination][departure]는 도착지를 행, 출발지를 열로 읽는 셈이다. 이 거리표는 값이 대칭이라 같은 수가 나올 수 있어 오류가 숨는다. 방향에 따라 값이 달라지는 운임·교통량 표에서는 결과가 달라지므로, 우연히 같은 숫자가 나왔다고 접근 순서가 맞다고 판단하면 안 된다.

2차원 모양은 행 수와 각 행의 열 수를 따로 확인한다

바깥 리스트의 길이는 행 수이고, 각 안쪽 리스트의 길이는 해당 행의 열 수다. 모든 행의 길이가 같으면 직사각형 표처럼 다룰 수 있다. 그러나 파이썬은 각 행의 길이가 다른 리스트도 허용한다.

rectangular = [
    [10, 20, 30],
    [40, 50, 60]
]

ragged = [
    [10, 20, 30],
    [40, 50]
]

print(len(rectangular))       # 2
print(len(rectangular[0]))    # 3
print([len(row) for row in ragged])  # [3, 2]

ragged도 문법적으로 올바른 중첩 리스트지만 2행 3열의 완전한 표는 아니다. 모든 행에 열 2가 있다고 가정해 ragged[1][2]를 읽으면 두 번째 행에는 세 번째 원소가 없어 오류가 발생한다.

모양 검사: 행 수는 len(data), 각 행의 열 수는 len(row)로 확인한다. 직사각형을 전제로 한 알고리즘이라면 모든 행의 길이가 같은지도 검사해야 한다.

리터럴 생성은 대괄호의 층과 행 경계를 읽는다

값을 미리 아는 경우에는 각 행을 리스트로 만들고 그 행들을 다시 바깥 리스트에 넣는다. 바깥쪽 대괄호 한 쌍은 전체 표, 안쪽 대괄호 한 쌍은 한 행을 나타낸다.

scores = [
    [82, 91, 76],
    [95, 88, 90]
]

이 코드는 학생 2명의 과목 점수 3개씩을 저장한 2행 3열 구조다. 쉼표로 구분된 바깥 원소는 두 개이므로 행은 2개다. 첫 행 [82, 91, 76]에는 원소가 세 개이므로 열은 3개다.

  1. 가장 바깥 대괄호 안에서 행 리스트가 몇 개인지 센다.
  2. 각 행의 대괄호 안에서 셀 값이 몇 개인지 센다.
  3. 행마다 열 수가 같은지 확인한다.
  4. 접근식의 첫 인덱스가 행 범위, 둘째 인덱스가 해당 행의 열 범위 안인지 확인한다.

이 네 단계를 사용하면 코드의 들여쓰기나 줄바꿈이 달라져도 구조를 읽을 수 있다. 줄바꿈은 가독성을 높이지만 리스트의 차원을 결정하는 것은 대괄호 안에 어떤 원소가 중첩되어 있는가이다.

동적 생성은 한 행을 완성한 뒤 바깥 리스트에 넣는다

행과 열의 개수가 실행 중 결정되거나 값을 반복해서 계산해야 한다면 중첩 반복문으로 2차원 리스트를 만든다. 강의자료의 난수 예제는 바깥 반복이 행을 만들고, 안쪽 반복이 그 행에 열 값을 채우는 구조다.

import random as rd

n_rows = 2
n_columns = 3
distance = []

for i in range(n_rows):
    row = []
    for j in range(n_columns):
        row.append(rd.randint(0, 99))
    distance.append(row)

print(distance)

난수는 실행할 때마다 달라질 수 있지만 구조는 언제나 2행 3열이다. 값 하나가 만들어질 때는 row에 추가되고, 안쪽 반복이 세 번 끝나야 완성된 행 하나가 distance에 들어간다.

시점row의 상태distance의 상태
첫 행 시작[][]
첫 행의 열 값 3개 생성 후[값, 값, 값][]
첫 행 추가 후첫 행 리스트[[값, 값, 값]]
둘째 행 시작새로운 []첫 행을 보관
전체 반복 종료둘째 행 리스트행 리스트 2개를 보관

distance.append(row)를 안쪽 반복문 안에 넣으면 열 값 하나를 추가할 때마다 미완성 행이 바깥 리스트에 들어간다. 행을 완성하는 반복과 행을 저장하는 시점의 들여쓰기를 구분해야 한다.

같은 행을 복제하면 한 칸 수정이 여러 행에 퍼질 수 있다

학습을 위해 0으로 채운 2행 3열 판을 만든다고 가정하자. 곱셈으로 안쪽 리스트를 반복하면 짧아 보이지만, 각 행이 독립된 리스트가 아니라 같은 행 리스트를 함께 가리키는 문제가 생길 수 있다.

bad_grid = [[0] * 3] * 2
bad_grid[0][1] = 7
print(bad_grid)  # [[0, 7, 0], [0, 7, 0]]

good_grid = [[0] * 3 for _ in range(2)]
good_grid[0][1] = 7
print(good_grid)  # [[0, 7, 0], [0, 0, 0]]

bad_grid에서는 두 행이 같은 안쪽 리스트를 참조하므로 첫 행의 열 1을 바꾸면 둘째 행도 함께 바뀐 것처럼 보인다. good_grid는 반복할 때마다 새 행 리스트를 만들기 때문에 한 칸의 변경이 그 행에만 적용된다.

교정 기준: 각 행을 서로 독립적으로 수정해야 한다면 행 객체도 각각 새로 만들어야 한다. 출력 모양이 처음에 같다는 사실만 확인하지 말고, 한 칸을 바꿨을 때 다른 행이 유지되는지 시험한다.

중첩 순회는 바깥 반복이 행을, 안쪽 반복이 셀을 방문한다

2차원 리스트의 모든 값을 행 순서대로 처리하려면 두 개의 반복 구조를 중첩한다. 바깥 반복의 변수 departure_row에는 한 행 리스트가 차례로 들어가고, 안쪽 반복의 변수 value에는 그 행의 셀 값이 차례로 들어간다.

distance = [
    [0, 151, 290],
    [151, 0, 178]
]

for departure_row in distance:
    for value in departure_row:
        print(value, end=" ")
    print()

출력은 첫 행의 0 151 290이 한 줄에 나오고, 다음 줄에 151 0 178이 나온다. 안쪽 print()의 end=" "가 각 값을 같은 줄에 이어 놓고, 안쪽 반복이 끝난 뒤 들여쓴 수준이 하나 줄어든 print()가 줄을 바꾼다.

  1. 바깥 반복이 첫 행을 선택한다.
  2. 안쪽 반복이 첫 행의 모든 열을 왼쪽에서 오른쪽으로 방문한다.
  3. 안쪽 반복이 끝나면 줄을 바꾼다.
  4. 바깥 반복이 다음 행을 선택하고 같은 과정을 반복한다.

행이 R개이고 모든 행에 열이 C개라면 안쪽 작업은 총 R × C번 수행된다. 2행 3열 예제에서는 값 출력이 6번 일어난다.

데이터의 규칙과 리스트 구조의 규칙을 분리해야 한다

도시 거리표의 대각선이 0이고 distance[0][1]과 distance[1][0]이 같은 것은 거리 데이터가 가진 의미다. 파이썬의 2차원 리스트가 자동으로 대각선을 0으로 만들거나 좌우 대칭을 보장하는 것은 아니다.

관찰어디에서 온 규칙인가?다른 2차원 리스트에도 자동 적용되는가?
첫 인덱스가 행을 선택함중첩 리스트의 접근 구조예
각 행을 다시 인덱싱해 셀을 선택함리스트 안에 리스트가 있는 구조예
같은 도시 사이 거리가 0임거리표의 의미아니요
두 도시의 왕복 거리 값이 대칭임이 예제 데이터의 조건아니요

성적표, 사진의 픽셀, 틱택토 게임판도 2차원 리스트로 표현할 수 있지만 셀 값의 의미와 검증 규칙은 서로 다르다. 자료구조가 제공하는 것은 행과 열로 값을 조직하고 접근하는 방법이며, 어떤 값이 허용되는지는 해결하려는 문제에서 정한다.

오류는 구조·좌표·생성·순회의 네 층에서 진단한다

  1. 구조: 바깥 원소가 실제로 행 리스트인지, 모든 행의 길이가 필요한 조건을 만족하는지 확인한다.
  2. 좌표: 문제의 세로·가로 의미를 행·열 인덱스로 올바르게 번역했는지 확인한다.
  3. 생성: 각 행을 새 리스트로 만들었는지, 완성된 행을 바깥 리스트에 추가하는 들여쓰기가 맞는지 확인한다.
  4. 순회: 바깥 반복이 행을 선택하고 안쪽 반복이 그 행의 값을 방문하는지 확인한다.

인덱스 오류가 나면 먼저 행과 열의 범위를 확인한다. 한 칸 수정이 다른 행까지 바꾸면 행 참조의 공유를 의심한다. 출력이 한 줄로만 나오면 값 출력과 행 종료 줄바꿈의 들여쓰기를 살핀다. 이처럼 증상을 구조의 어느 층에서 생긴 문제인지 연결하면 코드를 무작정 바꾸지 않고 원인을 좁힐 수 있다.

최소 검증 입력: 2행 3열처럼 눈으로 추적 가능한 작은 데이터를 사용해 네 모서리 [0][0], [0][2], [1][0], [1][2]를 읽고 한 칸을 수정한 뒤 전체를 순회해 본다. 접근·변경·순회가 모두 맞으면 더 큰 표로 확장한다.

핵심 개념 정리

  • 2차원 리스트는 행 리스트들을 원소로 가진 바깥 리스트다.
  • data[row][column]은 먼저 행 리스트를 선택한 뒤 그 행의 열 값을 선택한다.
  • 바깥 리스트의 길이는 행 수이고, 각 행 리스트의 길이는 해당 행의 열 수다.
  • 파이썬은 행 길이가 다른 중첩 리스트도 허용하므로 직사각형 전제가 필요하면 별도로 검사한다.
  • 동적 생성에서는 안쪽 반복으로 한 행을 완성한 뒤 바깥 리스트에 추가한다.
  • 독립적으로 수정할 행들은 같은 행 객체를 공유하지 않도록 각각 생성한다.
  • 중첩 순회의 바깥 반복은 행을, 안쪽 반복은 그 행의 셀을 방문한다.
  • 대각선 0이나 대칭 같은 성질은 2차원 리스트 자체가 아니라 거리표 데이터의 규칙이다.

2차원 리스트를 만나면 먼저 바깥 리스트와 행 리스트의 포함 관계를 그린다. 다음으로 문제의 두 기준을 행과 열에 연결하고, data[행][열]을 왼쪽부터 두 단계로 읽는다. 생성할 때는 새 행을 완성한 뒤 바깥 리스트에 넣고, 순회할 때는 같은 계층을 따라 행 반복 안에 셀 반복을 둔다. 마지막으로 모양의 직사각형 여부, 인덱스 범위와 행 참조의 독립성을 작은 입력으로 검증하면 거리표뿐 아니라 성적표와 게임판에도 같은 사고법을 적용할 수 있다.

예상문제 10선

1. 2차원 리스트의 구조를 가장 정확하게 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 참조 이름의 개수가 아니라 리스트 원소의 중첩이 차원을 만든다.
  • ② 오답: 2차원 리스트는 행 리스트의 경계를 유지한다.
  • ③ 정답: 바깥 리스트가 행들을 보관하고 각 행 리스트가 셀 값을 보관한다.
  • ④ 오답: 대괄호 접근은 이미 존재하는 중첩 구조에서 대상을 선택할 뿐이다.

2. 거리표에서 행은 출발 도시, 열은 도착 도시이고 대전은 1, 대구는 3이라면 대전→대구의 접근식은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 첫 인덱스로 출발 행 1을, 둘째 인덱스로 도착 열 3을 선택한다.
  • ② 오답: 행과 열의 의미를 뒤집어 대구 출발·대전 도착으로 읽는다.
  • ③ 오답: 인덱스를 더하면 바깥 리스트의 한 원소만 선택할 뿐 두 좌표를 표현하지 못한다.
  • ④ 오답: 기본 리스트는 행을 고른 뒤 다시 대괄호로 열을 골라야 한다.

3. data[2]와 data[2][1]의 차이를 바르게 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 인덱스가 있으면 전체가 아니라 특정 원소를 선택한다.
  • ② 오답: 첫 대괄호는 바깥 리스트의 원소인 행을 선택한다.
  • ③ 오답: 선택 결과의 크기를 서로 바꾸어 설명했다.
  • ④ 정답: 첫 접근의 결과에 두 번째 접근을 이어 적용하는 두 단계 구조다.

4. grid = [[4, 8, 2], [7, 1, 9]]일 때 grid[0][2]의 값은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 8은 첫 행의 열 1에 있는 값이다.
  • ② 정답: 행 0의 리스트 [4, 8, 2]에서 열 2를 선택한다.
  • ③ 오답: 7은 둘째 행의 첫 열인 grid[1][0]이다.
  • ④ 오답: 9는 둘째 행의 열 2인 grid[1][2]다.

5. 중첩 반복문으로 2차원 리스트를 동적 생성하는 순서로 가장 적절한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 안쪽 반복으로 한 행을 완성하고 바깥 반복에서 그 행을 저장하는 계층을 따른다.
  • ② 오답: 저장할 바깥 리스트를 삭제하면 생성 결과를 누적할 수 없다.
  • ③ 오답: 행과 바깥 리스트가 준비되기 전에 행을 추가할 수 없다.
  • ④ 오답: 생성에 필요한 새 행과 열 값 추가 단계가 빠져 있다.

6. board = [[0] * 3] * 2에서 board[0][1] = 5를 실행했더니 두 행의 열 1이 모두 5가 된 원인은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 정수는 리스트의 원소로 정상적으로 저장할 수 있다.
  • ② 오답: 둘째 인덱스는 먼저 선택된 행 안의 한 원소를 고른다.
  • ③ 오답: 각 행은 서로 다른 값을 가질 수 있으며 그것이 일반적인 사용 방식이다.
  • ④ 정답: 바깥 곱셈이 독립 행 두 개가 아니라 같은 행 객체의 참조를 반복한다.

7. 2행 4열 리스트의 모든 셀을 중첩 반복문으로 한 번씩 방문하면 안쪽 작업은 총 몇 번 실행되는가?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 한 행의 열 개수만 세어 둘째 행 방문을 빠뜨렸다.
  • ② 정답: 각 행에서 4번씩 두 행을 방문하므로 2 × 4 = 8번이다.
  • ③ 오답: 행 수와 열 수를 더한 값은 전체 셀 수가 아니다.
  • ④ 오답: 열 수끼리 곱하면 행 개수가 반영되지 않는다.

8. data = [[1, 2, 3], [4, 5]]에서 data[1][2]를 실행한 결과로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 첫 인덱스 1은 첫 행이 아니라 두 번째 행을 선택한다.
  • ② 오답: data[1]까지만 실행했을 때의 결과다.
  • ③ 정답: 두 번째 행은 열 0과 1만 가지므로 열 2를 선택할 수 없다.
  • ④ 오답: 파이썬 리스트는 없는 인덱스의 기본값을 자동 생성하지 않는다.

9. 도시 거리표의 대각선 값이 0인 이유와 2차원 리스트 구조의 관계를 바르게 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 리스트는 사용자가 넣은 값을 보관할 뿐 특정 위치를 자동 변경하지 않는다.
  • ② 오답: 정사각형 모양과 셀 값의 의미는 별개의 조건이다.
  • ③ 오답: 반복문은 작성한 처리만 수행하며 대각선 값을 임의로 정하지 않는다.
  • ④ 정답: 0은 거리 문제의 의미에서 나오고 중첩 리스트는 그 값을 행·열로 조직한다.

10. 행마다 독립적으로 수정 가능한 5행 4열 게임판을 만들고 모든 칸을 검사하는 방법으로 가장 적절한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 행 객체의 독립성과 2차원 구조에 맞는 중첩 순회를 함께 만족한다.
  • ② 오답: 같은 행을 공유하면 한 칸 수정이 다른 행에도 퍼질 수 있고 모든 열도 방문하지 못한다.
  • ③ 오답: 기본 리스트 접근은 먼저 행을 선택한 뒤 그 행에서 열을 선택해야 한다.
  • ④ 오답: 행 경계를 없애면 2차원 위치와 행별 처리를 유지할 수 없다.

참고 자료와 작성 기준

이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 행·열 좌표 번역, 중첩 구조 추적, 동적 생성 상태표, 행 참조 공유 오류와 문제 해설은 학습자의 이해와 적용을 돕도록 구성하고 검토했습니다.

  • 작성·편집: 올에이클래스 학습연구팀
  • 주요 근거: 한국방송통신대학교 컴퓨터과학과 「파이썬프로그래밍기초」 강의록 15강 「실전 프로젝트 2-2」의 2차원 리스트, 1~10쪽
  • 범위 구분: 11쪽의 종료 화면은 본문 개념 범위에서 제외
  • 보충 자료: 외부 자료를 사용하지 않음
  • 편집 원칙: 올에이클래스 편집 정책
  • 최종 내용 검토: 2026-09-08

댓글