프로그래밍

일차방정식 해 구하기

사람이 받은 것은 수식인데
컴퓨터가 받은 것은 글자 한 줄입니다.

학습 목표

  1. 글자로 들어온 식에서 필요한 값만 추출하는 방법을 설계할 수 있다.
  2. 문자열을 공백에서 자르고, 끝 글자를 떼어 내는 코드를 작성할 수 있다.
  3. 값이 비었거나 0인 경우처럼 계산이 무너지는 경우를 미리 찾아 막을 수 있다.
  4. 답의 모양(정수인지 소수인지)까지 요구 사항으로 정하고 그대로 내놓을 수 있다.

무엇을 만드나

사람은 식으로 보는 것을 컴퓨터는 글자로 받습니다.

주어진 문제

「3x + 5 = 20」 같은 일차방정식을 입력받아 x의 값을 구해 알려 주는 프로그램을 만듭니다.

손으로 푸는 것은 어렵지 않습니다. 5를 오른쪽으로 넘기고 3으로 나누면 x는 5입니다. 어려운 곳은 계산이 아니라 그 앞입니다. 컴퓨터가 받은 것은 식이 아니라 「3x + 5 = 20」이라는 글자 열한 개입니다. 그러니 이 글자 안에서 3과 5와 20을 꺼내는 일부터 해야 합니다.

단계 여기서 답할 질문
1 요구 분석 어떤 모양의 식까지 받아 줄 것인가
2 설계 글자 한 줄에서 숫자 셋을 어떻게 떼어 낼 것인가
3 구현 떼어 낸 것을 수로 바꿔 어떻게 계산할 것인가
4 테스트 어떤 식을 넣으면 무너지는가

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

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

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

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

「일차방정식을 푼다」는 말은 아직 너무 넓습니다.

일차방정식이라고 해도 생김새가 여럿입니다. 어디까지 받아 줄지 정하지 않으면 만들 수가 없습니다. 되물어 봅시다.

되물어야 할 것들

  • 어떻게 띄어 쓰나요? 「3x+5=20」처럼 붙여 써도 되나요
  • 계수가 없으면? 「x + 5 = 10」처럼 3이 안 붙어 있을 수 있습니다
  • 계수가 0이면? 「0x + 3 = 3」은 x를 구할 수 있나요
  • 답이 안 떨어지면? 소수점 몇 자리까지 보여 주나요
  • x가 오른쪽에 있으면? 「20 = 3x + 5」도 받아 주나요

다섯 가지가 전부 프로그램의 생김새를 바꿉니다. 하나라도 안 정하고 시작하면 코드를 적다가 되돌아오게 됩니다.

정리한 요구 사항

  1. 식은 한 줄로, 토막 사이를 한 칸씩 띄워 입력받는다. 예 — 3x + 5 = 20
  2. 좌변은 x가 있는 항 하나와 숫자 하나, 우변은 숫자 하나로 한다.
  3. 가운데 기호는 + 또는 − 둘만 받는다.
  4. 계수가 생략된 x도 받아 준다. 이때 계수는 1로 본다.
  5. 계수가 0이면 일차방정식이 아니라고 알리고 끝낸다.
  6. 답은 소수점 둘째 자리까지 반올림하고, 그 값이 딱 떨어지면 정수로 내놓는다.
  7. x가 오른쪽에 있는 식은 이번 판에서 받지 않는다.

1번이 뒤 단계를 통째로 정합니다

「한 칸씩 띄운다」는 사소해 보이지만 가장 큰 결정입니다. 띄어 쓰기로 정하면 공백에서 자르기만 하면 토막이 나뉩니다. 붙여 쓰기까지 받기로 하면 글자를 하나씩 순회하며 숫자와 기호를 구별해야 해서 프로그램이 몇 배로 커집니다. 요구 사항 하나가 난이도를 정한다는 것을 여기서 봐 두세요.

스스로 확인

요구 사항 7번은 「하지 않을 것」을 적은 자리입니다. 이것을 적어 두지 않으면 나중에 어떤 말이 나올까요?

2단계 · 글자에서 숫자를 꺼낸다

한 줄로 들어온 글자를 어떻게 토막 내야 할까요?

토막 무엇을 정하는가
입력 한 칸씩 띄어 쓴 식 한 줄 — 글자로 들어온다
처리 토막을 나눠 계수 · 기호 · 상수 · 우변을 뽑고, 이항해 나눈다
출력 x의 값 하나 — 정수 또는 소수 둘째 자리

공백에서 자르면 다섯 토막이 된다

요구 사항 1번에서 한 칸씩 띄어 쓰기로 정했기 때문에 공백을 기준으로 자르는 것만으로 토막이 깔끔하게 나뉩니다.

인덱스 토막 무엇인가
0 3x 계수와 미지수가 붙어 있다 — 여기서 3을 더 떼어 내야 한다
1 + 기호. 이항할 때 부호가 뒤집힌다
2 5 좌변의 상수
3 = 쓰지 않는다. 그래도 자리는 차지하므로 건너뛴다
4 20 우변의 값

인덱스 3을 쓰지 않는다고 해서 없는 것이 아닙니다. 자리는 그대로 있으므로 우변을 가져오려면 인덱스 2 다음이 아니라 인덱스 4를 봐야 합니다. 여기서 한 칸씩 밀려 엉뚱한 값을 가져오는 실수가 자주 납니다.

「3x」에서 3만 떼어 내기

인덱스 0 토막은 아직 계수와 x가 붙어 있는 글자입니다. 마지막 글자가 언제나 x이므로, 끝 한 글자를 잘라 내면 계수만 남습니다. 「3x」에서 끝을 자르면 「3」, 「12x」에서 자르면 「12」입니다.

「x」 하나만 있으면 아무것도 안 남는다

요구 사항 4번에서 계수가 생략된 x도 받기로 했습니다. 그런데 「x」에서 끝 한 글자를 자르면 남는 것이 없습니다. 빈 것을 수로 바꾸려 들면 거기서 프로그램이 멈춥니다. 그러니 설계에 「남은 것이 없으면 계수를 1로 본다」는 줄이 반드시 들어가야 합니다. 설계에서 못 보고 지나가면 구현 뒤에 실행 오류로 만나게 됩니다.

푸는 순서

  1. 식을 한 줄 입력받는다.
  2. 공백에서 잘라 다섯 토막으로 나눈다.
  3. 인덱스 0 토막의 끝 글자를 떼어 낸다.
  4. 떼어 낸 것이 비어 있으면 계수는 1, 아니면 그 값을 수로 바꾼다.
  5. 인덱스 1 토막을 기호로, 인덱스 2 토막을 상수로, 인덱스 4 토막을 우변 값으로 삼는다.
  6. 계수가 0이면 「일차방정식이 아니다」를 알리고 끝낸다.
  7. 기호가 +이면 x = (우변 − 상수) ÷ 계수
  8. 기호가 −이면 x = (우변 + 상수) ÷ 계수
  9. x를 둘째 자리까지 반올림한 뒤, 딱 떨어지면 정수로, 아니면 그대로 내놓는다.

6번이 7번 앞에 있는 이유

계수가 0인데 나누기부터 하면 0으로 나누게 되어 그 자리에서 멈춥니다. 막는 검사는 언제나 위험한 계산보다 앞에 둡니다.

이항하면 부호가 뒤집힌다

7번과 8번의 부호가 기호와 반대인 것에 유의합니다. +5는 넘기면 −5가 되고 −5는 넘기면 +5가 됩니다. 손으로 풀 때 자연스럽게 하던 일이라 코드에서 뒤집는 것을 잊기 쉽습니다.

스스로 확인

「2x - 4 = 10」을 「푸는 순서」 아홉 줄에 넣어 손으로 따라가 보세요. 7번과 8번 중 어느 줄로 가고, x는 얼마가 나오나요?

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

먼저 되는 것부터 만들고, 무너지는 경우를 막습니다.

먼저 스스로 적어 본 뒤에 코드를 보세요. 설계의 아홉 줄 중 4번과 6번, 9번을 빼고 먼저 만들어 봅니다. 가장 단순한 식 하나만 되게 하는 것이 첫 목표입니다.

일차방정식.기본.py

# "3x + 5 = 20" 처럼 토큰 사이를 한 칸씩 띄운 식을 받는다
equation = input()

# 공백에서 잘라 다섯 조각으로 나눈다
parts = equation.split()
# parts[0]="3x"  parts[1]="+"  parts[2]="5"  parts[3]="="  parts[4]="20"

a = float(parts[0][:-1])  # "3x"에서 끝의 x를 떼면 "3"
op = parts[1]             # "+" 또는 "-"
b = float(parts[2])
c = float(parts[4])       # parts[3]의 "="은 쓰지 않는다

# b를 우변으로 넘기고 a로 나눈다
if op == '+':
    x = (c - b) / a
else:
    x = (c + b) / a

print(x)

「3x + 5 = 20」은 이걸로 풀립니다

공백에서 자르니 다섯 토막이 되고, 인덱스 0의 끝 글자를 떼니 「3」이 남고, 기호가 +이니 (20 − 5) ÷ 3 = 5.0이 나옵니다. 여기까지 되면 절반은 온 것입니다. 나머지 절반은 이 코드가 무너지는 경우를 막는 일입니다.

기본 코드가 못 지키는 요구 사항 셋

  • 4번(계수 생략) — 「x + 5 = 10」을 넣으면 끝 글자를 뗀 자리에 아무것도 안 남아 수로 바꾸다가 멈춥니다.
  • 5번(계수 0) — 「0x + 3 = 3」을 넣으면 0으로 나누다가 멈춥니다.
  • 6번(답의 모양) — 답이 5여도 5.0이라고 나옵니다.

앞의 둘은 도중에 멈추는 실행 오류, 마지막은 멈추지 않고 틀린 답을 내는 논리 오류입니다. 셋 다 실행 전의 문법 검사는 멀쩡히 통과합니다.

일차방정식.완성.py

equation = input()
parts = equation.split()

# 계수가 생략된 "x + 5 = 10"이면 앞이 빈 문자열이 된다
a_str = parts[0][:-1]
a = float(a_str) if a_str else 1.0

op = parts[1]
b = float(parts[2])
c = float(parts[4])

if a == 0:
    # a가 0이면 x가 사라져 일차방정식이 아니다. 나누기도 할 수 없다
    print("계수가 0이면 일차방정식이 아닙니다")
else:
    if op == '+':
        x = (c - b) / a
    else:
        x = (c + b) / a

    # 먼저 둘째 자리까지 반올림한다. 실수 계산은 3이 2.9999999999999996처럼
    # 아주 조금 어긋날 수 있으므로, 반올림한 뒤에 딱 떨어지는지 본다
    x = round(x, 2)
    if x == int(x):
        print(int(x))
    else:
        print(x)

한 줄로 줄인 조건

계수를 정하는 줄이 낯설 수 있습니다. 「비어 있지 않으면 그 값, 비어 있으면 1」을 한 줄로 적은 것입니다. 여러 줄로 풀어 쓰면 이렇게 됩니다.

# 여러 줄로 쓰면 이렇게 된다
if a_str:              # 빈 문자열이 아니면
    a = float(a_str)
else:
    a = 1.0

# 한 줄로 줄이면 이렇게 된다
a = float(a_str) if a_str else 1.0

둘은 똑같이 동작합니다. 짧다고 언제나 나은 것은 아니고, 읽는 사람이 한눈에 알아볼 수 있을 때만 줄이는 편이 낫습니다.

스스로 확인

완성 코드에서 계수가 0인지 보는 줄을 아래로 옮기면 어떻게 될까요? 설계 6번을 7번 앞에 둔 이유를 떠올려 보세요.

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

요구 사항 일곱 줄을 하나씩 확인해 봅시다.

이 프로그램은 요구 사항마다 확인할 값이 정해져 있습니다. 테스트 표는 그것을 한 줄씩 옮겨 놓은 것입니다. 「나와야 할 결과」는 실행해 보기 전에 손으로 풀어 적었습니다.

넣은 값 나와야 할 결과 무엇을 보려는 값인가
3x + 5 = 20 5 가장 단순한 식. 답이 딱 떨어진다
3x - 5 = 10 5 빼기 기호 — 이항할 때 부호가 뒤집히는지
x + 5 = 10 5 요구 사항 4번 — 계수가 생략된 경우
2x + 1 = 4 1.5 요구 사항 6번 — 답이 소수인 경우
4x + 2 = 3 0.25 소수 둘째 자리까지 나오는지
0x + 3 = 3 안내 문구 요구 사항 5번 — 멈추지 않고 알리는지
0.1x + 0.2 = 0.5 3 요구 사항 6번 — 실수 계산이 2.9999999999999996처럼 조금 어긋나도 정수로 나오는지

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

  • 빼기 식에서만 답이 틀린다 — 이항할 때 부호를 뒤집었는지 봅니다. 두 갈래의 + 와 −가 바뀌기 쉽습니다
  • 우변을 수로 바꾸는 줄에서 멈춘다 — 우변을 인덱스 4가 아니라 인덱스 3 토막에서 가져오고 있는지 봅니다. 「=」는 수로 바꿀 수 없습니다
  • 「x + 5 = 10」에서 멈춘다 — 비어 있을 때 1로 보는 줄이 빠졌습니다
  • 답이 늘 5.0처럼 나온다 — 딱 떨어지는지 먼저 보는 줄이 빠졌습니다. 계산은 맞는데 모양이 틀린 것입니다

표를 다 통과해도

요구 사항에 없는 입력은 여전히 남아 있습니다. 「3x+5=20」처럼 붙여 쓰거나, 「20 = 3x + 5」처럼 뒤집어 쓰거나, 토막 수가 다른 식을 넣으면 멈춥니다. 이것들은 버그가 아닙니다 — 1번과 7번에서 받지 않기로 정한 것입니다. 받고 싶어지면 코드가 아니라 1단계부터 고쳐야 합니다.

스스로 확인

표에 한 줄을 더 보태 보세요. 계수가 생략되고 동시에 기호가 빼기인 식 — 「x - 3 = 4」는 무엇이 나와야 할까요?

더 해 보기

1단계에서 밀어낸 것을 하나씩 데려와 봅시다.

이 절의 과제는 전부 요구 사항을 넓히는 일입니다. 코드부터 열지 말고 1단계로 돌아가 요구 사항을 고쳐 적는 것에서 시작하세요. 테스트 표에도 줄이 늘어야 합니다.

음수 계수를 받는다

「-3x + 5 = 20」처럼 계수가 음수인 식입니다. 끝 글자를 떼는 방식은 대부분 그대로 통합니다. 먼저 왜 그런지 생각해 보세요 — 앞에 붙은 부호는 건드리지 않으니까요. 다만 계수를 생략한 「-x + 5 = 10」은 끝 글자를 떼면 「-」만 남아 수로 바꾸다가 멈춥니다. 이 경우를 어떻게 막을지도 요구 사항에 적어 보세요.

답의 자리를 정한다

「3x + 1 = 3」의 답은 끝없이 이어지는 소수입니다. 둘째 자리에서 반올림하면 0.67이 되는데, 이 값을 다시 식에 넣으면 3이 나오지 않습니다. 반올림이 무엇을 버리는지 생각해 보세요.

붙여 쓴 식도 받는다

「3x+5=20」을 받으려면 공백에서 자르는 방법을 통째로 버려야 합니다. 글자를 하나씩 순회하며 숫자와 기호를 구별해야 합니다. 요구 사항 한 줄이 프로그램을 얼마나 키우는지 직접 겪어 보는 과제입니다.

잘못된 식을 알린다

토막이 다섯이 아니거나 기호가 + · −가 아니면 멈추지 말고 「식을 다시 입력하세요」라고 알리게 만들어 보세요. 테스트에서 일부러 이상한 값을 넣어 보는 까닭이 여기 있습니다.

스스로 확인

네 과제 가운데 가장 손이 많이 갈 것은 어느 것일까요? 그렇게 생각한 이유를 요구 사항 1번과 이어서 말해 보세요.