프로그래밍웹 교과서
이 페이지의 목차
2단원 · 11차시

중첩된 제어 구조

중첩 반복제어 흐름 추적탈출 조건

코드 예제: Python · C · 예상 50분

학습 목표

  • 반복 안의 반복, 반복 안의 선택이 어떻게 동작하는지 추적한다.
  • 중첩 제어 구조로 표와 도형을 출력하는 프로그램을 작성한다.

관련 성취기준

  • 프그02-04

    순차, 선택, 반복의 제어 구조를 이해하고, 문제 상황에 알맞은 제어 구조를 선택하여 이를 적용한 프로그램을 작성할 수 있다.

생각 열기 — 구구단 전체를 찍으려면

10차시에서 3단만 찍었다. 2단부터 9단까지 전부 찍으려면 어떻게 할까.

단 값을 바꿔 여덟 번 실행하는 방법이 있다. 아니면 같은 for 문을 여덟 번 복사해 붙일 수도 있다.

중첩 반복 — 안쪽이 다 돌고 나서 바깥이 한 칸

중첩 반복(nested loop) — 반복 안에 또 다른 반복이 들어 있는 구조의 동작 규칙은 하나다.

바깥이 한 번 돌 때마다 안쪽이 처음부터 끝까지 다 돈다.

바깥이 8번, 안쪽이 9번이면 안쪽 몸통은 8 × 9 = 72번 실행된다.

바깥 i = 1 ─┬─ 안쪽 j = 1
            ├─ 안쪽 j = 2
            └─ 안쪽 j = 3     ← 안쪽이 끝나면
바깥 i = 2 ─┬─ 안쪽 j = 1     ← 안쪽이 처음부터 다시
            ├─ 안쪽 j = 2
            └─ 안쪽 j = 3
Python별 찍기와 대각선 표
for i in range(1, 5):
    for j in range(i):
        print("*", end="")
    print()

print()
for i in range(1, 4):
    for j in range(1, 4):
        if i == j:
            print("O", end=" ")
        else:
            print(".", end=" ")
    print()
확인된 실행 결과
*
**
***
****

O . . 
. O . 
. . O 
C별 찍기와 대각선 표확인된 출력
#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 4; i++) {
        for (int j = 0; j < i; j++) printf("*");
        printf("\n");
    }

    printf("\n");
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++)
            printf("%s ", i == j ? "O" : ".");
        printf("\n");
    }
    return 0;
}
실행 결과
*
**
***
****

O . . 
. O . 
. . O 

줄바꿈이 어디 있는지가 모양을 정한다

별 찍기에서 눈여겨볼 것은 print() 의 자리다.

for i in range(1, 5):
    for j in range(i):
        print("*", end="")   ← 줄바꿈 없이 이어 찍는다
    print()                  ← 안쪽 반복이 끝난 뒤 줄을 바꾼다

print() 가 안쪽 반복 밖, 바깥 반복 안에 있다. 이 자리를 한 칸만 옮기면 모양이 완전히 달라진다.

print() 의 자리 결과
안쪽 반복 밖 (지금) 줄마다 별이 늘어난다
안쪽 반복 안 별 하나마다 줄이 바뀐다 — 한 줄에 하나씩
바깥 반복 밖 별이 전부 한 줄에 붙는다

들여쓰기 한 칸이 흐름을 바꾼다. 9차시에서 Python의 들여쓰기가 문법이라고 한 것이 여기서 실감된다.

반복 안의 선택 — 걸러 내기

대각선 표는 반복 안에 조건이 들어간 예다.

for i in range(1, 4):
    for j in range(1, 4):
        if i == j:        ← 행 번호와 열 번호가 같은 자리
            print("O", ...)
        else:
            print(".", ...)

i == j 인 자리가 대각선이다. 3단원 6차시의 summarize 에서 반복 안에 if is_valid(...) 를 넣어 범위 밖 값을 걸러 낸 것과 같은 얼개다.

반복과 선택을 겹치는 흔한 얼개
하려는 일얼개보기
걸러 내기반복 안에 if범위 밖 점수를 빼고 합계 구하기
세기반복 안에 if + 세는 변수90점 이상이 몇 명인지
찾으면 멈추기반복 안에 if + break목록에서 특정 값 찾기
표 만들기반복 안에 반복구구단, 좌석표
표에서 골라 찍기반복 안에 반복 + if대각선만 O 로

C의 조건 ? A : B

C 예제에 i == j ? "O" : "." 라는 표기가 나온다. 조건이 참이면 A, 거짓이면 B 를 값으로 내놓는 짧은 표기다. if / else 를 한 줄로 줄인 것이다.

Python에도 비슷한 표기가 있다 — "O" if i == j else ".".

흐름을 손으로 짚는다

중첩이 들어가면 머릿속으로 따라가기 어렵다. 3단원 4차시의 책상 검사를 쓴다. 변수마다 칸을 만들어 한 회차씩 적는다.

별 찍기의 처음 세 회차를 짚어 보자.

바깥 i 안쪽 j 가 도는 횟수 이 줄에 찍히는 것 줄바꿈
1 1번 (j = 0) * 있음
2 2번 (j = 0, 1) ** 있음
3 3번 (j = 0, 1, 2) *** 있음
4 4번 **** 있음

안쪽 반복의 횟수 칸이 핵심이다. 이 칸이 i 에 따라 늘어나는 것을 눈으로 확인하면 삼각형이 나오는 이유가 보인다.

활동

실습

별 찍기와 달력 모양 출력

약 25분

1단계 — print() 자리를 옮겨 본다 (10분)

실행기의 [고치기]로 print() 의 들여쓰기만 바꿔 보고 결과를 적는다.

print() 위치 결과 (그대로 적기)
안쪽 반복 밖 (원래)
안쪽 반복 안 (한 단계 더 들여쓰기)
아예 지우면

들여쓰기 한 칸이 무엇을 바꿨는지 한 문장으로 적는다.

2단계 — 모양을 바꿔 본다 (10분)

아래 모양들을 만들어 본다. 각각 무엇을 고쳐야 하는지 먼저 생각한 뒤 실행한다.

(가)  ****      (나)     *      (다)  ****
      ****             **            ***
      ****            ***            **
      ****           ****            *
모양 무엇을 고쳤나
(가) 직사각형
(나) 오른쪽 정렬 삼각형
(다) 거꾸로 삼각형

(나)는 공백을 함께 찍어야 한다. 별 앞에 공백을 몇 개 찍어야 하는지 i 로 나타내 보자.

3단계 — 구구단 전체를 만든다 (5분)

생각 열기의 문제를 해결한다. 2단부터 9단까지 전부 찍는다.

  • 안쪽 몸통이 몇 번 실행되는가? 손으로 계산한 뒤 확인한다.
  • 단 사이에 빈 줄을 넣으려면 print() 를 어디에 넣는가?

4단계 — 책상 검사로 짚는다 (5분)

대각선 표 코드에서 i 와 j 의 변화를 표로 적는다. 아홉 칸 전부 적는다.

회차 i j i == j 찍히는 것
1 1 1 참 O
2 1 2
…
  • 줄이 바뀌는 회차는 몇 번째들인가?
  • i == j 를 i + j == 4 로 바꾸면 어떤 모양이 되는가? 먼저 표로 예상한 뒤 실행해 확인한다.

개념 확인 문제

보기를 누르면 바로 채점된다. 틀려도 해설을 읽으면 된다.

바깥 반복이 8번, 안쪽 반복이 9번이면 안쪽 몸통은 몇 번 실행되는가?프그02-04

별 찍기에서 print() 를 안쪽 반복 안으로 옮기면?프그02-04

for j in range(i) 처럼 안쪽 횟수를 바깥 변수로 두면?프그02-04

대각선 표에서 i == j 가 뜻하는 자리는?프그02-04

중첩된 흐름을 따라가기 어려울 때 쓰는 방법으로 알맞은 것은?프그02-04

더 알아보기

심화바깥 반복을 끊으려면

중첩 안에서 break 를 쓰면 안쪽 반복만 끊긴다. 바깥까지 한 번에 빠져나가는 문법은 두 언어에 없다. 방법은 두 가지다. 표시 변수를 하나 두어 바깥 조건에 넣거나, 그 부분을 함수로 떼어 내 return 으로 빠져나가는 것이다.

뒤의 방법이 대체로 깔끔하다. 16차시에서 함수로 프로그램을 나누는 것을 배우면 이 방법을 쓸 수 있다. 중첩이 깊어 빠져나가기 어려워졌다는 것은 그 부분이 따로 떼어 낼 만한 일이라는 신호이기도 하다.

심화2차원 표를 다루기

4차시 「더 알아보기」에서 리스트 안의 리스트를 잠깐 봤다. 중첩 반복이 그 표를 훑는 도구다.

점수 = [[88, 92, 75],
        [70, 80, 90]]

for 학생 in 점수:          ← 행마다
    for 과목점수 in 학생:   ← 그 행의 칸마다
        print(과목점수, end=" ")
    print()

바깥이 행, 안쪽이 열이다. C에서는 scores[i][j] 처럼 번호 두 개로 가리킨다. 학생별 평균은 바깥 반복 안에서, 과목별 평균은 반복 순서를 바꿔서 구한다. 표를 어느 방향으로 훑을지가 무엇을 구할지 정한다.

실무중첩이 깊어지면 느려진다

바깥 100번, 안쪽 100번이면 몸통이 1만 번 실행된다. 세 겹이면 100만 번이다. 자료가 100개일 때는 눈에 안 띄지만 10,000개가 되면 1억 번이 된다.

1단원 6차시에서 다룬 효율 이야기가 이 자리에서 실제 문제가 된다. 중첩을 세 겹 넘게 쓰고 있다면 정말 모든 짝을 봐야 하는지 다시 생각해 볼 만하다. 대개는 한 번만 훑으면서 필요한 것을 모아 두는 방법이 있다 — 3단원 4차시 「더 알아보기」에서 인원·합계·최고·최저를 한 번에 구한 것이 그 예다.

이어지는 학습

이 차시를 이해하려면 다음을 먼저 알아야 한다.

  • 10차시의 반복 구조 — 프로그래밍 프그02-04

교과서에서 찾기

교과서해당 위치쪽수
성림출판(Python)프로그래밍의 기초 › 04 제어문126쪽
씨마스커뮤니케이션(Python)프로그래밍 기초 › 4 제어문120쪽
삼양미디어(Python)프로그래밍의 기초 › 03 제어 구조98쪽
삼양미디어(C)프로그래밍의 기초 › 03 제어 구조100쪽
씨마스(C)C 프로그래밍의 기초 › 4 제어문96쪽
웅보출판사(C)프로그래밍의 기초 › Chapter 04 제어문116쪽