Human-to-Code Translation
Overview
알고리즘에서 진짜 어려운 건 "푸는 것"이 아니라, 사람이 머릿속으로 자연스럽게 하는 일을 기계가 따라 할 수 있는 형태로 옮기는 것이다. 사람은 패턴 매칭과 "그냥 보면 아는" 도약으로 풀지만, 기계는 그 도약을 한 번도 하지 못한다.
핵심 원리: 코드는 갑자기 튀어나오지 않는다. 말 → 손풀이 → 규칙 → 변수/흐름 → 코드 → 트레이싱 순으로 번역된다. 이 다리의 의미는 언어와 무관하고(같음), 달라지는 건 문법뿐이다.
When to Use
사람 풀이를 코드로 옮기는 과정이 막혔거나 그 과정을 설명해 달라는 요청에 적용한다. 경계·종료 오류는 실제 풀이와 코드로 원인을 확인한다. 사용자가 이미 설명한 단계는 되풀이하지 않고, 요청한 언어와 설명 깊이에 맞춘다.
When NOT to use: 이미 자명한 한 줄짜리, 또는 라이브러리 한 번 호출로 끝나는 일. 번역할 "사람의 풀이"가 없으면 이 스킬은 불필요하다.
The Translation Pipeline
- 사람 말로 다시 쓴다. 문제를 일상어로 옮기고, 손으로 풀 작은 구체 예제를 하나 정한다. (예: 배열 5칸, target 하나)
- 손으로 풀며 "내가 실제로 한 일"을 관찰한다. 답을 먼저 구하지 말고, 답에 이르는 절차를 본다. 필요한 중간 상태를 짧게 적는다.
- 암묵지를 명시화한다. "가운데를 본다", "큰 걸 고른다" 같은 그냥 아는 행동을 정확한 규칙으로 쪼갠다. (가운데 =
(low+high)//2? 정수 나눗셈? 같은 값이면?) 손풀이에 등장한 그냥 아는 행동 전부가 산술·비교식으로 적히면 완료. - 머릿속 상태를 변수/자료구조로 꺼낸다. "내가 기억하고 있는 게 뭐지?"를 묻는다. 그 답이 곧 변수와 자료구조다. (아래 매핑표)
- 반복·조건·종료를 제어 흐름으로 옮긴다. "
할 때까지" →이면" →while+ 종료 조건, "if, "하나씩" →for. 언제 멈추는지를 반드시 명시한다(무한 루프 방지). - 규칙을 코드로 받아쓰고, 손으로 트레이싱해 검증한다. 규칙 한 줄 ≈ 코드 한 줄. 그다음 "없는 값 / 맨 끝 / 빈 입력" 경계를 표로 돌려본다.
핵심 매핑표: 사람의 머릿속 행동 → 기계 구성요소
이 표가 4~5단계(머릿속 상태 → 변수, 사람 말 → 제어 흐름)의 사전이다. "사람의 표현"을 보고 오른쪽을 떠올려라.
| 사람의 표현 / 머릿속 행동 | 기계 구성요소 |
|---|---|
| "기억해 둔다 / 머릿속에 담아둔다" | 변수 (variable) |
| "쭉 훑어본다 / 하나씩 차례로 본다" | for 반복문 |
| "~인 동안 계속 / ~할 때까지" | while + 종료 조건 |
| "가장 큰/작은 걸 고른다" | max/min 추적 변수 + 비교 |
| "지금까지 중 제일 ~" | 누적 변수 (running best/sum) |
| "본 적 있나? / 중복인가?" | 집합(set) / 딕셔너리 membership |
| "이름표 붙여 분류해 모은다" | 딕셔너리/맵 그룹핑 |
| "짝을 맞춘다 / 가장 최근 것부터 되돌린다" | 스택(stack) |
| "줄 세워 들어온 순서대로 처리" | 큐(queue) |
| "양쪽 끝에서 가운데로 좁혀온다" | 투 포인터 (두 인덱스 변수) |
| "경우를 나눠서" | if / elif / else |
| "표를 그려 칸을 채워나간다" | 2차원 배열 / DP 테이블 |
| "n번 똑같이 한다" | for i in range(n) |
| "더 작은 같은 문제로 쪼갠다" | 재귀 (recursion) |
Worked Example: 정렬된 배열에서 값 찾기
1) 사람 말로 + 작은 예제: "작은 수부터 정렬된 줄에서 target의 자리 번호를 찾아라. 없으면 -1." 예제: [2,5,8,12,19], target=12.
2) 손풀이 관찰: 사전을 찾듯 가운데를 펴고, target이 더 크면 왼쪽 절반을 통째로 버린다. 가운데(8) < 12 → 오른쪽만. 남은 가운데(12) = 12 → 3번. 두 번 만에.
3) 암묵지 명시화: "가운데" = (low+high)//2 (정수 나눗셈). "버린다" = 가운데는 이미 확인했으니 mid±1로 구간을 줄인다(중복 검사 방지).
4) 상태 → 변수: 내가 머릿속에 쥐고 있는 건 "아직 볼 구간의 양 끝" — 두 손가락. → 매핑표 "양쪽 끝에서 좁혀온다" → 두 인덱스 low, high.
5) 흐름 + 종료: "두 손가락이 안 엇갈리는 동안" → while low <= high. 엇갈리면(low > high) 구간이 비었다는 뜻 → 종료 후 -1.
6) 코드 + 트레이싱. 다리(low/high/mid 로직)는 세 언어에서 동일하다. 달라지는 건 문법 표면뿐:
# Python: 길이는 len(), 정수 나눗셈 //
def search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target: return mid
elif arr[mid] < target: low = mid + 1 # 답은 오른쪽
else: high = mid - 1 # 답은 왼쪽
return -1
// Java: 길이는 .length, mid는 오버플로 안전식
int search(int[] arr, int target) {
int low = 0, high = arr.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
/* C: 배열 길이를 인자로 따로 받아야 함 */
int search(int arr[], int n, int target) {
int low = 0, high = n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
번역의 교훈: 의미(가운데 비교 → 절반 버리기)는 셋 다 똑같다. 차이는 길이 얻는 법(len() / .length / 인자), 오버플로 주의(Java·C는 low+(high-low)/2), 타입 명시뿐이다. 다리를 한 번 놓으면 언어는 갈아끼우면 된다.
트레이싱(없는 값 target=7): low,high가 0,4 → 0,1 → 1,1 → 2,1로 가다 low>high라 종료 → -1. 손으로 돌려 확인.
스택·해시·DP로 옮기는 예제가 필요하면 worked-examples.md를 읽는다.
막힌 지점별 점검
| 신호 | 결과 / 고치는 법 |
|---|---|
| 문제를 읽자마자 손풀이 없이 코드부터 타이핑 | 경계·종료 조건 틀림. 1~2단계로 돌아가 작은 예제로 손풀이 먼저. |
| 암묵지를 그대로 둠 ("가운데를 본다") | mid 계산·정수 나눗셈·off-by-one 누락. 3단계로 규칙화. |
| 머릿속 상태를 변수로 안 뺌 | 기계는 기억 못 함. 매핑표로 변수/자료구조 도출(4단계). |
종료 조건 안 정함 / while 안에서 low·high(종료 변수)가 매번 바뀌는지 확인 안 함 |
무한 루프. "언제 멈추나"를 변수 조건으로 명시(5단계). |
| 예제 하나 손으로 안 돌려보고 "맞다"고 한다 | 경계 버그. "없는 값/맨 끝/빈 입력"을 표로 손 트레이싱(6단계). 트레이싱 전엔 미완성. |
| "대충 이진 탐색이네" 하고 외운 코드로 점프 | 문제가 살짝 다르면 깨짐. 먼저 이 문제 고유 손풀이로 구조 확인. |