무엇을 만드나
글자를 밀어서 감춘 말을 되돌려 봅시다.
주어진 문제
알파벳을 일정한 칸수만큼 뒤로 밀어 감춘 암호문이 있습니다. 3칸 밀어 감춘 글을 입력받아 원래 글로 되돌려 내놓는 프로그램을 만듭니다. D는 A로, EFG는 BCD로 돌아갑니다.
「민다」는 말이 자연스럽게 들리지만, 잘 생각하면 이상합니다. 글자를 어떻게 더하고 뺀다는 걸까요? 그래서 먼저 컴퓨터가 글자를 사실 숫자로 다루고 있다는 것을 확인합니다.
| 단계 | 여기서 답할 질문 |
|---|---|
| 1 요구 분석 | 어떤 글자까지 받고, 앞을 넘어가면 어떻게 할 것인가 |
| 2 설계 | 글자를 어떻게 숫자로 바꿔 밀 것인가 |
| 3 구현 | 글자를 하나씩 순회하며 새 글을 어떻게 쌓을 것인가 |
| 4 테스트 | 앞쪽 글자를 넣으면 무슨 일이 벌어지는가 |
먼저 개발 문서 양식을 내려받으세요
읽기만 하지 말고 직접 채워 보세요. 단계마다 자기 답을 적어 두면 그 기록이 요구 사항 명세서이자 설계서가 됩니다. 답을 보기 전에 먼저 적어 봅니다.
요구 분석과 설계는 언어와 상관없습니다. 두 양식의 앞부분은 같고, 구현을 적는 자리만 언어에 맞춰 다릅니다. 수업에서 쓰는 언어에 맞는 것을 받으면 됩니다. 이 강의노트의 코드 예시는 파이썬으로 되어 있습니다.
1단계 · 무엇을 만들 것인가
A를 3칸 앞으로 밀면 어디로 갈까요?
되물어야 할 것들
- 대문자만 받나요? 소문자나 공백, 숫자가 섞이면 어떻게 하나요
- A보다 앞으로 가면? A를 3칸 밀면 알파벳을 벗어납니다
- 몇 칸을 미나요? 3으로 고정인가요, 사용자가 정하나요
- 글자 수에 제한이 있나요?
정리한 요구 사항
- 대문자 알파벳으로만 된 글을 한 줄로 입력받는다.
- 미는 칸수는 3으로 고정한다.
- 각 글자를 알파벳 순서에서 3칸 앞으로 옮겨 되돌린다.
- A보다 앞으로 넘어가면 Z 쪽에서 이어 받는다. A는 X가 되고 B는 Y가 된다.
- 되돌린 글을 한 줄로 내놓는다.
- 대문자 아닌 글자는 들어오지 않는다고 본다. 이번 판에서 걸러 내지 않는다.
4번을 정하지 않으면 프로그램이 이상해집니다
A를 3칸 앞으로 밀면 알파벳 바깥으로 나갑니다. 감추는 쪽에서 X를 3칸 밀어 A로 만들었다면, 되돌릴 때 A는 X로 돌아와야 맞습니다. 「끝에서 이어 받는다」는 규칙을 안 적어 두면 프로그램은 멈추지도 않고 알파벳이 아닌 기호를 내놓습니다.
요구 사항 2번에서 미는 칸수를 3으로 고정했습니다. 이 암호 방식의 약점이 무엇일지 여기서 짐작할 수 있을까요?
2단계 · 글자를 숫자로 바꿔서 민다
더할 수 없는 것은 더할 수 있는 것으로 바꿉니다.
| 토막 | 무엇을 정하는가 |
|---|---|
| 입력 | 대문자로만 된 글 한 줄 |
| 처리 | 글자마다 번호로 바꿔 3을 빼고, 넘치면 되돌리고, 다시 글자로 |
| 출력 | 되돌린 글 한 줄 |
글자마다 번호가 매겨져 있다
컴퓨터는 글자를 정해진 번호로 저장합니다. 대문자는 A가 65, B가 66… Z가 90으로 차례대로 이어져 있습니다. 차례대로라는 것이 중요합니다 — 그래서 번호를 3 빼면 알파벳도 3칸 앞으로 갑니다.
| 글자 | 번호 | 3을 빼면 |
|---|---|---|
| D | 68 | 65 → A |
| E | 69 | 66 → B |
| A | 65 | 62 → 알파벳이 아니다 |
A 줄이 요구 사항 4번이 필요한 이유입니다. 번호 62에도 무언가 글자가 있습니다. 그래서 멈추지 않고 그것을 내놓습니다.
끝에서 이어 받게 하려면
번호를 그대로 두고 밀면 65 아래로 떨어집니다. 그래서 A를 0으로 옮겨 놓고 생각합니다 — 번호에서 65를 빼면 A가 0, Z가 25가 됩니다. 이 상태에서 3을 빼면 0부터 25 사이를 벗어나는 값이 생기는데, 26으로 나눈 나머지를 취하면 다시 그 안으로 돌아옵니다. 마지막에 65를 도로 더하면 글자 번호가 됩니다.
A(0)를 예로 보면 — 0에서 3을 빼면 −3이고, 이것을 26으로 나눈 나머지는 23입니다. 23은 X이니 요구 사항 4번대로입니다.
푸는 순서
- 암호문을 한 줄 입력받는다.
- 결과를 담을 빈 문자열을 하나 마련한다.
- 암호문의 글자를 하나씩 꺼내 반복한다.
- 글자를 번호로 바꾸고 65를 빼서 0~25로 옮긴다.
- 3을 뺀 뒤 26으로 나눈 나머지를 취한다.
- 65를 도로 더해 글자로 바꾸고 결과 뒤에 이어 붙인다.
- 반복이 끝나면 결과를 내놓는다.
B를 「푸는 순서」에 넣어 손으로 따라가 보세요. 4번부터 6번까지 값이 어떻게 바뀌고, 어떤 글자가 나올까요?
3단계 · 파이썬으로 옮기기
먼저 되는 것부터, 그다음 넘치는 경우를 막습니다.
먼저 스스로 적어 본 뒤에 코드를 보세요. 요구 사항 4번(끝에서 이어 받기)을 빼고 단순히 3만 빼는 코드부터 만들어 봅니다.
암호.기본.py
# 암호문을 한 줄로 입력받는다
cipher = input()
plain = ""
for ch in cipher:
# 글자를 번호로 바꿔 3을 빼고 다시 글자로 되돌린다
plain += chr(ord(ch) - 3)
print(plain)글자와 번호를 오가는 두 함수
ord(글자)는 글자 하나를 받아 그 글자의 번호를 돌려준다.ord("D")는 68이다.chr(번호)는 번호를 받아 그 번호의 글자를 돌려준다.chr(65)는 A다.
결과를 쌓아 가는 방식
빈 문자열에서 시작해 한 글자씩 뒤에 이어 붙이며 답을 키워 갑니다. 빈 문자열은 반복 밖에서 마련합니다. 안에서 마련하면 돌 때마다 비워져 마지막 글자 하나만 남습니다.
이 코드는 요구 사항 4번을 못 지킵니다
DEFG를 넣으면 ABCD가 잘 나옵니다. 그런데 ABC를 넣으면 알파벳이 아닌 기호가 나옵니다. 멈추지 않으므로 실행 오류가 아니라 논리 오류(멈추지 않고 틀린 답을 내는 오류)입니다. 앞쪽 글자를 넣어 보기 전에는 발견되지 않습니다.
암호.순환.py
cipher = input()
plain = ""
for ch in cipher:
# A를 0으로 옮겨 놓고 밀면, 26으로 나눈 나머지가 알파벳 안을 돌게 한다
n = (ord(ch) - 65 - 3) % 26
plain += chr(n + 65)
print(plain)순환 코드에서 마지막에 65를 더하는 줄을 빼면 어떻게 될까요? 번호 0~25에는 어떤 글자들이 있을지 생각해 보세요.
4단계 · 넣어 보고 확인하기
뒤쪽 글자만 넣어 보면 아무 문제가 없어 보입니다.
이 프로그램의 오류는 흔한 값에서는 안 드러납니다. D 이후의 글자만 넣어 보면 기본 코드도 완벽해 보입니다.
| 넣은 값 | 나와야 할 결과 | 무엇을 보려는 값인가 |
|---|---|---|
| DEFG | ABCD | 넘치지 않는 흔한 경우 |
| PYTHON | MVQELK | 여러 글자 — 하나씩 제대로 도는지 |
| D | A | 넘치기 직전의 경계 |
| C | Z | 넘치는 첫 글자 — 요구 사항 4번 |
| A | X | 가장 많이 넘치는 경우 |
| ABC | XYZ | 넘치는 글자만 이어진 경우 |
결과가 어긋났다면 여기부터 봅니다
- A·B·C만 이상한 기호로 나온다 — 26으로 나누는 줄이 빠졌습니다
- 마지막 글자 하나만 나온다 — 빈 문자열을 반복 안에서 마련하고 있습니다
- 한 줄이 아니라 조금씩 길어지는 여러 줄이 나온다 — 내놓는 줄이 반복 안에 들어갔습니다
- 전부 알파벳이 아닌 것으로 나온다 — 65를 도로 더하는 줄이 빠졌습니다
표의 C → Z를 손으로 검산해 보세요. C는 65를 뺐을 때 몇이고, 3을 빼면 얼마이며, 26으로 나눈 나머지는 얼마인가요?
더 해 보기
고정해 둔 것을 풀어 봅시다.
미는 칸수를 입력받는다
3으로 고정한 것을 풀어 봅니다. 26으로 나눈 나머지를 쓰고 있다면 칸수가 100이어도 그대로 동작합니다. 왜 그런지 설명할 수 있나요?
감추는 쪽도 만든다
원래 글을 받아 3칸 뒤로 밀어 감추는 프로그램입니다. 빼기를 더하기로 바꾸기만 하면 될까요? Z 쪽에서 넘치는 경우를 확인해 보세요.
공백을 그대로 둔다
요구 사항 6번을 풀어 공백은 밀지 않고 그대로 내보내게 합니다. 문장이 훨씬 읽기 쉬워집니다.
칸수를 모를 때 풀어 본다
25가지를 전부 밀어 보고 모두 보여 주면 그중 하나가 읽히는 말일 것입니다. 이 암호의 약점이 무엇인지 여기서 드러납니다.
「칸수를 모를 때 풀어 본다」 과제처럼 25가지를 모두 시도해 보면 풀린다면, 이 암호를 오늘날 비밀을 지키는 데 쓸 수 있을까요?