벡터와 거리·단위
150분 안팎
학습 목표
2차원 위치 차이와 유클리드 거리를 계산합니다.
개념
숫자보다 먼저 기준을 정합니다
로봇과 목표 위치가 모두 숫자 두 개라고 해서 바로 빼면 안 됩니다. 로봇 위치가 map 기준이고 목표가 sensor 기준이면 원점과 축이 다릅니다. 계산은 실행되지만 의미 없는 방향을 만들 수 있습니다. 이번 레슨에서는 같은 프레임의 두 점을 받아 목표까지의 차이와 직선 거리를 구합니다. 다음 레슨에서 축을 회전시키기 전에, 수치의 단위와 기준을 검사하는 습관을 먼저 만듭니다.
이번 모듈은 Python 3 표준 라이브러리의 math와 sys만 사용합니다. 브라우저에서는 시작 코드의 TODO를 채워 표준 입력과 출력을 비교하고, 미션에서는 ZIP을 푼 폴더에서 bash check.sh를 실행합니다. NumPy나 ROS 2 설치는 필요하지 않습니다. 따라하기의 Python 블록은 각각 독립 실행하며 앞 단계의 변수에 의존하지 않습니다. 입력 계약과 기대 결과를 먼저 적고, 실행 결과를 증거로 남깁니다.
점과 이동 벡터의 의미
점은 정한 원점을 기준으로 물체가 있는 곳을 나타냅니다. 이동 벡터는 출발점에서 도착점까지의 변화입니다. map에서 로봇이 (1,2)m이고 목표가 (4,6)m라면 차이는 (3,4)m입니다. dx는 목표 x에서 로봇 x를 뺀 값, dy는 목표 y에서 로봇 y를 뺀 값입니다. 뺄셈의 순서를 뒤집으면 거리는 같지만 방향은 반대가 되므로 방향과 크기를 함께 확인해야 합니다.
두 점을 뺀 결과는 여전히 map 축으로 표현됩니다. 로봇이 어느 쪽을 바라보는지는 이 계산에 들어 있지 않습니다. 따라서 dx가 양수라는 이유만으로 목표가 로봇 정면이라고 말할 수 없습니다. 로봇이 map의 음의 x축을 바라본다면 같은 목표가 뒤쪽일 수 있습니다. 변수 이름을 robot_map_m, target_map_m, delta_map_m처럼 정하면 제어 함수로 넘기기 전에 남은 변환을 발견할 수 있습니다.
여기서는 평면만 다룹니다. 축은 오른손 좌표계의 수평 단면으로 이해하고, 로봇에 붙은 프레임에서는 x가 전방, y가 왼쪽, z가 위를 향하도록 계약합니다. map의 x와 y는 도면에서 정한 축이며 로봇이 회전해도 바뀌지 않습니다. 좌표의 기준은 프레임 이름, 크기의 기준은 단위로 표시합니다. 같은 (1,0)도 sensor와 map에서 다른 점일 수 있다는 사실을 계산 전에 말로 설명할 수 있어야 합니다.
거리 계산을 코드로 옮깁니다
직선 거리는 두 성분의 제곱합에 제곱근을 취한 값입니다. Python에서는 math.hypot(dx,dy)를 사용합니다. dx가 -3이고 dy가 4여도 거리는 5입니다. 거리는 크기이므로 음수가 아니며, 같은 점끼리는 0입니다. abs(dx)+abs(dy)는 축 방향으로 나누어 이동한 길이에 해당하므로 이번 유클리드 거리와 다릅니다. 격자 경로 길이는 나중 계획 모듈에서 별도로 다룹니다.
리스트 [x,y]는 수학 벡터와 비슷하게 보이지만 Python의 리스트 뺄셈은 정의되어 있지 않습니다. target - robot을 실행하면 TypeError가 납니다. 외부 패키지를 쓰지 않는 이번 실습에서는 target[0]-robot[0]과 target[1]-robot[1]을 각각 계산합니다. 리스트 덧셈도 성분별 합이 아니라 연결이므로 [1,2]+[3,4]는 네 원소가 됩니다. 자료구조의 연산과 수학의 연산을 구분합니다.
단위 변환의 경계를 세웁니다
앞 모듈은 센서 거리를 range_m로 검증했습니다. 이번 위치 입력도 m로 통일합니다. 장치가 3000mm를 보내면 입력 경계에서 1000으로 나눠 3m로 만든 다음 계산합니다. m 좌표에서 mm 숫자를 그대로 빼면 1000배 오류가 생겨도 Python은 예외를 내지 않습니다. 숫자가 유한하다는 검사만으로 단위가 맞다는 것을 알 수 없으므로 필드 이름과 문서가 함께 필요합니다.
단위 변환은 값을 표시하는 소수점 자리수와 다릅니다. 3m를 3.000으로 출력해도 단위는 여전히 m입니다. 문자열을 만든 뒤 다음 연산에 쓰면 TypeError 또는 예상치 못한 문자열 처리가 생길 수 있습니다. 함수는 숫자 dx,dy,distance를 반환하고, 출력할 때만 f 문자열 형식을 적용합니다. 정수 나눗셈 //로 mm를 바꾸면 소수 m가 버려지므로 일반 나눗셈 /를 사용합니다.
입력과 출력의 계약
브라우저 과제는 공백으로 구분한 유한한 숫자 네 개 rx ry tx ty를 읽습니다. 네 숫자는 모두 같은 프레임의 m 좌표이며 순서는 로봇 x,y 다음 목표 x,y입니다. 첫 토큰을 입력 개수로 해석하지 않습니다. 빈 입력, 세 토큰, 문자 토큰은 이번 채점 입력에 없으며 파일 기반 검증은 미션에서 앞 모듈 구현을 이어받습니다. 범위를 작게 고정한 과제에서도 전제를 명시해야 다른 입력 경로와 혼동하지 않습니다.
결과는 dx dy distance 순서로 소수점 아래 세 자리씩 한 줄에 출력합니다. 정밀한 계산값은 유지하고 표시할 때만 절댓값이 0.0005보다 작은 성분을 0.0으로 보여 음의 0.000을 피합니다. 이것은 화면 계약일 뿐 로봇이 목표에 도착했다는 판단 기준이 아닙니다. 내부 허용 오차와 표시 정밀도는 별개의 선택이며, 제어기의 도달 반경을 출력 자리수로 정하지 않습니다.
실패를 읽고 좁힙니다
정상 사례 하나가 맞아도 방향 오류는 남을 수 있습니다. (1,2)에서 (4,6)으로 갈 때 (3,4,5), 반대로 갈 때 (-3,-4,5)가 되는지 비교합니다. 같은 점, 한 축만 다른 점, 소수 좌표를 순서대로 넣으면 뺄셈 순서와 단위 손실을 분리해 볼 수 있습니다. 거리만 비교하면 반대 방향 오류를 놓치므로 두 성분까지 함께 검사합니다. 기대값을 계산 코드로 다시 만들기보다 간단한 그림으로 독립적으로 구합니다.
ValueError: could not convert string to float는 좌표 연산보다 앞선 파싱 실패입니다. 쉼표나 m 글자를 토큰에 붙였는지 먼저 봅니다. not enough values to unpack은 네 토큰 계약이 지켜지지 않았다는 뜻입니다. unsupported operand type 메시지가 리스트 뺄셈에서 나오면 수식의 대상 형식을 확인합니다. 오류를 0 거리로 바꾸어 숨기지 않고 입력 계약과 연산 구현을 따로 수정합니다.
미션으로 이어갈 때 range_m는 센서에서 측정한 거리라는 의미를 유지합니다. map 원점부터의 거리로 덮어쓰면 앞 모듈의 범위 계약이 바뀝니다. 변환된 점은 point_m라는 별도 필드에 넣을 예정입니다. 이번 레슨의 완료 증거는 네 좌표 입력에서 부호가 있는 차이 두 개와 직선 거리를 재현하는 프로그램입니다. 내적과 벡터의 다른 응용은 더 읽기의 좌표 장에서 확장합니다.
따라하기
목표까지의 차이
동일 map 기준의 로봇과 목표 좌표를 성분별로 뺍니다.
import math
robot_map_m = (1.0, 2.0)
target_map_m = (4.0, 6.0)
dx = target_map_m[0] - robot_map_m[0]
dy = target_map_m[1] - robot_map_m[1]
print(f"{dx:.3f} {dy:.3f} {math.hypot(dx,dy):.3f}")
실행 결과
3.000 4.000 5.000
단위와 정수 나눗셈
mm 입력을 m로 바꾸고 소수 부분이 보존되는지 확인합니다.
distance_mm = 1250
print(f"m={distance_mm / 1000:.3f}")
print("floor", distance_mm // 1000)
실행 결과
m=1.250 floor 1
방향 반전과 같은 점
거리만 같다고 방향까지 같은 것은 아닙니다. 영거리도 확인합니다.
import math
for dx, dy in [(3,4),(-3,-4),(0,0),(0,-2.5)]:
print(f"{dx:.3f} {dy:.3f} {math.hypot(dx,dy):.3f}")
실행 결과
3.000 4.000 5.000 -3.000 -4.000 5.000 0.000 0.000 0.000 0.000 -2.500 2.500
확인 문제
실습
같은 프레임의 m 좌표 rx ry tx ty 네 숫자를 읽습니다. 목표-로봇의 dx dy와 math.hypot 거리 세 값을 소수점 아래 세 자리로 한 줄 출력합니다. 입력은 유한하며 절댓값 10000 이하입니다. 절댓값이 0.0005 미만인 표시값은 0.000으로 정리합니다. 경계 사례는 같은 점과 한 축만 다른 점입니다.
모범 답안
import sys, math
def show(value):
return f"{0.0 if abs(value) < 0.0005 else value:.3f}"
rx, ry, tx, ty = map(float, sys.stdin.read().split())
dx, dy = tx-rx, ty-ry
print(" ".join(show(v) for v in (dx,dy,math.hypot(dx,dy))))
더 읽기
면접 질문
- 서로 다른 좌표계의 위치를 변환하는 과정을 설명해 주시면 됩니다.