무엇을 만드나
두 원을 그려 보지 않고 관계를 알 수 있을까요?
주어진 문제
두 원의 중심 좌표와 반지름을 입력받아, 두 원이 두 점에서 만나는지 · 한 점에서 만나는지 · 만나지 않는지를 판정해 내놓는 프로그램을 만듭니다.
눈으로 보면 한눈에 아는 것을 숫자만으로 판정해야 합니다. 다행히 규칙이 단순합니다. 두 중심 사이의 거리를 반지름의 합, 반지름의 차와 비교하기만 하면 됩니다. 어려운 곳은 계산이 아니라 경우를 빠짐없이 나누는 일입니다.
| 단계 | 여기서 답할 질문 |
|---|---|
| 1 요구 분석 | 몇 가지 경우가 있고 무엇으로 답할 것인가 |
| 2 설계 | 거리와 무엇을 비교해 경우를 나눌 것인가 |
| 3 구현 | 조건을 어떤 순서로 늘어놓을 것인가 |
| 4 테스트 | 다섯 경우를 각각 어떤 값으로 만들 것인가 |
먼저 개발 문서 양식을 내려받으세요
읽기만 하지 말고 직접 채워 보세요. 단계마다 자기 답을 적어 두면 그 기록이 요구 사항 명세서이자 설계서가 됩니다. 답을 보기 전에 먼저 적어 봅니다.
요구 분석과 설계는 언어와 상관없습니다. 두 양식의 앞부분은 같고, 구현을 적는 자리만 언어에 맞춰 다릅니다. 수업에서 쓰는 언어에 맞는 것을 받으면 됩니다. 이 강의노트의 코드 예시는 파이썬으로 되어 있습니다.
1단계 · 무엇을 만들 것인가
경우는 다섯인데 답은 셋뿐입니다.
되물어야 할 것들
- 몇 가지로 답하나요? 만나는 점의 개수로만인가요, 관계 이름까지인가요
- 한쪽이 다른 쪽 안에 들어 있으면? 만나지 않는 것과 같은 답인가요
- 두 원이 완전히 같으면? 중심도 반지름도 같으면 어떻게 하나요
- 좌표가 소수여도 되나요?
- 반지름이 0이면?
정리한 요구 사항
- 두 줄로 입력받는다. 한 줄에 중심 x, 중심 y, 반지름 세 정수를 공백으로 띄운다.
- 답은 세 가지 중 하나다 — 두 점에서 만난다 · 한 점에서 만난다 · 만나지 않는다
- 떨어져 있는 경우와 한쪽이 다른 쪽 안에 든 경우는 모두 「만나지 않는다」로 한다.
- 바깥에서 스치는 경우와 안쪽에서 스치는 경우는 모두 「한 점에서 만난다」로 한다.
- 정수만 들어온다고 본다. 반지름은 1 이상이다. 두 원이 완전히 같은 경우는 이번 판에서 다루지 않는다.
경우 다섯이 답 셋으로 접힙니다
실제 위치 관계는 다섯 가지인데 요구 사항 2번은 답을 셋으로 정했습니다. 3번과 4번이 어느 경우가 어느 답으로 접히는지를 못박은 자리입니다. 이것을 안 적어 두면 코드를 짜다가 「안에 들어 있으면 뭐라고 하지?」에서 멈춥니다. 경우의 수와 답의 수가 다를 수 있다는 것을 여기서 봐 두세요.
요구 사항 5번에서 두 원이 완전히 같은 경우를 뺐습니다. 그런 두 원은 만나는 점이 몇 개일까요? 세 가지 답 중 어디에도 넣기 어려운 이유를 말해 보세요.
2단계 · 거리 하나로 다 정해진다
두 원을 그리지 않고 비교하는 방법입니다.
| 토막 | 무엇을 정하는가 |
|---|---|
| 입력 | 두 줄, 각 줄에 정수 세 개 |
| 처리 | 중심 사이의 거리를 구해 반지름의 합 · 차와 비교한다 |
| 출력 | 세 가지 답 중 하나 |
거리를 두 값과 비교한다
기준이 되는 값은 둘입니다. 반지름의 합(두 원이 바깥에서 닿는 거리)과 반지름의 차(안쪽에서 닿는 거리)입니다. 거리가 이 둘 사이에 있으면 두 점에서 만나고, 딱 같으면 한 점, 벗어나면 안 만납니다.
| 거리가 | 실제 관계 | 내놓을 답 |
|---|---|---|
| 합보다 크다 | 떨어져 있다 | 만나지 않는다 |
| 합과 같다 | 바깥에서 스친다 | 한 점에서 만난다 |
| 합과 차 사이 | 가로지른다 | 두 점에서 만난다 |
| 차와 같다 | 안쪽에서 스친다 | 한 점에서 만난다 |
| 차보다 작다 | 하나가 다른 하나 안에 있다 | 만나지 않는다 |
거리가 커지는 순서대로 늘어놓으면 다섯 경우가 겹치지도 빠지지도 않게 나뉩니다. 「차」는 반드시 뺀 값의 절댓값이어야 합니다. 작은 원에서 큰 원을 빼면 음수가 나와, 거리가 그보다 크다는 비교가 늘 참이 되어 버립니다. 뒤에서 제곱한 채로 비교하면 부호가 사라져 절댓값을 빠뜨려도 결과는 같지만, 뜻을 분명히 하려고 절댓값을 씁니다.
제곱근을 구하지 않는다
거리를 구하려면 제곱해서 더한 뒤 제곱근을 씌워야 합니다. 그런데 세 값을 모두 제곱한 채로 비교하면 결과가 같습니다. 모두 0 이상인 값이라 제곱해도 크기 순서가 바뀌지 않기 때문입니다.
제곱근을 피하는 이유가 하나 더 있습니다. 입력이 정수이므로 제곱한 값끼리의 비교는 정수끼리의 비교가 되고, 정수는 「딱 같다」까지 정확하게 판정됩니다. 스치는 경우도 오차 없이 판정됩니다.
푸는 순서
- 두 원의 정보를 두 줄로 입력받는다.
- 중심 사이 거리의 제곱을 구한다.
- 반지름 합의 제곱, 반지름 차(절댓값)의 제곱을 구한다.
- 거리의 제곱이 합의 제곱보다 크면 「만나지 않는다」
- 아니고 합의 제곱과 같으면 「한 점에서 만난다」
- 아니고 차의 제곱보다 크면 「두 점에서 만난다」
- 아니고 차의 제곱과 같으면 「한 점에서 만난다」
- 둘 다 아니면 「만나지 않는다」
4번부터 8번까지가 거리가 큰 쪽에서 작은 쪽으로 내려갑니다. 순서를 뒤집어 작은 쪽부터 적으려면 부등호를 어떻게 바꿔야 할까요?
3단계 · 파이썬으로 옮기기
조건 다섯 개를 어떻게 이어 붙일까요?
먼저 스스로 적어 본 뒤에 코드를 보세요. 설계 여덟 줄이 코드에서도 거의 그대로입니다.
원겹침.기본.py
# 두 원의 중심과 반지름을 한 줄에 하나씩 입력받는다
x1, y1, r1 = map(int, input().split())
x2, y2, r2 = map(int, input().split())
# 제곱근을 구하지 않고 제곱한 채로 비교한다
d2 = (x2 - x1) ** 2 + (y2 - y1) ** 2
sum2 = (r1 + r2) ** 2
diff2 = abs(r1 - r2) ** 2
if d2 > sum2:
print("만나지 않는다") # 서로 떨어져 있다
elif d2 == sum2:
print("한 점에서 만난다") # 바깥에서 스친다
elif d2 > diff2:
print("두 점에서 만난다") # 두 점에서 가로지른다
elif d2 == diff2:
print("한 점에서 만난다") # 안쪽에서 스친다
else:
print("만나지 않는다") # 하나가 다른 하나 안에 들어 있다같은 답이 두 번 나옵니다
「만나지 않는다」와 「한 점에서 만난다」가 각각 두 곳에서 나옵니다. 줄이고 싶어지지만 그대로 두는 편이 낫습니다. 두 자리는 서로 다른 이유로 같은 답을 내놓는 것이라, 합쳐 놓으면 나중에 답을 나누어야 할 때 다시 풀어야 합니다. 실제로 「더 해 보기」에서 그 일을 하게 됩니다.
「아니고」로 이어야 합니다
조건들을 따로따로 떨어진 검사로 적으면 여러 줄이 한꺼번에 참이 되어 답이 여러 개 찍힙니다. 앞에서 걸러진 것은 뒤에서 다시 보지 않는다는 것이 「아니고」의 뜻입니다. 그래서 6번이 「합보다 작으면서 차보다 크다」를 다 적지 않고 「차보다 크다」만 적어도 되는 것입니다.
6번 줄에 「합보다 작다」를 굳이 적지 않아도 되는 이유를 4번과 5번 줄을 근거로 설명해 보세요.
4단계 · 넣어 보고 확인하기
다섯 경우를 손으로 만들어 넣어야 합니다.
답은 셋이지만 확인해야 할 경우는 다섯입니다. 같은 답이 나오는 두 경우를 각각 따로 넣어 봐야 조건 한 줄이 빠진 것을 잡을 수 있습니다. 테스트 표의 값은 모두 중심을 가로로 나란히 두어 거리를 눈으로 알 수 있게 골랐습니다.
| 넣은 값 | 거리 | 나와야 할 결과 | 어느 경우인가 |
|---|---|---|---|
| 0 0 1 / 5 0 1 | 5 | 만나지 않는다 | 떨어져 있다 (합 2보다 큼) |
| 0 0 1 / 2 0 1 | 2 | 한 점에서 만난다 | 바깥에서 스친다 (합과 같음) |
| 0 0 3 / 2 0 3 | 2 | 두 점에서 만난다 | 가로지른다 (합 6과 차 0 사이) |
| 0 0 5 / 2 0 3 | 2 | 한 점에서 만난다 | 안쪽에서 스친다 (차 2와 같음) |
| 0 0 5 / 1 0 1 | 1 | 만나지 않는다 | 안에 들어 있다 (차 4보다 작음) |
결과가 어긋났다면 여기부터 봅니다
- 안에 들어 있는데 「두 점」이 나온다 — 차와 비교하는 줄 없이 셋째 조건을 「나머지 모두」(
else)로 적었습니다 - 안쪽에서 스치는데 「두 점」이 나온다 — 차와 비교할 때 「크다」와 「크거나 같다」를 헷갈렸습니다
- 큰 원과 작은 원의 순서를 바꾸면 답이 달라진다 — 제곱하지 않은 거리와 비교하면서 차를 절댓값으로 만들지 않았습니다
- 답이 여러 줄 찍힌다 — 조건을 「아니고」로 잇지 않았습니다
테스트 표의 「가로지른다」 줄과 「안쪽에서 스친다」 줄은 거리가 똑같이 2인데 답이 다릅니다. 무엇이 달라서 답이 달라졌을까요?
더 해 보기
접어 두었던 경우를 열어 봅시다.
다섯 경우를 그대로 답한다
「떨어져 있다 · 바깥에서 스친다 · 가로지른다 · 안쪽에서 스친다 · 안에 들어 있다」 다섯 가지로 답하게 바꿔 봅니다. 조건은 하나도 안 고쳐도 됩니다 — 내놓는 말만 바꾸면 됩니다. 구현 절에서 같은 답을 합치지 않은 이유가 여기서 드러납니다.
완전히 같은 두 원
요구 사항 5번을 풀어 봅니다. 중심도 반지름도 같으면 거리도 0, 차도 0이라 지금 코드는 「한 점에서 만난다」고 답합니다. 맞는 답일까요? 요구 사항부터 다시 정해야 합니다.
소수 좌표를 받는다
정수 제약을 풀면 값이 소수가 되고, 컴퓨터는 0.1 같은 많은 소수를 아주 조금 어긋나게 담습니다. 그러면 스치는 경우의 「딱 같다」 판정이 흔들리기 시작합니다. 어떤 입력에서 틀린 답이 나오는지 찾아보세요.
만나는 점의 좌표까지
만나는지만이 아니라 어디서 만나는지를 구해 봅니다. 여기서는 제곱근을 피할 수 없습니다. 어떤 계산에는 근사가 꼭 따라온다는 것을 겪어 보는 과제입니다.
「완전히 같은 두 원」은 만나는 점이 무한히 많습니다. 이것을 세 가지 답 중 하나로 억지로 밀어 넣는 것과 새 답을 하나 더 만드는 것 중 어느 쪽이 나을까요?