이 페이지의 목차
중첩된 제어 구조
코드 예제: 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
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
#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쪽 |