프로그래밍

철근 자르기

같은 일을 수십 번 반복해야 한다면
무엇부터 정해야 할까요?

학습 목표

  1. 몇 번 반복할지를 계산으로 구하고, 그 횟수만큼 도는 반복을 설계할 수 있다.
  2. 나눗셈의 몫과 나머지를 구별해 쓰고, 버려지는 값이 무엇인지 설명할 수 있다.
  3. 자릿수를 맞춰 내놓는 형식을 요구 사항으로 정하고 그대로 구현할 수 있다.
  4. 반복 횟수가 0이 되는 경우까지 넣어 보고 결과를 확인할 수 있다.

무엇을 만드나

번호를 손으로 붙이면 몇 개까지 버틸 수 있을까요?

주어진 문제

긴 철근을 일정한 길이로 잘라 토막마다 F-0001, F-0002… 하는 식별 번호를 붙이려고 합니다. 전체 길이와 자를 길이를 입력받아 붙일 번호를 한 줄에 하나씩 내놓는 프로그램을 만듭니다.

열 개면 손으로 적어도 됩니다. 그런데 전체 길이가 1000이고 자를 길이가 3이면 333개입니다. 이렇게 같은 일을 여러 번 하는 것은 반복에 맡깁니다. 반복은 컴퓨터가 사람보다 압도적으로 잘하는 일이고, 사람은 「몇 번 도는지」만 정해 주면 됩니다.

단계 여기서 답할 질문
1 요구 분석 남는 자투리와 번호 모양을 어떻게 할 것인가
2 설계 몇 번 반복할지를 어떻게 구할 것인가
3 구현 번호의 자릿수를 어떻게 맞출 것인가
4 테스트 한 개도 못 만드는 경우에는 무엇이 나와야 하는가

먼저 개발 문서 양식을 내려받으세요

읽기만 하지 말고 직접 채워 보세요. 단계마다 자기 답을 적어 두면 그 기록이 요구 사항 명세서이자 설계서가 됩니다. 답을 보기 전에 먼저 적어 봅니다.

요구 분석과 설계는 언어와 상관없습니다. 두 양식의 앞부분은 같고, 구현을 적는 자리만 언어에 맞춰 다릅니다. 수업에서 쓰는 언어에 맞는 것을 받으면 됩니다. 이 강의노트의 코드 예시는 파이썬으로 되어 있습니다.

1단계 · 무엇을 만들 것인가

「잘라서 번호를 붙인다」에는 정하지 않은 것이 많습니다.

되물어야 할 것들

  • 딱 나누어떨어지지 않으면? 남는 자투리에도 번호를 주나요
  • 번호를 어떤 모양으로? 1인가요 0001인가요. 자릿수는 몇 자리인가요
  • 전체보다 자를 길이가 길면? 한 개도 못 만드는데 무엇을 내놓나요
  • 자를 길이가 0이면? 나눌 수가 없습니다
  • 번호가 네 자리를 넘으면? 10000번째는 어떻게 되나요

정리한 요구 사항

  1. 전체 길이와 자를 길이를 한 줄에 공백으로 띄어 정수로 입력받는다.
  2. 온전한 토막에만 번호를 붙인다. 남는 자투리는 버린다.
  3. 번호는 F- 다음에 네 자리 숫자로 하고, 빈 자리는 0으로 채운다. 예 — F-0001
  4. 1번부터 1씩 늘려 가며 한 줄에 하나씩 내놓는다.
  5. 한 개도 만들 수 없으면 아무것도 내놓지 않는다.
  6. 자를 길이는 1 이상이 들어온다고 본다. 0이나 음수는 이번 판에서 다루지 않는다.

5번은 「아무 일도 안 한다」를 정해 둔 것입니다

한 개도 못 만들 때 「0개입니다」라고 알릴지, 그냥 아무것도 안 내놓을지는 정하기 나름입니다. 여기서는 뒤쪽으로 정했습니다. 정해 두지 않으면 나중에 「아무것도 안 나오는데 고장 난 거 아니야?」라는 말이 나옵니다. 「아무 일도 일어나지 않는 것」도 정해 두어야 하는 동작입니다.

스스로 확인

요구 사항 3번이 네 자리로 정해져 있습니다. 만들 토막이 12345개라면 이 요구 사항은 지켜질 수 있을까요?

2단계 · 몇 번 돌지를 먼저 구한다

반복을 짜기 전에 답해야 할 질문이 하나 있습니다.

토막 무엇을 정하는가
입력 전체 길이와 자를 길이, 정수 두 개
처리 만들 수 있는 개수를 구하고, 그 횟수만큼 번호를 만든다
출력 번호를 한 줄에 하나씩. 개수만큼 여러 줄

몇 개를 만들 수 있나 — 몫이 답이다

전체 길이를 자를 길이로 나눈 몫이 만들 수 있는 개수입니다. 나머지는 자투리이고, 요구 사항 2번에서 버리기로 했으니 쓰지 않습니다.

전체 / 자를 길이 개수 남는 자투리
10 / 3 3개 1 — 버린다
9 / 3 3개 0 — 딱 떨어진다
5 / 6 0개 5 — 전부 자투리

5 / 6 줄을 눈여겨보세요. 몫이 0이면 반복을 한 번도 하지 않습니다. 따로 조건을 달지 않아도 요구 사항 5번이 저절로 지켜집니다.

푸는 순서

  1. 전체 길이와 자를 길이를 입력받는다.
  2. 개수 = 전체 길이를 자를 길이로 나눈 몫
  3. 번호를 1부터 개수까지 하나씩 늘려 가며 반복한다.
  4. 반복할 때마다 F- 뒤에 네 자리로 맞춘 번호를 한 줄 내놓는다.

「1부터 개수까지」가 함정입니다

파이썬에서 범위를 적을 때 끝값은 포함되지 않습니다. 1부터 3까지 돌게 하려면 「1부터 4까지」라고 적어야 1, 2, 3이 나옵니다. 이것을 잊으면 언제나 마지막 하나가 빠집니다. 문법 검사도 통과하고 결과도 그럴듯하게 나오는 논리 오류(멈추지 않고 틀린 답을 내는 오류)라 개수를 세어 보기 전에는 모릅니다.

스스로 확인

전체 길이 10, 자를 길이 3일 때 자투리 1이 남습니다. 이 값은 프로그램 어디에서도 쓰이지 않는데, 왜 설계 표에는 적어 두었을까요?

3단계 · 파이썬으로 옮기기

번호를 0001처럼 맞추는 일은 어떻게 할까요?

먼저 스스로 적어 본 뒤에 코드를 보세요. 설계 네 줄이 코드에서도 거의 네 줄입니다.

철근.기본.py

# 전체 길이와 자를 길이를 한 줄에 공백으로 띄어 입력받는다
total, cut = map(int, input().split())

# 나눈 몫이 만들 수 있는 개수다. 남는 자투리는 버린다
count = total // cut

# 1번부터 count번까지. 끝값은 포함되지 않으므로 1을 더한다
for i in range(1, count + 1):
    # 네 자리로 맞추고 빈 자리는 0으로 채운다
    print(f"F-{i:04d}")

네 자리로 맞추는 표기

04d는 세 가지를 한꺼번에 말하고 있습니다. d는 정수로 적으라는 뜻, 4는 네 칸을 차지하라는 뜻, 앞의 0은 남는 칸을 공백이 아니라 0으로 채우라는 뜻입니다. 0을 빼면 「F-   1」처럼 공백이 들어갑니다.

번호가 네 자리를 넘으면 잘리지 않고 그대로 늘어납니다. 10000번째는 F-10000이 되어 다섯 자리가 됩니다. 「최소 네 자리」이지 「정확히 네 자리」가 아닙니다. 요구 사항 3번이 큰 값에서 깨지는 경우입니다.

스스로 확인

반복 범위에서 1을 더하지 않으면 전체 10, 자를 길이 3일 때 번호가 몇 개 나올까요? 그리고 마지막 번호는 무엇일까요?

4단계 · 넣어 보고 확인하기

한 개도 안 나오는 경우를 꼭 넣어 봐야 합니다.

반복이 들어간 프로그램은 몇 번 돌았는지를 세어 봐야 합니다. 0번 도는 경우와 1번 도는 경우가 가장 잘 틀립니다.

넣은 값 나와야 할 결과 무엇을 보려는 값인가
10 3 F-0001 ~ F-0003 자투리가 남는 일반적인 경우
9 3 F-0001 ~ F-0003 딱 떨어져도 개수가 같은지
3 3 F-0001 하나만 한 번만 도는 경계
5 6 아무것도 안 나옴 0번 도는 경계 — 요구 사항 5번
30 3 F-0001 ~ F-0010 두 자리로 넘어갈 때 0이 제대로 채워지는지

결과가 어긋났다면 여기부터 봅니다

  • 언제나 하나가 모자라다 — 범위 끝에 1을 더하지 않았습니다
  • F-0000부터 시작한다 — 범위를 0부터 시작했습니다
  • F-1, F-2처럼 나온다 — 자릿수를 맞추는 표기가 빠졌습니다
  • 「F-   1」처럼 공백이 들어간다 — 채움 문자 0이 빠졌습니다
  • 반복 범위를 만드는 줄에서 멈춘다 — 몫이 아니라 보통 나눗셈을 써서 개수가 소수가 되었습니다
스스로 확인

표에 한 줄을 더 보태 보세요. 번호가 세 자리에서 네 자리로 넘어가는 경계를 보려면 무엇을 넣어야 할까요?

더 해 보기

1단계에서 미뤄 둔 것과 깨지는 경우를 봅시다.

요구 사항 6번에서 자를 길이가 0이나 음수인 경우를 다루지 않기로 했습니다. 0을 넣으면 나누는 줄에서 실행 오류로 멈춥니다. 0으로는 나눌 수 없기 때문입니다.

스스로 해 본 뒤에 열어 보세요

더 알아보기: 자를 길이를 검사하는 코드

나누기보다 앞에서 막습니다.

# 자를 길이가 0이면 나눌 수 없다. 나누기보다 앞에서 막는다
if cut <= 0:
    print("자를 길이는 1 이상이어야 합니다")
else:
    count = total // cut
    for i in range(1, count + 1):
        print(f"F-{i:04d}")

한 걸음 더

  • 맨 끝에 버린 자투리가 얼마인지 함께 알려 주기
  • 번호가 네 자리를 넘을 것 같으면 미리 알리기 — 요구 사항 3번을 지키려면 필요합니다
  • 번호 앞머리 F-를 사용자가 정하게 하기
스스로 확인

「네 자리를 넘을 것 같으면 미리 알린다」고 하려면 무엇과 무엇을 비교해야 할까요? 반복을 시작하기 전에 알 수 있을까요?