본문으로 건너뛰기

반복 안의 반복

파이썬 목차반복레슨 22 / 59

목차

반복의 덩어리 안에 반복을 또 넣으면, 바깥 반복이 한 바퀴 돌 때마다 안쪽 반복이 처음부터 끝까지 다 돕니다. 이런 반복을 겹친 반복(중첩 반복, nested loop)이라고 부릅니다.

2단을 찍는 데는 반복 하나면 됩니다. 2단과 3단을 다 찍으려면 단마다 그 반복을 한 번씩 되풀이해야 합니다. 반복을 반복하는 것입니다. 메뉴와 컵 크기의 모든 짝을 늘어놓을 때처럼 두 가지를 짝지어 볼 때도 같은 꼴을 씁니다.

19단계 중 1단계. 바깥 반복은 i(2, 3), 안쪽 반복은 j(1, 2, 3)를 꺼냅니다.

1바깥 한 바퀴마다 안쪽은 처음부터 끝까지

2단과 3단에 1부터 3까지 곱한 값을 찍습니다. 바깥 반복의 덩어리 안에 안쪽 반복이 있고, 안쪽 반복의 덩어리는 8칸 들여 씁니다.

for i in range(2, 4):
    for j in range(1, 4):
        print(i, j, i * j)
실행 결과
2 1 2
2 2 4
2 3 6
3 1 3
3 2 6
3 3 9

찍힌 차례를 봅니다. i가 2인 동안 j가 1, 2, 3을 다 돈 뒤에야 i가 3이 됩니다. 그리고 j는 다시 1부터 셉니다.

  1. 1번 줄: 바깥 반복이 2를 꺼내 그 값에 i를 붙입니다.
  2. 2~3번 줄: 안쪽 반복이 1, 2, 3을 차례로 꺼내 그 값에 j를 붙이며 3번 줄을 세 번 실행합니다.
  3. 안쪽 반복이 끝나면 바깥 덩어리의 끝이라 1번 줄로 돌아가 3을 꺼내 그 값에 i를 붙입니다.
  4. 2번 줄이 다시 실행되면 안쪽 반복은 range(1, 4)를 처음부터 다시 꺼냅니다.

추적 표로 바깥 첫 바퀴를 따라가면 이렇습니다.

줄바뀐 이름값출력
1i2
2j1
32 1 2
2j2
32 2 4
2j3
32 3 6
2꺼낼 수 없음 — 안쪽 반복 끝
1i3
2j1
…

안쪽 반복은 바깥 덩어리 안에 있는 줄이라, 바깥 한 바퀴마다 통째로 실행됩니다(규칙 4).

2몇 번 실행되나 — 바깥 바퀴 × 안쪽 바퀴

아래 코드는 무엇을 찍을까요? 답을 정한 뒤 펼쳐 보세요.

count = 0
for i in range(2, 4):
    for j in range(1, 4):
        count = count + 1
print(count)
실행 결과 보기
실행 결과
6

예상과 다르면 그곳이 배울 곳입니다. 바깥이 2바퀴이고, 바깥 한 바퀴마다 안쪽이 3바퀴를 돕니다. 그래서 4번 줄은 2 + 3 = 5번이 아니라 2 × 3 = 6번 실행됩니다. 2단·3단 곱셈표가 2줄 × 3칸 = 6칸인 것과 같습니다.

3들여쓰기로 정하는 자리

겹친 반복 안의 줄은 들여쓰기가 0칸, 4칸, 8칸 가운데 하나입니다. 몇 칸이냐에 따라 그 줄이 실행되는 횟수가 정해집니다. 단마다 곱을 다 찍은 뒤 '2단 끝'을 한 번 찍으려면, 그 줄을 안쪽 for 줄과 같은 4칸에 둡니다.

for i in range(2, 4):
    for j in range(1, 4):
        print(i * j)
    print(f'{i}단 끝')
실행 결과
2
4
6
2단 끝
3
6
9
3단 끝

4번 줄은 4칸이라 바깥 덩어리에 들어, 바깥 한 바퀴에 한 번씩 모두 두 번 실행됩니다. 같은 줄을 다르게 들이면 실행 횟수가 이렇게 바뀝니다.

4번 줄의 들여쓰기드는 덩어리실행 횟수
0칸없음(반복 밖)1
4칸바깥 반복2
8칸안쪽 반복6

예제

메뉴와 컵 크기의 모든 짝

커피와 라테 두 메뉴에 S·M·L 세 크기가 있습니다. 메뉴 첫 글자와 크기를 짝지어, 주문할 수 있는 가짓수를 모두 찍습니다.

for menu in '커라':
    for size in 'SML':
        print(menu + size)
실행 결과
커S
커M
커L
라S
라M
라L

바깥 메뉴 하나마다 안쪽 크기 셋이 다 돌아 2 × 3 = 6가지입니다. menu + size는 두 글자를 이어 붙입니다.

들여쓰기 칸 수가 섞이면

for i in range(2, 4):
    for j in range(1, 4):
        print(i, j)
      print(i)
실행 결과
  File "main.py", line 4    print(i)            ^IndentationError: unindent does not match any outer indentation level

맨 아래 줄부터 읽습니다. IndentationError는 들여쓰기 오류이고, '들여쓰기를 줄였는데 바깥 어느 줄의 들여쓰기와도 맞지 않는다'는 뜻입니다. 4번 줄은 6칸이라 바깥 덩어리(4칸)에도, 반복 밖(0칸)에도 맞지 않습니다. ^는 줄 끝을 가리키지만 고칠 곳은 줄 앞의 빈칸입니다. 4칸이나 0칸으로 맞춥니다.

자주 틀리는 곳

틀린 생각: 안쪽 반복과 바깥 반복이 번갈아 한 번씩 돈다.

두 for 줄이 위아래로 붙어 있어서, 2 1 2 다음에 3 2 6처럼 i와 j가 함께 한 걸음씩 나아갈 것 같습니다.

실제로는 1절 실행 결과처럼 2 1 2 다음에 2 2 4가 찍힙니다.

맞는 규칙: 안쪽 반복은 바깥 덩어리 안의 줄이라, 바깥 한 바퀴마다 처음부터 다시 돕니다. 안쪽 반복이 중간에 끝나지 않으면 끝까지 다 돌아, 안쪽 덩어리의 실행 횟수는 바깥 바퀴 × 안쪽 바퀴입니다(규칙 4).

틀린 생각: 안쪽과 바깥 반복은 같은 이름을 써도 서로 섞이지 않는다.

for 줄마다 이름을 따로 적으니, 반복마다 자기 이름이 있을 것 같습니다. 그러나 이름 i는 하나뿐입니다.

2단 코드(for i in range(1, 4): 다음 print(2 * i))를 바깥 반복으로 감싸 2단과 3단을 찍으려 했습니다. 바깥 이름도 늘 쓰던 i로 하고, 2를 i로 바꿨습니다.

for i in range(2, 4):
    for i in range(1, 4):
        print(i * i)
실행 결과
1
4
9
1
4
9

안쪽 for가 같은 이름 i를 1, 2, 3에 다시 붙여서, 바깥 반복이 붙여 둔 2·3과의 연결이 끊겼습니다. 그래서 2단·3단 대신 1, 4, 9가 두 번 찍힙니다. 바깥 반복은 range(2, 4)에서 다음 수를 꺼내 다시 붙이므로 두 바퀴는 그대로 돕니다.

맞는 규칙: 이름 하나는 한 번에 값 하나만 가리키므로, 안쪽 반복이 같은 이름을 다시 붙이면 바깥 반복이 붙여 둔 값은 그 이름으로 볼 수 없습니다. 안쪽 반복의 이름은 j처럼 바깥과 다르게 씁니다(규칙 3).

틀린 생각: 안쪽 반복 아래에 쓴 줄은 몇 칸을 들이든 안쪽 반복이 끝난 뒤에 실행된다.

줄이 안쪽 for 덩어리보다 아래에 있으니, 안쪽 반복이 끝난 뒤 한 번 실행될 것처럼 보입니다. 그 줄이 어느 덩어리에 드는지는 자리가 아니라 들여쓰기가 정합니다.

3절의 print(f'{i}단 끝')을 8칸 들이면 안쪽 덩어리에 들어, 3절 표처럼 곱 하나마다 따라 찍혀 여섯 번 나옵니다.

맞는 규칙: 줄은 들여쓰기 칸 수로 정해지는 덩어리에 들고, 그 덩어리가 도는 만큼 실행됩니다. 그 줄이 몇 번 실행되어야 하는지 먼저 정하고 칸 수를 맞춥니다(규칙 4).

확인 문제

1. 1절 코드의 안쪽 range(1, 4)를 range(1, 3)으로 바꿨습니다. 무엇이 찍히는지 마지막 줄까지 글자 그대로 쓰세요.

for i in range(2, 4):
    for j in range(1, 3):
        print(i, j, i * j)
힌트

range(1, 3)이 내주는 수부터 적고, 바깥 i가 2일 때와 3일 때를 차례로 따라갑니다.

정답
실행 결과
2 1 2
2 2 4
3 1 3
3 2 6

안쪽이 1, 2 두 바퀴라 모두 2 × 2 = 4줄입니다.

흔한 오답은 2 3 6과 3 3 9까지 쓰는 것입니다. 끝 수 3도 든다고 본 것입니다(끝도 든다는 생각). range는 끝 수를 넣지 않습니다.

2. 2단부터 4단까지, 1·2·3을 곱해 찍으려 합니다. 빈칸 둘을 채우고, 3번 줄이 모두 몇 번 실행되는지도 쓰세요.

for i in range(___, ___):
    for j in range(1, 4):
        print(i, j, i * j)
힌트

바깥 i가 2, 3, 4를 내주면 됩니다. 1절 코드의 range(2, 4)가 내주는 수와 견줘 봅니다.

정답

차례로 2와 5이고, 3번 줄은 3 × 3 = 9번 실행됩니다.

for i in range(2, 5):
    for j in range(1, 4):
        print(i, j, i * j)
실행 결과
2 1 2
2 2 4
2 3 6
3 1 3
3 2 6
3 3 9
4 1 4
4 2 8
4 3 12

흔한 오답은 range(2, 4)입니다. 마지막 단 4를 끝 자리에 써서 끝 수도 든다고 본 것입니다(끝도 든다는 생각). 끝 수는 넣지 않아 4단이 빠집니다.

3. 요일마다 1교시와 2교시를 차례로 찍는 시간표 코드입니다. 줄마다 찍히는 값을 고르세요.

for day in '월화':
    for period in range(1, 3):
        print(day, period)
  • ① 월 1, 월 2, 화 1, 화 2
  • ② 월 1, 화 2
  • ③ 월 1, 화 1, 월 2, 화 2
힌트

어느 for가 바깥이고 어느 것이 안쪽인지 들여쓰기로 먼저 가립니다. 그다음 바깥 첫 바퀴를 끝까지 따라갑니다.

정답
실행 결과
월 1
월 2
화 1
화 2

①입니다. 바깥이 '월'을 꺼낸 동안 안쪽이 1, 2를 다 돌고, 그다음에 '화'로 넘어갑니다.

  • ② 안쪽과 바깥이 번갈아 한 번씩 돈다고 봤습니다. 바깥 한 바퀴 안에서 안쪽은 끝까지 돕니다.
  • ③ 안쪽과 바깥을 뒤바꿔 읽었습니다. 바깥 반복은 1번 줄의 for day이고, 덩어리 안의 for period가 바깥 한 바퀴마다 다 돕니다.

아래 1절 코드의 2번 줄 range(1, 4)를 range(1, 2)로 바꾸면 찍히는 줄을 모두 봅니다.

처음 펼 때 약 6MB를 받습니다.컴퓨터에서 해 보기

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

파이썬 목차로