코딩 테스트 준비 - 유형별 순서와 실패 기록
이 장에서 배우는 것
팀 프로젝트를 끝냈는데 짧은 코딩 테스트 문제 앞에서 손이 멈추는 일이 있다. 서비스에서는 화면과 API를 나누고 동료와 상의하면서 해결했지만, 코딩 테스트에서는 제한된 시간에 입력을 해석하고 풀이를 혼자 완성해야 하기 때문이다. 이미 만든 서비스가 있다는 사실은 출발점이 된다. 요구사항을 코드로 바꾸고 일정 안에 동작을 확인한 경험을 작은 문제 풀이에도 적용하면 된다.
앞 장에서 자신의 테이블을 바탕으로 SQL과 DB 개념을 확인했다면, 이번에는 풀이 기록을 바탕으로 연습 순서를 정한다. Python 문법 전체를 다시 공부하거나 하루 목표 문제 수를 늘리는 데서 시작하지 않는다. 어떤 유형에서 어디까지 혼자 해결했는지, 무엇 때문에 실패했는지를 남긴다. 이 장의 도구는 그 기록으로 유형별 정답률을 계산하고 다음 연습 유형 하나를 추천한다.
- 입출력·구현에서 출발해 정렬·탐색·그리디·BFS·DP로 연습 범위를 넓힌다.
- 실패를 정답 여부와 원인으로 나누고, 다시 풀 때 확인할 행동을 적는다.
- 코드패스의 단계별 학습과 문제집을 학습 목적에 맞게 나누어 활용한다.
- 풀이 기록을 읽는 Python 도구를 실행하고 추천 결과의 한계를 설명한다.
문제 상황
수료 후 지원할 공고를 살펴보니 코딩 테스트가 포함되어 있다. 팀 프로젝트에서 주문 목록과 검색 기능을 맡았던 독자는 배열과 반복문을 사용한 경험이 있다. 그런데 연습 문제에서는 입력 한 줄을 잘못 나누어 틀리고, 다음 문제에서는 마지막 원소를 빠뜨린다. 해설을 읽으면 이해되지만 다음 날 비슷한 문제를 만나면 다시 풀이가 떠오르지 않는다.
이때 기록이 “오늘 네 문제 풀이”뿐이면 다음 연습을 정하기 어렵다. 네 문제 모두 처음부터 해결했는지, 해설을 읽고 옮겼는지, 같은 경계값을 반복해서 놓쳤는지 구분되지 않기 때문이다. 문제 수는 활동량을 보여 주지만, 혼자 해결할 수 있는 범위를 그대로 보여 주지는 않는다.
제한 시간 뒤에 해설을 읽고 구현을 끝낸 것은 학습으로서 의미가 있다. 다만 그것을 독립적으로 해결한 정답과 섞으면 연습 방향이 흐려진다. 기록에서는 첫 시도의 결과를 유지하고, 해설을 덮은 뒤 다시 해결한 결과는 별도로 남겨야 한다. 실패 기록은 자신을 평가하는 문장이 아니라 다음 행동을 정하는 자료다.
이번 장에서는 파일 입력을 기다리지 않도록 예시 기록을 main.py 안에 둔다. 프로그램이 읽는 자료는 사람이 먼저 판정한 풀이 기록이다. 제출 사이트에 접속하거나 코드의 정답 여부를 자동 채점하는 프로그램은 아니다. 실행하면 바로 보고서를 출력하고 끝나므로 네트워크가 없는 환경에서도 같은 결과를 확인할 수 있다.
연습 순서는 풀이에 필요한 발판으로 정한다
이 장의 기본 순서는 입출력, 구현, 정렬, 탐색, 그리디, 너비 우선 탐색(BFS), 동적 계획법(DP)이다. 이 순서는 뒤의 유형이 언제나 더 어렵다는 뜻이 아니다. 입력을 읽고 상태를 바꾸는 코드에 익숙해진 다음, 데이터를 정리하고 찾으며, 선택의 근거와 상태 사이의 관계를 설명하도록 학습 부담을 나눈 것이다.
| 유형 | 먼저 할 일 | 다음으로 넘어갈 증거 |
|---|---|---|
| 입출력·구현 | 입력 단위와 상태 변경 순서를 적는다. | 최소 입력과 경계 입력을 직접 확인한다. |
| 정렬·탐색 | 정렬 기준과 검색 범위를 문장으로 적는다. | 중복 값과 찾는 값이 없는 경우를 설명한다. |
| 그리디 | 현재 선택이 이후 선택을 해치지 않는 이유를 찾는다. | 다른 선택이 더 나은 작은 반례를 점검한다. |
| BFS | 한 번의 이동과 방문 상태의 뜻을 정한다. | 같은 상태가 중복으로 대기열에 들어가는지 확인한다. |
| DP | 상태 하나의 의미와 이전 상태와의 관계를 적는다. | 초깃값부터 작은 입력의 표를 손으로 채운다. |
입출력과 구현에서 멈출 조건을 만든다
입출력 연습에서는 한 줄이 한 자료인지, 여러 줄을 합쳐야 하는지부터 구분한다. 구현 연습에서는 문제 문장의 조건을 실행 순서로 바꾼다. 예를 들어 대기 인원이 들어오고 나가는 기록을 처리한다면, 각 기록을 반영한 뒤 인원을 검사하는지 모든 기록을 반영한 뒤 검사하는지를 분명히 해야 한다. 반복문을 쓸 수 있는지보다 검사 시점을 정확히 옮길 수 있는지가 중요하다.
매번 큰 입력부터 만들 필요는 없다. 항목이 하나인 경우, 같은 값만 있는 경우, 조건의 기준값과 정확히 같은 경우를 먼저 적는다. 문제에서 허용하지 않는 입력을 무작정 추가하는 대신 허용 범위의 양 끝을 확인한다. 오류가 나면 “구현을 못한다”가 아니라 “기준값과 같은 경우를 빠뜨렸다”라고 기록한다.
정렬·탐색 뒤에는 선택과 상태를 설명한다
정렬에서는 오름차순인지 내림차순인지뿐 아니라 값이 같을 때 무엇을 먼저 두는지를 확인한다. 탐색은 순서대로 확인하는 방법부터 시작하고, 정렬된 자료에서 범위를 줄이는 방법으로 넓힌다. 범위를 줄이는 탐색에서는 시작과 끝을 포함하는지, 반복이 언제 끝나는지를 작은 예제로 추적한다.
그리디는 눈앞에서 좋아 보이는 선택을 반복하는 코드만으로 끝나지 않는다. 그 선택으로 바꾸어도 결과가 나빠지지 않는 이유를 설명해야 한다. 설명이 막히면 항목 두세 개로 반례를 만들어 본다. BFS에서는 이동 횟수와 방문 여부가 서로 어떤 역할을 하는지 구분한다. 간선마다 비용이 같은 상황의 최단 이동 횟수를 구할 때와 비용이 다른 상황을 같은 방식으로 처리하지 않는다.
DP에서는 배열부터 선언하지 않는다. “이 칸에 저장한 값이 무엇인가”를 한 문장으로 쓰고, 그 값을 이미 계산한 어떤 값에서 얻는지 적는다. 상태의 의미가 불분명한 채 점화식만 외우면 조건이 조금 바뀌었을 때 수정할 곳을 찾기 어렵다.
실패 기록은 다음에 바꿀 행동까지 담는다
풀이 노트에는 문제를 식별할 값, 대표 유형, 첫 시도 결과, 실패 원인, 다음 행동을 남긴다. 시간도 적으면 도움이 되지만 처음부터 항목을 많이 만들 필요는 없다. “어려웠다”는 감상만 남기지 않고, 코드나 풀이에서 관찰한 사실을 적는 것이 먼저다. 이전 직장에서 예외 상황을 분류하거나 작업 절차를 점검했던 경험도 이런 기록 습관에 활용할 수 있다.
이번 도구에서 정답은 미리 정한 연습 시간 안에 해설 없이 완성하고, 해당 연습의 채점에서 통과한 첫 시도를 뜻한다. 시간은 자신의 준비 상황에 맞게 정하되 같은 보고서 안에서는 기준을 유지한다. 중간에 해설을 보거나 시간을 넘겼다면 첫 시도는 실패로 남긴다. 그 뒤의 학습과 재풀이는 풀이 노트에 이어 적는다.
대표 유형은 한 문제에 하나만 붙인다. 여러 방법이 섞인 문제라면 이번에 확인하려던 핵심 행동을 기준으로 고른다. 같은 문제를 두 유형의 정답으로 중복 집계하면 실제보다 많은 자료를 확보한 것처럼 보이기 쉽다. 이 도구는 고유한 첫 시도만 입력받도록 문제 식별자의 중복도 검사한다.
| 관찰한 사실 | 원인 분류 | 다음 행동 |
|---|---|---|
| 기준값과 같은 입력에서 결과가 달라졌다. | 경계값 | 기준값 바로 아래·같음·바로 위를 적는다. |
| 찾는 값이 없을 때 반복이 끝나지 않았다. | 범위 설정 | 반복마다 시작점과 끝점을 표로 추적한다. |
| 같은 위치가 대기열에 여러 번 들어갔다. | 방문 처리 | 대기열에 넣는 시점의 방문 표시를 확인한다. |
| 저장한 값이 개수인지 비용인지 섞였다. | 상태 정의 | 상태의 뜻과 초깃값을 문장으로 다시 쓴다. |
한 번의 실패에 원인이 여러 개라면 다음 시도에서 먼저 고칠 원인 하나를 대표로 선택한다. 나머지는 풀이 노트에 적는다. 분류를 지나치게 세분화하면 같은 실수를 서로 다른 이름으로 기록하게 된다. 반대로 모든 실패를 “실수”로 묶으면 무엇을 연습해야 하는지 알 수 없다. 몇 번의 기록을 함께 읽고 분류 이름을 일정하게 유지한다.
완성 코드에는 일곱 유형마다 두 문제씩 기록한 예시가 들어 있다. 유형별 정답률은 정답 수를 시도 수로 나눈 뒤 100을 곱한다. 시도하지 않은 유형은 0%로 단정하지 않고 “미기록”으로 표시한다. 풀지 않은 상태와 풀었지만 실패한 상태는 다음 행동이 다르기 때문이다.
추천 규칙은 의도적으로 단순하다. 정해 둔 유형 순서에서 시도 수가 두 개 미만이거나 정답률이 70% 미만인 첫 유형을 고른다. 두 개와 70%는 이 도구의 연습용 기준이며 취업 가능성을 판정하는 수치가 아니다. 두 문제만으로 실력을 확정할 수도 없다. 추천을 받은 뒤에는 문제 난이도와 기록 시점, 실제 실패 원인을 사람이 함께 읽어야 한다.
이 규칙은 정답률이 가장 낮은 유형을 바로 고르지 않는다. 입출력과 구현에서 반복되는 오류를 먼저 고쳐 뒤의 유형에서도 같은 오류가 이어지는 일을 줄이려는 목적이다. 예시에서는 BFS와 DP의 정답률이 더 낮아도 구현을 추천한다. 필요하다면 이후 연습 문제에서 추천 규칙을 바꾸어 결과를 비교한다.
학습 자료와 이번 주의 증거를 연결한다
코드패스에서는 현재 확인할 유형의 단계별 학습으로 개념과 작은 예제를 점검한 뒤, 문제집에서 같은 유형의 다른 문제를 골라 독립 풀이를 한다. 단계별 학습을 읽은 직후에는 설명을 기억해서 풀 수 있으므로, 그 결과만으로 익숙해졌다고 판단하지 않는다. 다른 날 문제집의 문제를 해설 없이 풀어 보고 기록을 비교한다. 이 장은 특정 메뉴 이름이나 제공 문제 수를 전제로 하지 않는다.
서재의 《논리로 푸는 알고리즘》은 풀이를 떠올리지 못했을 때 선택의 근거나 반례를 설명하는 데 사용한다. 《개발자를 위한 컴퓨터 사이언스》와 《비전공자를 위한 컴퓨터공학 지도》는 대기열이나 탐색 비용처럼 자료구조의 의미가 흐릴 때 해당 주제를 찾아 읽는다. 책을 처음부터 다시 읽기보다 실패 원인과 연결되는 부분을 읽고 자신의 작은 예제로 확인한다.
《데이터베이스 개론》과 SQL 연구소는 SQL 연습 기록을 확인할 때 사용한다. 이번 프로그램의 일곱 유형에는 SQL을 넣지 않는다. SQL 문제와 일반 알고리즘 문제는 채점 대상과 실패 분류가 달라 별도 기록으로 관리하는 편이 읽기 쉽다. 《클린 코드 노하우》와 《코드 리뷰 실무》는 정답 이후 함수 이름과 조건 분기를 검토할 때 참고한다. 《개발자 실무 면접》은 풀이의 가정과 선택 이유를 말로 정리할 때 연결하되, 면접 답변 구성 자체는 여기서 확장하지 않는다.
| 확인 | 이번 주에 할 일 | 완료 증거 |
|---|---|---|
| □ | 첫날에 입출력·구현의 현재 풀이를 확인한다. | 해설 없이 시도한 코드와 직접 만든 경계 입력을 남긴다. |
| □ | 둘째 날에 코드패스 단계별 학습으로 막힌 개념을 확인한다. | 실패 원인 하나와 이를 확인한 작은 예제를 남긴다. |
| □ | 셋째·넷째 날에 문제집에서 같은 유형의 다른 문제를 푼다. | 문제 식별자, 대표 유형, 첫 시도 결과를 기록한다. |
| □ | 다섯째 날에 실패한 문제를 해설을 덮고 다시 푼다. | 첫 기록을 보존하고 달라진 풀이와 남은 막힘을 덧붙인다. |
| □ | 주말에 도구를 실행하고 다음 유형을 정한다. | 출력 보고서와 추천을 따르거나 조정한 이유 한 문장을 남긴다. |
이번 주의 완료 기준은 모든 유형을 끝내는 것이 아니다. 기록의 판정 기준을 정하고, 실패 원인 하나를 다음 행동으로 바꾸고, 보고서에 근거해 다음 연습을 고르면 된다. 앞의 유형이 안정되면 정렬부터 순서대로 범위를 넓힌다. 이미 익숙한 유형은 짧게 확인하고 넘어가되, 과거에 풀었던 기억만으로 통과 처리하지 않는다.
완성 코드
다음 내용을 main.py로 저장한다. Python 3.12 이상에서 표준 라이브러리만 사용한다. 예시 기록을 바꾸려면 RECORDS를 수정한다. 각 기록은 문제 식별자, 대표 유형, 첫 시도의 정답 여부, 실패 원인 순서다. 정답은 빈 원인을 사용하고 실패는 REASONS에 있는 원인 하나를 사용한다.
from collections import Counter
TYPES = ("입출력", "구현", "정렬", "탐색", "그리디", "BFS", "DP")
REASONS = (
"경계값", "조건 해석", "범위 설정",
"근거 부족", "방문 처리", "상태 정의",
)
MIN_ATTEMPTS = 2
TARGET_RATE = 70.0
RECORDS = [
("io-01", "입출력", True, ""),
("io-02", "입출력", True, ""),
("impl-01", "구현", True, ""),
("impl-02", "구현", False, "경계값"),
("sort-01", "정렬", True, ""),
("sort-02", "정렬", False, "조건 해석"),
("search-01", "탐색", False, "범위 설정"),
("search-02", "탐색", False, "경계값"),
("greedy-01", "그리디", False, "근거 부족"),
("greedy-02", "그리디", True, ""),
("bfs-01", "BFS", False, "방문 처리"),
("bfs-02", "BFS", False, "방문 처리"),
("dp-01", "DP", False, "상태 정의"),
("dp-02", "DP", False, "상태 정의"),
]
ACTIONS = {
"입출력": "입력 단위를 적고 최소 입력을 확인한다.",
"구현": "조건의 경계값 세 가지를 적고 다시 푼다.",
"정렬": "같은 값의 처리 순서를 적고 확인한다.",
"탐색": "검색 범위의 변화를 표로 추적한다.",
"그리디": "선택의 근거를 적고 작은 반례를 찾는다.",
"BFS": "대기열에 넣는 시점의 방문 표시를 확인한다.",
"DP": "상태의 뜻과 초깃값을 문장으로 적는다.",
}
def summarize(records):
stats = {kind: [0, 0] for kind in TYPES}
failures = Counter()
seen = set()
for problem_id, kind, solved, reason in records:
if not isinstance(problem_id, str) or not problem_id.strip():
raise ValueError("문제 식별자는 빈 문자열일 수 없다.")
if problem_id in seen:
raise ValueError(f"중복 문제 식별자: {problem_id}")
if kind not in TYPES:
raise ValueError(f"알 수 없는 유형: {kind}")
if type(solved) is not bool:
raise ValueError("정답 여부는 True 또는 False여야 한다.")
if solved and reason != "":
raise ValueError("정답 기록의 실패 원인은 비워 둔다.")
if not solved and reason not in REASONS:
raise ValueError(f"알 수 없는 실패 원인: {reason}")
seen.add(problem_id)
stats[kind][0] += 1
if solved:
stats[kind][1] += 1
else:
failures[reason] += 1
return stats, failures
def accuracy(attempts, correct):
if attempts == 0:
return None
return correct / attempts * 100
def recommend(stats):
for kind in TYPES:
attempts, correct = stats[kind]
rate = accuracy(attempts, correct)
if attempts < MIN_ATTEMPTS:
return kind
if rate is not None and rate < TARGET_RATE:
return kind
return None
def main():
stats, failures = summarize(RECORDS)
print("유형별 첫 시도 정답률")
for kind in TYPES:
attempts, correct = stats[kind]
rate = accuracy(attempts, correct)
if rate is None:
print(f"{kind}: 미기록")
else:
print(f"{kind}: {correct}/{attempts} ({rate:.1f}%)")
print("\n실패 원인")
if not failures:
print("기록된 실패 없음")
for reason in REASONS:
if failures[reason]:
print(f"{reason}: {failures[reason]}회")
next_kind = recommend(stats)
print("\n다음 연습")
if next_kind is None:
print("모든 유형이 기준을 충족했다. 섞어서 다시 확인한다.")
else:
print(f"추천 유형: {next_kind}")
print(f"할 일: {ACTIONS[next_kind]}")
if __name__ == "__main__":
main()
줄별 해설
from collections import Counter는 실패 원인별 횟수를 셀 도구를 가져온다. 외부 설치는 필요 없다. TYPES는 출력과 추천의 순서이며, REASONS는 허용할 실패 원인과 출력 순서다. 실패 횟수가 같은 항목의 순서를 따로 추측하지 않고 정해 둔 순서로 출력하므로 실행 결과가 일정하다.
MIN_ATTEMPTS와 TARGET_RATE는 추천 기준을 한곳에 모은 값이다. 추천 조건을 수정하려고 함수 안의 여러 숫자를 찾을 필요가 없다. RECORDS의 문자열은 이 장에서 만든 예시 식별자다. 실제 기록을 넣을 때는 자신이 다시 찾을 수 있는 고유한 이름을 사용한다.
ACTIONS는 추천 유형을 구체적인 행동으로 연결한다. 현재 코드는 유형별 기본 행동을 출력한다. 실패 원인까지 조합해 개별 처방을 만드는 기능은 없다. 따라서 추천이 구현이라고 나와도 실제 기록의 원인이 조건 해석이라면 풀이 노트를 보고 행동을 조정해야 한다.
summarize의 첫 줄은 모든 유형에 [시도 수, 정답 수]를 배정한다. 사전 컴프리헨션 안에서 매번 새 리스트를 만들기 때문에 한 유형의 값을 바꿔도 다른 유형의 값은 바뀌지 않는다. failures는 실패 횟수를, seen은 이미 처리한 문제 식별자를 보관한다.
for 문은 기록 하나를 네 변수로 풀어 받는다. 이 코드는 각 기록이 네 항목이라는 입력 형식을 전제로 한다. 이어지는 검사는 빈 식별자, 중복 식별자, 알 수 없는 유형, 잘못된 정답 표시와 실패 원인을 거른다. type(solved) is not bool은 숫자 1이나 문자열을 정답 표시로 받아들이지 않으려는 검사다.
검사를 통과한 뒤에만 식별자를 추가하고 집계를 바꾼다. stats[kind][0]은 정답과 실패 모두에서 증가한다. stats[kind][1]은 정답일 때만 증가한다. 실패이면 원인 횟수만 추가한다. 반환한 두 값은 보고서 출력과 다음 유형 추천에 각각 사용한다.
accuracy는 시도 수가 0일 때 None을 반환한다. 계산할 수 없는 정답률을 숫자 0과 구분하기 위한 선택이다. 기록이 있으면 나눗셈으로 백분율을 계산한다. 소수 첫째 자리로 보이는 형식은 이 함수가 아니라 출력 부분의 :.1f가 정한다.
recommend는 유형을 앞에서부터 확인한다. 기록이 부족하면 정답률과 관계없이 그 유형을 반환한다. 기록 수가 충분하면 계산값을 기준과 비교한다. 조건을 만족하는 첫 유형에서 함수가 끝나므로 뒤의 유형과 최저 정답률을 비교하지 않는다. 모든 유형을 통과하면 None을 반환한다.
main은 집계, 유형별 출력, 실패 원인 출력, 추천 출력 순서로 실행한다. 줄바꿈 문자 \n은 보고서 묶음 사이에 빈 줄을 만든다. 마지막 조건문은 이 파일을 직접 실행했을 때 main()을 호출한다. 입력 대기나 현재 시각에 의존하는 부분이 없어서 같은 기록에는 같은 출력이 나온다.
실행 결과
터미널에서 main.py가 있는 디렉터리로 이동한 뒤 다음 명령을 실행한다.
python3 main.py
예시 기록을 그대로 두면 다음과 같이 출력된다.
유형별 첫 시도 정답률
입출력: 2/2 (100.0%)
구현: 1/2 (50.0%)
정렬: 1/2 (50.0%)
탐색: 0/2 (0.0%)
그리디: 1/2 (50.0%)
BFS: 0/2 (0.0%)
DP: 0/2 (0.0%)
실패 원인
경계값: 2회
조건 해석: 1회
범위 설정: 1회
근거 부족: 1회
방문 처리: 2회
상태 정의: 2회
다음 연습
추천 유형: 구현
할 일: 조건의 경계값 세 가지를 적고 다시 푼다.
입출력은 두 번 시도해 두 번 통과했으므로 현재 기준을 충족한다. 그다음 구현은 시도 수는 충족하지만 정답률이 50%이므로 추천된다. 탐색·BFS·DP의 0%는 이번 두 번의 첫 시도 결과다. 해당 유형을 전혀 이해하지 못한다는 판정으로 해석하지 않는다. 실패 원인과 풀이 노트를 함께 읽어야 다음 연습의 범위를 정할 수 있다.
실무에서 자주 틀리는 것
문자열을 정답 여부로 사용한다
다음 코드는 실패라는 뜻으로 저장한 문자열도 참으로 판단한다. 비어 있지 않은 문자열은 조건문에서 참으로 취급되기 때문이다. 기록의 표기가 편하다는 이유로 자료의 의미를 흐리면 집계 전체가 달라진다.
틀린 코드는 다음과 같다.
solved = "False"
correct = 0
if solved:
correct += 1
고친 코드는 불리언 값을 사용한다. 완성 코드의 검사도 이 입력 규칙을 지키도록 돕는다.
solved = False
correct = 0
if solved:
correct += 1
미기록을 실패율과 섞거나 0으로 나눈다
시도 수가 없는 유형에서도 바로 나누면 예외가 발생한다. 예외를 피하려고 무조건 0%로 채우면 아직 시도하지 않았다는 정보가 사라진다.
틀린 코드는 다음과 같다.
attempts = 0
correct = 0
rate = correct / attempts * 100
고친 코드는 값이 없는 상태를 분리하고 출력에서 해석한다.
attempts = 0
correct = 0
rate = None if attempts == 0 else correct / attempts * 100
if rate is None:
print("미기록")
else:
print(f"{rate:.1f}%")
모든 유형이 같은 집계 리스트를 공유한다
같은 초기값을 간단히 넣으려고 dict.fromkeys에 리스트를 전달하면 각 키가 같은 리스트를 가리킨다. 구현의 시도 수를 올렸는데 다른 유형도 함께 증가할 수 있다.
틀린 코드는 다음과 같다.
stats = dict.fromkeys(TYPES, [0, 0])
stats["구현"][0] += 1
고친 코드는 유형마다 별개의 리스트를 만든다. 집계 도구에서는 초기화 방식도 결과의 신뢰성과 연결된다.
stats = {kind: [0, 0] for kind in TYPES}
stats["구현"][0] += 1
추천 기준의 경계값을 다르게 구현한다
설명은 70% 미만인데 코드에서 70% 이하를 검사하면 기준을 충족한 유형까지 다시 추천한다. 작은 차이지만 독자가 보고서를 해석하는 기준과 실제 동작이 어긋난다.
틀린 코드는 다음과 같다.
rate = 70.0
needs_practice = rate <= TARGET_RATE
고친 코드는 문장으로 정한 기준과 같은 비교를 사용한다. 이 경우 결과는 False다.
rate = 70.0
needs_practice = rate < TARGET_RATE
한눈에 보기
| 항목 | 이 장의 규칙 | 해석할 때 확인할 점 |
|---|---|---|
| 연습 순서 | 입출력 → 구현 → 정렬 → 탐색 → 그리디 → BFS → DP | 이미 익숙한 유형도 독립 풀이로 확인한다. |
| 정답 기록 | 정한 시간 안에 해설 없이 통과한 첫 시도 | 해설을 본 뒤의 완성과 구분한다. |
| 실패 기록 | 대표 원인 하나와 다음 행동을 남긴다. | 상세한 원인은 풀이 노트에 보충한다. |
| 정답률 | 정답 수 ÷ 시도 수 × 100 | 미기록은 0%와 구분한다. |
| 추천 | 기록 부족 또는 70% 미만인 첫 유형 | 학습용 규칙이며 채용 가능성의 판정이 아니다. |
| 완료 증거 | 첫 코드, 실패 원인, 재풀이, 실행 보고서 | 문제 수보다 바뀐 행동을 확인한다. |
보고서가 쌓이면 같은 원인이 반복되는지 먼저 본다. 구현과 탐색에서 모두 경계값 문제가 나타난다면 유형을 더 늘리기 전에 경계 입력을 만드는 습관을 고칠 수 있다. 반대로 원인이 줄어들고 새로운 문제에서도 풀이를 설명할 수 있다면 다음 유형으로 넓힐 근거가 된다. 이후 프로젝트를 다듬을 때도 작은 입력으로 동작을 확인하는 습관을 이어 갈 수 있다.
연습 문제
- 예시 기록에서 구현의 실패를 정답으로 바꾸고 실패 원인을 비운다. 다음 추천 유형과 그 이유를 실행 전에 적은 뒤 출력과 비교한다.
- RECORDS를 빈 리스트로 바꾼다. 유형별 표시, 실패 원인 표시, 추천 유형을 예상한다. 미기록을 0%로 출력하지 않는 이유도 적는다.
- 유형별 출력 뒤에 전체 시도 수와 전체 정답 수, 전체 정답률을 추가한다. 기존 예시 기록에서 예상할 값을 구하고, 빈 기록에서도 예외 없이 출력되도록 만든다.
- 추천 방식을 “기록이 두 개 미만인 유형을 먼저 찾고, 모두 충분하면 정답률이 가장 낮은 유형을 고른다”로 바꾼다. 최저 정답률이 기준 이상이면 추천하지 않고, 동률이면 TYPES의 앞 유형을 고른다. 기존 예시에서 결과가 어떻게 달라지는지 설명한다.
정답과 해설
-
구현이 2/2가 되어 기준을 충족하므로 다음 추천은 정렬이다. 바꿀 기록은 다음과 같다. 정답 여부만 바꾸고 원인을 남겨 두면 입력 검사에서 예외가 발생한다.
("impl-02", "구현", True, ""), -
일곱 유형 모두 “미기록”으로 출력된다. 실패 원인은 “기록된 실패 없음”이고 추천은 입출력이다. 첫 유형의 시도 수가 두 개 미만이기 때문이다. 아직 풀이를 관찰하지 않은 상태이므로 낮은 정답률로 해석하지 않는다. 실패가 없다는 출력 역시 모든 문제를 맞혔다는 뜻이 아니라 기록된 실패 항목이 없다는 뜻이다.
-
전체 시도 수는 14, 정답 수는 5, 정답률은 소수 첫째 자리로 35.7%다. 유형별 백분율을 단순 평균하는 대신 정답 수와 시도 수를 각각 합한다. 유형별 시도 수가 달라져도 전체 비율이 올바르게 계산된다. 다음 코드를 유형별 출력 반복문 다음에 넣는다.
total_attempts = sum(item[0] for item in stats.values()) total_correct = sum(item[1] for item in stats.values()) total_rate = accuracy(total_attempts, total_correct) if total_rate is None: print("전체: 미기록") else: print( f"전체: {total_correct}/{total_attempts} " f"({total_rate:.1f}%)" ) -
기존 예시에서는 모든 유형의 기록 수가 두 개이므로 최저 정답률을 비교한다. 탐색·BFS·DP가 모두 0%이고, 순서상 앞에 있는 탐색을 추천한다. 다음 함수로 기존 recommend를 교체할 수 있다.
def recommend(stats): for kind in TYPES: if stats[kind][0] < MIN_ATTEMPTS: return kind lowest_kind = None lowest_rate = TARGET_RATE for kind in TYPES: attempts, correct = stats[kind] rate = accuracy(attempts, correct) if rate is not None and rate < lowest_rate: lowest_kind = kind lowest_rate = rate return lowest_kind비교에 작은 경우만 사용하므로 같은 정답률의 뒤 유형은 앞의 선택을 덮어쓰지 않는다. 이 방식은 관찰된 약점을 먼저 고르는 데 적합하지만, 앞 유형의 반복 실수를 우선 고치려는 원래 목적과는 다르다. 두 방식 중 어느 것을 쓰든 추천을 따른 이유와 실제 연습 뒤 달라진 점을 함께 남겨야 기록이 다음 판단에 쓰인다.