정보

다차원 데이터 구조

값이 서른 개라면 변수를 서른 개 만들 것인가.
묶어서 이름 하나로 부르면 됩니다.
그러면 표는 어떻게 저장할까요?

학습 목표

  1. 값이 여러 개일 때 변수를 하나씩 두는 대신 리스트로 묶는 까닭을 설명할 수 있다.
  2. 인덱스가 0부터 시작하는 까닭을 알고, 리스트에서 값을 꺼내고 바꾸고 잘라 낼 수 있다.
  3. 2차원 리스트로 표 모양의 데이터를 저장하고, 이 구조가 실제로 어디에 쓰이는지 예를 들 수 있다.

값이 많아지면 변수로는 감당이 되지 않는다

다섯 개도 버거운데 삼백 개라면 어떨까요.

다섯 과목 점수의 평균을 구한다고 해 봅시다. 변수만 쓴다면 값 하나에 변수 하나씩 두게 됩니다.

score1 = 88
score2 = 74
score3 = 95
score4 = 61
score5 = 79

print((score1 + score2 + score3 + score4 + score5) / 5)

실행은 잘 됩니다. 79.4가 나옵니다. 그런데 과목이 하나 늘면 어떻게 될까요? 변수를 하나 더 만들고, 더하는 식에도 끼워 넣고, 나누는 수도 5에서 6으로 고쳐야 합니다. 세 군데를 빠짐없이 고쳐야 하고, 한 군데라도 놓치면 답이 조용히 틀립니다.

과목이 아니라 전교생 삼백 명의 점수라면 이 방법은 아예 쓸 수 없습니다. 변수 이름을 삼백 개 지어야 하고, 더하는 식은 화면을 넘어갑니다.

리스트(list)

여러 값을 한 줄로 묶어 이름 하나로 부르는 구조입니다. 대괄호 [ ] 안에 값을 쉼표로 나누어 적습니다.

scores = [88, 74, 95, 61, 79]

print(sum(scores) / len(scores))

결과는 똑같이 79.4인데 줄이 셋으로 줄었습니다. sum은 다 더한 값을, len은 요소가 몇 개인지를 알려 주는, 파이썬이 미리 만들어 둔 함수입니다.

진짜 달라진 것은 줄 수가 아닙니다. 이제 과목이 여섯이 되든 삼백이 되든 대괄호 안에 값을 하나 더 적기만 하면 됩니다. sum(scores) / len(scores)는 한 문자도 고칠 필요가 없습니다. 묶어 두는 진짜 이유는 값이 몇 개인지 코드가 몰라도 되게 만드는 데 있습니다.

스스로 확인

반 학생 서른 명의 키 평균을 구하려고 합니다. 변수만 쓰면 이름을 몇 개 지어야 하고, 리스트를 쓰면 몇 개일까요? 한 명이 전학을 오면 각각 어디를 고쳐야 할지도 말해 보세요.

인덱스는 왜 0부터 시작할까

「몇 번째」가 아니라 「맨 앞에서 얼마나 떨어져 있나」를 세기 때문입니다.

묶어 두었으니 이제 그중 하나를 꺼내는 방법이 있어야 합니다. 리스트에 저장된 값 하나하나를 요소(element)라고 하고, 요소마다 번호가 매겨져 있습니다. 이 번호를 인덱스(index)라고 합니다.

리스트 scores 88 0 74 1 95 2 61 3 79 4 인덱스는 맨 앞에서 얼마나 떨어져 있는지를 나타낸다

왜 1이 아니라 0부터일까

사람은 「첫째, 둘째」라고 세는 데 익숙해서 처음에는 이상하게 느껴집니다. 그런데 컴퓨터가 세는 것은 「몇 번째인가」가 아니라 「맨 앞에서 얼마나 떨어져 있는가」입니다. 맨 앞 요소는 조금도 떨어져 있지 않으므로 0이고, 그다음이 1입니다. 줄자를 댈 때 시작점이 0인 것과 같습니다. 이 방식은 파이썬만이 아니라 C, 자바를 비롯해 많은 프로그래밍 언어가 함께 씁니다.

scores = [88, 74, 95, 61, 79]

print(scores[0])
print(scores[2])
print(scores[-1])

차례로 88, 95, 79가 나옵니다. scores[0]이 맨 앞, scores[2]가 세 번째 요소입니다. scores[-1]처럼 음수를 쓰면 뒤에서부터 셉니다. 맨 뒤 값을 꺼낼 때 길이를 몰라도 되어 편합니다.


바꾸고, 덧붙이고, 잘라 내기

인덱스를 알면 그 요소의 값을 바꿀 수도 있습니다. 맨 뒤에 값을 덧붙이거나, 원하는 구간만 잘라 낼 수도 있습니다.

scores = [88, 74, 95, 61, 79]

scores[1] = 80
scores.append(100)

print(scores)
print(scores[1:4])
리스트를 다루는 코드와 그 결과
코드 결과 무엇을 한 것인가
scores[1] = 80 인덱스 1의 값이 74에서 80으로 그 요소의 값을 바꿉니다.
scores.append(100) [88, 80, 95, 61, 79, 100] 맨 뒤에 값을 덧붙입니다. 리스트의 요소가 하나 늘어납니다.
scores[1:4] [80, 95, 61] 인덱스 1부터 인덱스 4 앞까지 잘라 냅니다.

잘라 낼 때 끝 인덱스는 들어가지 않습니다

scores[1:4]는 인덱스 1, 2, 3의 요소까지만 가져오고 인덱스 4의 요소는 빼고 옵니다. 「인덱스 1부터 4까지」가 아니라 「인덱스 1에서 시작해 인덱스 4 앞에서 멈춘다」고 읽어야 맞습니다. 익숙해지기 전까지 하나씩 어긋나는 실수가 자주 납니다. 이렇게 읽으면 가져오는 개수가 4 − 1 = 3개라는 것도 바로 나옵니다.

요소가 다섯 개인 리스트에서 scores[5]를 꺼내려 하면 오류가 나고 멈춥니다. 인덱스가 0부터 4까지뿐이기 때문입니다. 요소가 다섯 개면 마지막 인덱스는 4입니다. 처음에는 이 하나 차이에서 자주 실수합니다.

스스로 확인

요소가 열 개인 리스트가 있습니다. 마지막 요소의 인덱스는 몇일까요? 앞의 요소 세 개만 잘라 내려면 어떻게 적어야 할까요?

한 줄로는 나타낼 수 없는 것들

좌석 배치도, 시간표, 달력은 모두 가로세로가 있습니다.

리스트는 값을 한 줄로 늘어놓습니다. 그런데 세상에는 한 줄로 나타낼 수 없는 것이 많습니다. 좌석 배치도는 몇 번째 줄의 몇 번째 좌석인지를 함께 말해야 하고, 시간표는 무슨 요일 몇 교시인지를 함께 말해야 합니다. 두 가지 번호가 있어야 위치 하나가 정해집니다.

이럴 때는 리스트 안에 리스트를 넣습니다. 한 행이 리스트 하나가 되고, 그 행들을 다시 리스트로 묶는 것입니다. 이렇게 만든 것을 2차원 리스트라고 합니다.

seats = [
    ['학생 A', '학생 B', '학생 C'],
    ['학생 D', '학생 E', '학생 F'],
]

print(seats[1][2])

행이 두 개이고 각 행에 요소가 셋이니 2행 3열의 표입니다. 그림으로 보면 이렇습니다.

2행 3열 좌석 배치도와 각 요소의 인덱스
열 인덱스 0 열 인덱스 1 열 인덱스 2
행 인덱스 0 학생 A
seats[0][0]
학생 B
seats[0][1]
학생 C
seats[0][2]
행 인덱스 1 학생 D
seats[1][0]
학생 E
seats[1][1]
학생 F
seats[1][2]

그래서 seats[1][2]는 학생 F입니다. 대괄호를 두 번 쓰는 것이 낯설어 보이지만, 왼쪽부터 차례로 읽으면 쉽습니다. seats[1]이 먼저 인덱스 1인 행을 꺼내고, 그 행에서 다시 [2]가 인덱스 2인 요소를 꺼냅니다. 행을 먼저, 열을 나중에입니다.

행과 열의 순서를 바꾸면 엉뚱한 값이 나옵니다

seats[2][1]이라고 쓰면 인덱스 2인 행을 먼저 찾습니다. 그런데 행은 인덱스 0과 1 둘뿐이므로 오류가 나고 멈춥니다. 표가 정사각형이면 오류조차 나지 않고 엉뚱한 요소의 값이 조용히 나옵니다. 앞의 인덱스가 행, 뒤의 인덱스가 그 행 안의 열이라는 것을 헷갈리지 마세요.

스스로 확인

우리 반 시간표를 2차원 리스트로 저장한다면, 앞의 번호와 뒤의 번호는 각각 무엇을 가리키게 하겠습니까?

컴퓨터는 그림도 표로 본다

화면에 보이는 사진 한 장도 2차원 리스트로 저장할 수 있습니다.

2차원 리스트가 쓰이는 곳 가운데 가장 놀라운 것은 그림입니다. 화면에 보이는 사진은 아주 작은 점이 가로세로로 촘촘히 박힌 것이고, 점 하나하나에는 얼마나 밝은지를 나타내는 수가 들어 있습니다. 수가 가로세로로 놓였으니 표입니다.

image = [
    [255, 255, 0, 255, 255],
    [255, 0, 0, 0, 255],
    [0, 0, 0, 0, 0],
]

print(image[0][2])
print(len(image), len(image[0]))

0은 가장 어둡고 255는 가장 밝다고 정해 두면, 이 3행 5열짜리 표는 다음과 같은 그림이 됩니다.

255 255 0 255 255
255 0 0 0 255
0 0 0 0 0

어두운 점만 눈으로 이으면 산 모양이 보입니다. 사람에게는 그림이지만 컴퓨터에게는 수가 저장된 표일 뿐입니다.

그림을 표로 두면 좋은 점이 있습니다. 모든 점의 값을 255에서 빼면 밝고 어두운 것이 뒤집혀 사진의 밝기를 반전시킨 것이 됩니다. 모든 점의 값에 같은 수를 곱하면 사진이 밝아지거나 어두워집니다(255를 넘는 값은 255로 맞춥니다). 사진 보정 기능의 상당수가 이런 계산입니다.


차원을 더 늘릴 수도 있다

색이 있는 사진은 어떨까요? 점 하나에 빨강 · 초록 · 파랑의 밝기 세 개가 필요합니다. 그러면 점 하나에 값이 세 개 필요하니 표를 세 장 겹쳐 놓은 것과 같아집니다. 이것이 3차원입니다.

차원에 따라 값 하나를 가리키는 데 필요한 번호의 개수
차원 번호 몇 개로 찾는가 이런 것을 나타낸다
1차원 하나 — [i] 점수 목록, 이름 목록처럼 한 줄로 늘어놓을 수 있는 것
2차원 둘 — [i][j] 좌석 배치도, 시간표, 흑백 사진처럼 가로세로가 있는 것
3차원 셋 — [i][j][k] 색이 있는 사진, 여러 날의 시간표처럼 표가 여러 장 겹친 것

차원이 늘어난다고 새로운 문법을 배우는 것이 아닙니다. 대괄호를 하나 더 쓰는 것뿐입니다. 다만 차원이 늘수록 사람이 머릿속에 그리기는 어려워집니다. 그래서 실제로 만들 때는 번호가 각각 무엇을 뜻하는지 적어 두는 습관이 중요합니다. 여기서는 3차원이 있다는 것까지만 봅니다.

스스로 확인

산 모양 표에서 모든 점의 값을 255에서 뺀다면 어떤 그림이 될까요? 산 모양은 어떻게 보일지 말해 보세요.

핵심 정리

리스트

list

  • 여러 값을 한 줄로 묶어 이름 하나로 부른다
  • 대괄호 [ ] 안에 쉼표로 나누어 적는다
  • 진짜 이득은 값이 몇 개인지 코드가 몰라도 되는 것

인덱스

index

  • 0부터 시작한다 — 맨 앞에서 얼마나 떨어져 있는지를 센다
  • 요소가 다섯 개면 마지막 인덱스는 4
  • 잘라 낼 때 끝 인덱스는 들어가지 않는다

2차원 리스트

리스트 안의 리스트

  • 한 행이 리스트 하나가 되고, 그 행들을 다시 묶는다
  • seats[1][2]는 행을 먼저, 열을 나중에
  • 두 번호를 바꿔 쓰면 엉뚱한 값이 나온다

어디에 쓰이나

가로세로가 있는 것들

  • 좌석 배치도 · 시간표 · 달력처럼 번호가 둘 필요한 것
  • 사진은 밝기 값이 저장된 표 — 보정은 그 수를 계산하는 일
  • 차원이 늘어도 대괄호를 하나 더 쓸 뿐이다

확인 퀴즈

배운 내용을 확인해 봅시다.

Q1

scores = [88, 74, 95, 61, 79]일 때 scores[2]의 값은?

Q2

값을 리스트로 묶어 두면 좋은 점으로 가장 알맞은 것은?

Q3

2행 3열짜리 2차원 리스트 seats에서 seats[1][2]가 가리키는 것은?