전공자와 무엇이 다른가 - 과목 지도와 메울 순서
이 장에서 배우는 것
기능은 만들 수 있는데, 기능이 느려지거나 여러 요청이 겹치면 설명이 막히는 순간이 있다. 검색으로 문제를 해결했더라도 왜 그 방법이 통했는지 말하기 어려울 수 있다. 이런 경험은 특정 전공 출신에게만 생기는 일이 아니다. 배운 내용을 연결하고, 설명을 검증할 기회가 충분했는지에 따라 누구에게나 생긴다.
컴퓨터공학 교과 과정은 그 연결에 이름을 붙인 지도다. 전공자는 여러 학기에 걸쳐 과목 이름과 설명 틀을 접할 기회를 얻는다. 다른 전공이나 직업 교육을 거쳐 개발을 배운 사람은 실제 기능을 먼저 만들며 같은 문제를 만날 수 있다. 두 경로의 차이는 개인의 능력을 판정하는 기준이 아니다. 지금 가진 경험을 어떤 과목의 언어로 설명할 수 있는지 확인하는 출발점이다.
이 장에서는 과목을 모두 끝내야 개발을 시작할 수 있다고 말하지 않는다. 이미 해 본 일을 지도 위에 올리고, 아직 설명하기 어려운 부분을 찾아 다음 학습을 정한다. 자가 진단 프로그램도 합격과 불합격을 가르는 시험이 아니라, 공부 순서를 고치기 위한 도구로 만든다.
- 수학·프로그래밍·시스템·이론·응용이라는 다섯 줄기로 교과 과정을 읽는다.
- 실무에서 설명이 막힌 상황을 관련 과목과 연결한다.
- 자가 진단에서 기억과 설명, 확인 가능한 근거를 구분한다.
- 과목별 점수와 선수 관계를 함께 사용해 학습 순서를 계산한다.
- 작은 실험으로 추천 규칙의 한계와 쓰임을 확인한다.
문제 상황
다른 전공을 공부하다 개발 일을 시작한 지우가 학습 계획 서비스의 기능을 맡았다고 하자. 사용자는 부족한 과목을 선택하고, 서비스는 공부할 순서를 보여 준다. 처음에는 선택한 과목을 화면에 나열하면 충분했다. 그런데 이용자가 늘고 요구가 구체화되자 서로 다른 질문이 들어온다.
“이 과목보다 먼저 알아야 하는 내용이 있지 않은가”, “과목 수가 늘면 계산이 얼마나 더 걸리는가”, “같은 사람이 두 번 저장하면 어느 결과가 남는가”, “다른 사람의 진단 결과를 읽을 수는 없는가” 같은 질문이다. 화면과 입력 처리를 잘 만드는 경험만으로는 이 질문들을 한꺼번에 설명하기 어렵다. 각각 다른 관점이 필요하기 때문이다.
먼저 배울 관계를 표현하는 일은 이산수학과 알고리즘에 연결된다. 자료를 담는 방식과 처리량의 관계는 자료구조에 연결된다. 함께 실행되는 작업은 운영체제와 데이터베이스에, 요청의 이동은 네트워크에, 접근 범위는 보안에 연결된다. 사용자의 요구를 확인하고 변경을 검증하는 과정은 소프트웨어 공학에 연결된다.
여기서 필요한 것은 모든 과목의 정의를 외워 말하는 능력이 아니다. 문제가 생겼을 때 어느 서랍을 열어야 할지 아는 능력이다. 이 장의 프로그램은 지우가 자신의 과목별 상태를 기록하고, 부족한 점과 먼저 살펴볼 관계를 함께 고려하도록 돕는다. 실제 서비스나 저장 기능을 구현하지 않고, 그 결정을 만드는 작은 부분에 집중한다.
교과 과정을 다섯 줄기로 읽는다
교과 과정은 교육 내용을 목적과 순서에 따라 묶은 것이다. 아래 다섯 줄기는 이해를 위한 분류이며, 모든 교육 과정에 같은 경계가 있다는 뜻은 아니다. 한 과목이 여러 줄기에 걸치기도 한다. 예를 들어 프로그래밍 언어는 코드를 쓰는 방법과 실행 원리를 함께 다룬다.
| 줄기 | 대표 과목 | 실무의 질문 | 핵심 관점 |
|---|---|---|---|
| 수학 | 이산수학 | 조건과 관계를 정확히 표현했는가 | 논리, 집합, 관계 |
| 프로그래밍 | 자료구조, 프로그래밍 언어 | 자료와 코드를 어떻게 표현할까 | 표현, 연산, 실행 의미 |
| 시스템 | 컴퓨터 구조, 운영체제, 네트워크 | 실행 자원과 통신은 어떻게 움직이는가 | 실행 장치, 자원, 연결 |
| 이론 | 알고리즘 | 답을 구하는 절차와 비용은 적절한가 | 정확성, 처리량, 한계 |
| 응용 | 데이터베이스, 소프트웨어 공학, 보안 | 자료와 서비스를 어떻게 믿고 운영할까 | 저장, 변경, 보호 |
서로 떨어진 대상을 세고 그 관계를 다루는 수학을 전공 수업에서는 이산수학이라 부른다. 이 장에서는 과목 이름의 모음과 과목 사이의 “먼저 살펴본다”라는 관계만 사용한다. 조건을 정확히 쓰고 관계를 자료로 표현하는 일이 이미 그 줄기의 작은 활용이다.
자료를 담고 다루는 방식을 자료구조라 부르고, 문제를 푸는 유한한 절차를 알고리즘이라 부른다. 아래 프로그램에서 과목들을 순서대로 담는 목록과 이름으로 응답을 찾는 사전은 자료구조의 예다. 배울 수 있는 후보를 골라 하나씩 결과에 넣는 절차는 알고리즘의 예다. 지금은 내부 구현이나 성능 분석까지 확장하지 않는다.
시스템은 프로그램이 실행되는 장치와 실행을 돕는 환경을 함께 바라보는 말이다. 컴퓨터 구조는 실행 장치의 구성을, 운영체제는 실행 중인 프로그램에 자원을 나누는 방식을, 네트워크는 장치 사이에 자료가 오가는 방식을 다룬다. 응용 줄기는 이런 기반 위에서 저장과 변경, 보호라는 목적을 달성하는 쪽에 초점을 둔다.
지도에는 생략도 있다. 수학에는 확률과 선형대수 같은 분야가 더 있고, 응용에는 화면 상호작용이나 영상 처리 같은 분야도 있다. 이 책의 지도는 개발자가 자주 마주치는 설명의 빈틈을 찾기 위해 범위를 정한 것이다. 아래 코드의 과목 목록도 이 범위만 표현한다. 목록에 없는 과목이 중요하지 않다는 뜻은 아니다.
자가 진단은 기억보다 근거를 묻는다
자가 진단은 자신이 답하고 자신이 해석하는 점검이다. “들어 본 적이 있다”와 “다른 사람에게 설명할 수 있다”를 같은 응답으로 처리하면 다음 행동을 정하기 어렵다. 여기서는 각 문항에 0, 1, 2 중 하나를 기록한다. 0은 아직 설명하기 어렵다는 뜻이고, 1은 예시나 자료를 보며 설명할 수 있다는 뜻이며, 2는 자료 없이 설명하고 작은 예로 확인할 수 있다는 뜻이다.
2를 고를 때는 근거를 남긴다. 근거는 직접 실행한 짧은 코드, 실패를 재현한 기록, 말로 설명한 내용을 정리한 메모가 될 수 있다. 프로그램은 그 근거의 품질을 검사하지 않는다. 같은 점수라도 근거가 다를 수 있으므로 숫자만 다른 사람과 비교하지 않는다.
| 과목 | 첫째 문항 | 둘째 문항 | 남길 근거의 예 |
|---|---|---|---|
| 이산수학 | 조건 두 개를 함께 만족한다는 뜻을 설명한다 | 과목 사이의 선행 관계를 표현한다 | 조건표와 관계 목록 |
| 자료구조 | 목록과 이름별 조회의 용도를 구분한다 | 자료를 담는 방식을 고른 이유를 설명한다 | 같은 자료의 두 표현 |
| 알고리즘 | 문제 해결 절차를 빠짐없이 적는다 | 입력이 커질 때 작업 증가를 설명한다 | 작은 입력의 손 추적 |
| 컴퓨터 구조 | 계산 장치와 주기억장치의 역할을 구분한다 | 자료를 어디서 읽는지가 속도에 영향을 주는 이유를 말한다 | 실행 장치 구성도 |
| 운영체제 | 실행 중인 작업과 실행 흐름을 구분한다 | 공유 자료를 함께 바꿀 때의 문제를 설명한다 | 두 작업의 순서 기록 |
| 네트워크 | 요청과 응답의 흐름을 설명한다 | 응답이 늦을 때 확인할 구간을 나눈다 | 요청 이동 경로 |
| 데이터베이스 | 자료 사이의 관계를 표로 표현한다 | 함께 성공해야 하는 변경을 찾는다 | 저장 전후의 표 |
| 프로그래밍 언어 | 이름과 값의 관계를 설명한다 | 코드가 실행 결과에 이르는 단계를 구분한다 | 짧은 코드의 실행 설명 |
| 소프트웨어 공학 | 요구를 확인 가능한 조건으로 쓴다 | 변경 뒤 확인할 사례를 고른다 | 요구와 확인 사례의 쌍 |
| 보안 | 사용자 확인과 허용 범위를 구분한다 | 외부 입력을 그대로 믿을 때의 문제를 찾는다 | 허용과 거절 사례 |
예를 들어 “실행 중인 작업”은 전공 수업에서 프로세스라 부르는 개념으로 이어지고, 그 안의 “실행 흐름”은 스레드라는 이름으로 이어진다. “함께 성공해야 하는 변경”은 데이터베이스 수업에서 트랜잭션이라는 단위로 다룬다. 지금은 이름과 질문의 연결만 알아 둔다. 구체적인 동작은 해당 과목을 살펴볼 때 확인한다.
과목별로 두 응답을 더하면 최솟값은 0이고 최댓값은 4다. 이를 읽기 편하게 0부터 100까지의 값으로 바꾼다. 계산식은 점수 = 응답 합계 × 100 ÷ 4다. 응답이 정수 두 개이므로 결과는 0, 25, 50, 75, 100 중 하나다. 이 점수는 이 장에서 정한 설명 준비도이며, 학문 전체의 이해도나 채용 가능성을 뜻하지 않는다.
아래 완성 코드에는 표의 과목마다 두 응답이 들어 있다. 독자는 자신의 근거를 확인한 뒤 그 숫자를 바꾼다. 실행 중 입력을 기다리지 않고 응답 자료를 함수에 전달하므로, 같은 자료로 다시 실행하면 같은 결과가 나온다.
부족한 과목과 먼저 볼 과목을 함께 고른다
점수가 낮은 순서로만 정렬하면 컴퓨터 구조의 질문을 아직 설명하기 어려운데 운영체제를 먼저 깊게 파고들 수 있다. 반대로 모든 기초 과목을 끝내겠다고 정하면 당장 맡은 업무와의 연결이 멀어질 수 있다. 여기서는 간단한 절충 규칙을 사용한다. 먼저 볼 과목이 결과에 들어간 후보 중에서 점수가 가장 낮은 것을 고른다.
어떤 내용을 이해하기 전에 도움이 되는 지식을 전공 수업에서는 선수 지식이라 부른다. 코드의 선수 관계는 이 책에서 제안하는 탐색 순서다. 수강 자격이나 학습 금지 조건이 아니다. 컴퓨터 구조를 추천 목록에 넣었다고 그 과목을 다 이해했다는 뜻도 아니다. 프로그램은 추천 목록을 구성할 뿐, 실제 학습 완료를 추적하지 않는다.
알고리즘 앞에는 이산수학과 자료구조를, 운영체제와 프로그래밍 언어 앞에는 컴퓨터 구조를 둔다. 보안 앞에는 네트워크와 데이터베이스를 둔다. 이는 주제의 연결을 드러내는 최소한의 관계이며, 해당 과목에 필요한 지식을 모두 열거한 목록은 아니다.
과목을 점으로, 먼저 살펴볼 관계를 화살표로 표현한 것을 전공 수업에서는 방향 그래프라고 부른다. 화살표가 요구하는 앞뒤 관계를 지키며 과목을 나열하는 방식은 위상 정렬이라는 이름으로 이어진다. 여기서는 그 방식에 낮은 점수 우선 규칙을 더한다. 같은 점수라면 코드에 적힌 과목 순서를 따른다.
선수 관계가 서로를 기다리는 고리를 만들면 순서를 만들 수 없다. 예를 들어 이산수학은 알고리즘을 기다리고 알고리즘은 이산수학을 기다리도록 적으면 어느 쪽도 먼저 고를 수 없다. 코드는 이 상태를 발견하면 잘못된 관계를 알린다. 조용히 일부 결과만 보여 주는 것보다 원인을 확인하기 쉽다.
작은 실험도 포함한다. 실제 응답으로 만든 추천 목록과 모든 응답을 2로 바꾼 목록을 비교한다. 모든 점수가 같아지면 낮은 점수 우선 규칙의 차이가 사라지고, 선수 관계와 과목의 기본 순서가 선택을 결정한다. 이 실험은 추천이 사람의 본질을 판정하는 것이 아니라, 주어진 자료와 규칙을 계산한 결과임을 보여 준다.
완성 코드
다음 내용을 main.py에 저장한다. 응답 사전의 각 쌍은 자가 진단표의 첫째 문항과 둘째 문항에 대응한다. 표의 문항은 그대로 두고 자신의 응답 숫자만 바꾸면 된다. 외부 파일이나 네트워크, 추가 패키지는 사용하지 않는다.
SUBJECTS = (
"이산수학", "자료구조", "알고리즘", "컴퓨터 구조", "운영체제",
"네트워크", "데이터베이스", "프로그래밍 언어", "소프트웨어 공학", "보안",
)
PREREQUISITES = {
"알고리즘": ("이산수학", "자료구조"),
"운영체제": ("컴퓨터 구조",),
"프로그래밍 언어": ("컴퓨터 구조",),
"보안": ("네트워크", "데이터베이스"),
}
RESPONSES = {
"이산수학": (1, 0),
"자료구조": (2, 1),
"알고리즘": (0, 1),
"컴퓨터 구조": (0, 0),
"운영체제": (1, 0),
"네트워크": (2, 0),
"데이터베이스": (1, 1),
"프로그래밍 언어": (1, 0),
"소프트웨어 공학": (2, 1),
"보안": (0, 1),
}
def calculate_scores(responses):
if set(responses) != set(SUBJECTS):
raise ValueError("응답 과목을 확인한다.")
scores = {}
for subject in SUBJECTS:
answers = responses[subject]
if len(answers) != 2:
raise ValueError(f"{subject}: 응답은 두 개여야 한다.")
if any(type(value) is not int or value not in (0, 1, 2)
for value in answers):
raise ValueError(f"{subject}: 응답은 정수 0, 1, 2만 허용한다.")
scores[subject] = sum(answers) * 100 // 4
return scores
def recommend(scores):
remaining = list(SUBJECTS)
ordered = []
positions = {subject: index for index, subject in enumerate(SUBJECTS)}
while remaining:
candidates = [
subject for subject in remaining
if all(previous in ordered
for previous in PREREQUISITES.get(subject, ()))
]
if not candidates:
raise ValueError("선수 관계에 고리가 있거나 과목 이름이 잘못됐다.")
chosen = min(candidates, key=lambda subject: (
scores[subject], positions[subject]
))
ordered.append(chosen)
remaining.remove(chosen)
return ordered
def main():
scores = calculate_scores(RESPONSES)
ordered = recommend(scores)
print("자가 진단 점수: 설명 준비도")
for subject in SUBJECTS:
print(f"{subject}: {scores[subject]}점")
print("\n추천 학습 순서")
for number, subject in enumerate(ordered, start=1):
print(f"{number}. {subject} ({scores[subject]}점)")
full_responses = {subject: (2, 2) for subject in SUBJECTS}
full_scores = calculate_scores(full_responses)
full_order = recommend(full_scores)
print("\n작은 실험: 모든 응답이 2일 때")
print(" → ".join(full_order))
if __name__ == "__main__":
main()
줄별 해설
SUBJECTS는 과목 이름을 정해진 순서로 담는다. 괄호로 묶은 이 자료는 생성한 뒤 항목을 바꾸지 않는 튜플이다. Python 기초 문법의 목록과 비슷하게 순회할 수 있다. 이 순서는 점수 출력 순서이면서, 같은 점수의 후보 중 무엇을 먼저 고를지 정하는 기준이다.
PREREQUISITES는 과목 이름을 키로 쓰는 사전이다. 값에는 먼저 살펴볼 과목들을 넣는다. 항목을 적지 않은 과목은 이 예제에서 선행 조건이 없는 것으로 취급한다. 과목의 본질적인 난도를 숫자로 매기거나, 모든 실제 교육 과정의 순서를 대신하는 자료가 아니다.
RESPONSES는 프로그램에 전달할 응답 자료다. 계산 함수가 이 사전을 매개변수로 받도록 해서 점수 계산과 예시 자료를 분리했다. 같은 함수에 다른 응답 사전을 전달하면 다른 사람이나 다른 시점의 점수를 계산할 수 있다. 실행 중 질문을 띄우지 않아 명령을 실행하면 바로 결과를 출력하고 끝난다.
calculate_scores의 첫 조건은 응답의 과목 이름이 정확한지 확인한다. set은 중복 없이 원소의 포함 여부를 다루는 집합 자료형이다. 이 비교에서는 순서가 아니라 이름의 구성만 중요하다. 빠진 과목과 덧붙인 과목이 있으면 잘못된 입력을 알리는 ValueError 예외를 발생시킨다.
반복문 안에서는 과목마다 응답이 두 개인지 확인한다. any는 조건을 만족하는 항목이 하나라도 있는지 판단한다. 정수인지와 허용 범위에 있는지를 함께 검사해 3이나 문자열 같은 값을 계산에 넣지 않는다. Python에서는 참과 거짓도 정수처럼 계산될 수 있으므로, 여기서는 type(value) is int로 응답용 정수를 명확히 한정했다.
sum(answers) * 100 // 4는 두 응답을 합쳐 점수로 바꾼다. //는 나눈 결과의 내림값을 구하는 연산이다. 이 프로그램에서는 가능한 분자가 모두 4로 나누어떨어져 소수 부분을 잃지 않는다. 문항 수나 응답 범위를 바꾸면 분모도 다시 정해야 한다.
recommend의 remaining에는 아직 추천하지 않은 과목을, ordered에는 추천한 과목을 담는다. positions는 각 과목이 기본 순서에서 몇 번째인지 기록한다. enumerate는 순회하는 항목에 번호를 붙인다. 기본 번호는 0부터 시작하지만 여기서는 상대적인 앞뒤만 필요하다.
while remaining은 남은 과목이 있는 동안 선택을 반복한다. candidates를 만드는 대괄호 안의 표현은 조건을 만족하는 항목으로 새 목록을 만드는 목록 내포다. all은 선수 과목이 모두 결과에 들어갔는지 확인한다. 선수 과목이 없는 빈 묶음에 대해서도 참이므로, 조건 없는 과목은 곧바로 후보가 된다.
get(subject, ())는 사전에 과목이 없으면 빈 튜플을 돌려준다. 후보가 하나도 없는데 남은 과목이 있다면 더 진행할 수 없으므로 예외를 발생시킨다. 고리뿐 아니라 존재하지 않는 선수 과목 이름을 적은 경우에도 같은 검사에 걸린다.
min은 기준값이 가장 작은 후보를 고른다. lambda는 이 자리에서 사용할 짧은 함수를 만드는 문법이다. 기준값은 점수와 기본 위치를 묶은 쌍이다. Python은 쌍의 앞값을 먼저 비교하고, 같을 때 뒷값을 비교한다. 따라서 낮은 점수가 우선이고, 동점이면 기본 위치가 빠른 과목이 우선이다.
append와 remove는 선택한 과목을 결과에 넣고 남은 목록에서 뺀다. 이 이동이 반복되며 같은 과목을 두 번 추천하지 않는다. 과목이 열 개뿐인 지도이므로 이해하기 쉬운 목록 연산을 사용했다. 큰 자료에서의 처리 비용을 비교하는 일은 별도의 자료구조와 알고리즘 질문으로 남겨 둔다.
main은 계산과 출력을 연결한다. 마지막 실험은 모든 과목에 (2, 2)를 넣은 새 사전을 만들어 실제 응답을 보존한다. join은 추천 목록을 화살표가 들어간 문자열로 연결한다. 맨 아래 조건은 이 파일을 직접 실행했을 때 main을 호출한다.
실행 결과
macOS나 Linux의 터미널에서 파일이 있는 위치로 이동해 다음 명령을 실행한다. 아래 출력은 응답 자료를 바꾸지 않았을 때의 결과다.
python3 main.py
자가 진단 점수: 설명 준비도
이산수학: 25점
자료구조: 75점
알고리즘: 25점
컴퓨터 구조: 0점
운영체제: 25점
네트워크: 50점
데이터베이스: 50점
프로그래밍 언어: 25점
소프트웨어 공학: 75점
보안: 25점
추천 학습 순서
1. 컴퓨터 구조 (0점)
2. 이산수학 (25점)
3. 운영체제 (25점)
4. 프로그래밍 언어 (25점)
5. 네트워크 (50점)
6. 데이터베이스 (50점)
7. 보안 (25점)
8. 자료구조 (75점)
9. 알고리즘 (25점)
10. 소프트웨어 공학 (75점)
작은 실험: 모든 응답이 2일 때
이산수학 → 자료구조 → 알고리즘 → 컴퓨터 구조 → 운영체제 → 네트워크 → 데이터베이스 → 프로그래밍 언어 → 소프트웨어 공학 → 보안
보안은 25점이지만 50점인 네트워크와 데이터베이스 뒤에 온다. 알고리즘도 25점이지만 자료구조 뒤에 온다. 이것이 점수 정렬과 선수 관계를 고려한 추천의 차이다. 목록 전체가 점수 오름차순이어야 한다고 기대하면 이 결과를 오류로 읽게 된다.
모든 응답이 2인 실험에서는 모든 과목이 100점이다. 선택할 수 있는 후보 중 기본 순서가 가장 빠른 과목이 선택된다. 이 결과는 높은 점수일 때 학습할 필요가 없다는 판정이 아니다. 프로그램은 점수와 관계에 따라 전체 과목의 탐색 순서를 제시하도록 만들어졌다.
실제 계획에는 당장 맡은 업무도 반영해야 한다. 데이터 저장 문제를 해결 중이라면 데이터베이스의 해당 질문부터 확인할 수 있다. 추천 목록은 공부를 시작할 이유를 제공하며, 업무상의 긴급도나 학습 시간까지 계산하지 않는다.
실무에서 자주 틀리는 것
낮은 점수만 정렬하고 선수 관계를 잊는다
다음 코드는 부족한 정도만 보여 주고 싶을 때는 쓸 수 있다. 그러나 먼저 살펴볼 관계를 지키는 학습 순서로 제시하면 설명과 동작이 어긋난다. 아래 조각들은 완성 코드의 함수와 자료가 있는 상태에서 비교한다.
# 학습 순서라는 목적에 맞지 않는 코드
scores = calculate_scores(RESPONSES)
order = sorted(SUBJECTS, key=lambda subject: scores[subject])
print(order)
# 관계를 함께 반영한 코드
order = recommend(scores)
print(order)
정렬 자체가 잘못된 것은 아니다. “부족한 정도의 순위”와 “선수 관계를 지키는 탐색 순서”는 다른 출력이다. 이름을 붙이기 전에 무엇을 계산하는지 먼저 정해야 한다.
문항을 늘리고 계산 기준은 그대로 둔다
문항 세 개를 두 문항용 분모로 계산하면 모든 응답이 2일 때 150점이 된다. 아래 고친 코드는 문항마다 0부터 2까지 답한다는 조건에서 문항 수를 분모에 반영한다.
answers = (2, 2, 2)
# 잘못된 코드: 두 문항용 기준을 그대로 사용한다
score = sum(answers) * 100 // 4
print(score) # 150
# 고친 코드: 비어 있지 않은 응답의 최대 합계를 계산한다
if not answers:
raise ValueError("응답이 하나 이상 필요하다.")
maximum = len(answers) * 2
score = sum(answers) * 100 // maximum
print(score) # 100
완성 프로그램에 문항을 추가하려면 응답 개수 검사와 진단표도 함께 바꿔야 한다. 계산식만 바꾸고 안내 문구를 남겨 두면 독자가 서로 다른 기준으로 응답하게 된다. 문항 수가 달라지면 정수 나눗셈으로 소수 부분이 내려갈 수 있다는 점도 표시해야 한다.
같은 점수의 순서를 우연에 맡긴다
집합은 학습 순서를 표현하는 자료가 아니다. 다음 코드는 동점일 때 무엇을 먼저 고를지 규칙에 적지 않는다. 집합 순회 순서는 실행 환경에 따라 달라질 수 있으므로, 같은 응답에서 같은 결과를 얻으려는 요구에 맞지 않는다.
scores = calculate_scores(RESPONSES)
# 잘못된 코드: 동점 규칙이 없다
candidates = set(SUBJECTS)
chosen = min(candidates, key=lambda subject: scores[subject])
# 고친 코드: 점수 다음에 명시적인 기본 위치를 비교한다
positions = {subject: index for index, subject in enumerate(SUBJECTS)}
chosen = min(candidates, key=lambda subject: (
scores[subject], positions[subject]
))
print(chosen) # 컴퓨터 구조
이 조각은 동점 처리만 비교한다. 완성 코드에서는 먼저 선수 관계를 만족하는 후보를 만든다. 순서가 의미 있는 결과에서는 무엇으로 동점을 푸는지도 결과를 만드는 규칙에 포함해야 한다.
점수를 학습 완료라는 말로 바꾼다
두 문항에서 100점이 나와도 과목 전체를 확인한 것은 아니다. 응답 자료가 담은 범위를 넘어서는 문구는 다음 행동을 흐리게 만든다.
score = 100
# 잘못된 코드: 응답 범위를 넘어선 결론이다
print("과목 학습 완료")
# 고친 코드: 실제로 계산한 범위를 표시한다
print(f"두 문항의 설명 준비도: {score}점")
화면 문구도 계산의 일부다. 측정한 것이 두 질문에 대한 자기 응답이라면 결과 설명에도 그 범위를 남겨야 한다. 점수를 높이는 일보다 근거를 다시 확인하는 일이 우선이다.
한눈에 보기
| 할 일 | 판단 기준 | 코드의 위치 | 해석의 한계 |
|---|---|---|---|
| 과목을 찾는다 | 설명이 막힌 질문이 무엇인가 | SUBJECTS | 모든 전공 과목을 담지는 않는다 |
| 현재 상태를 적는다 | 자료 없이 설명하고 확인할 수 있는가 | RESPONSES | 근거의 품질은 사람이 살핀다 |
| 점수를 계산한다 | 두 응답의 합계를 100점 기준으로 바꾼다 | calculate_scores | 학문 전체의 이해도가 아니다 |
| 후보를 추린다 | 선수 과목이 추천 목록에 들어갔는가 | PREREQUISITES와 all | 실제 학습 완료를 뜻하지 않는다 |
| 다음 과목을 고른다 | 낮은 점수, 동점이면 기본 위치 | recommend와 min | 업무 긴급도는 반영하지 않는다 |
각 과목을 더 볼 때는 네 질문을 반복하면 된다. 실무에서 어디에 쓰이는가, 꼭 알아야 할 핵심 세 가지에서 다섯 가지는 무엇인가, 면접에서는 어떤 설명을 요구하는가, 더 깊게 읽을 책은 어떤 질문을 해결해 주는가다. 면접 질문도 용어 암기보다 선택 이유와 확인 방법을 중심으로 읽는다. 예를 들어 이 장에서는 “점수가 같은 과목의 순서를 어떻게 정했는가”와 “선수 관계가 서로를 기다리면 어떻게 되는가”를 설명할 수 있다.
서재의 책을 고를 때도 이 네 질문을 기준으로 목차를 본다. 현재 과목을 이해할 기본서는 정의와 짧은 예를 제공하는지, 더 깊이 읽을 책은 구현이나 설계의 이유를 설명하는지 확인한다. 이 장에서는 과목 이름이 아직 낯선 상태에서 책 목록부터 늘리기보다, 자신의 진단 근거 한 개와 연결할 질문 한 개를 먼저 정한다.
다음 장에서는 이산수학을 통해 조건과 관계를 더 정확히 읽는 방법을 살펴본다. 지금 작성한 점수는 보관할 수 있지만 고정된 평가표로 삼지는 않는다. 공부한 뒤 같은 질문에 다시 답하고, 이전에는 남기지 못했던 근거를 추가하면 지도가 조금씩 구체화된다.
연습 문제
- 이산수학의 응답을
(2, 2)로 바꾸면 점수는 얼마가 되는가. 첫 번째 추천 과목도 바뀌는지 실행 전에 예상하고, 실행해서 확인하라. - 새로운 함수 없이 완성 코드의 함수를 사용해 모든 과목의 응답이
(0, 0)인 실험을 만들어라. 모든 응답이(2, 2)인 실험과 추천 순서가 같은 이유를 설명하라. - 응답 하나를
(3, 0)으로 바꾸면 어떤 일이 일어나는가. 점수를 계산하기 전에 이 입력을 거절하는 이유를 설명하라. - 원래 응답 자료로 돌아간 뒤 이산수학의 선수 과목을 알고리즘으로 추가하라. 기존 관계와 어떤 고리가 생기는지 쓰고, 프로그램이 이를 어떻게 알리는지 확인하라.
정답과 해설
첫째 문제에서 이산수학은 100점이다. 첫 추천은 여전히 0점인 컴퓨터 구조다. 이산수학의 점수 변화는 뒤의 후보 선택에 영향을 주지만, 현재 선택 가능한 과목 중 최저 점수인 컴퓨터 구조의 우선순위를 바꾸지는 않는다. 확인 뒤에는 원래 응답으로 돌려놓는다.
changed = dict(RESPONSES)
changed["이산수학"] = (2, 2)
scores = calculate_scores(changed)
print(scores["이산수학"]) # 100
print(recommend(scores)[0]) # 컴퓨터 구조
둘째 문제는 다음처럼 확인한다. 모든 점수가 0이든 100이든 후보 사이에는 점수 차이가 없다. 따라서 선수 관계와 기본 위치가 같으면 추천 순서도 같다. 절대 점수는 다르지만 이 규칙이 선택에 사용하는 상대적인 점수 차이는 두 실험 모두 없다.
zero_responses = {subject: (0, 0) for subject in SUBJECTS}
zero_order = recommend(calculate_scores(zero_responses))
full_responses = {subject: (2, 2) for subject in SUBJECTS}
full_order = recommend(calculate_scores(full_responses))
print(zero_order == full_order) # True
셋째 문제에서는 응답 범위를 검사하는 조건에 걸려 ValueError가 발생한다. 이산수학을 바꾸었다면 예외 메시지는 “이산수학: 응답은 정수 0, 1, 2만 허용한다.”다. 범위를 벗어난 응답을 그대로 합산하면 같은 100점 기준이라는 설명을 유지할 수 없다. 입력 실수를 조용히 다른 점수로 바꾸지 않고 원인을 드러낸다.
넷째 문제에서는 이산수학이 알고리즘을 기다리고, 알고리즘이 이산수학과 자료구조를 기다리는 고리가 생긴다. 다른 과목을 일부 선택할 수는 있지만 두 과목은 끝까지 남는다. 결국 후보가 비어 “선수 관계에 고리가 있거나 과목 이름이 잘못됐다.”라는 메시지의 ValueError가 발생한다. 확인 후 추가한 관계를 삭제한다.
# main()에서 점수를 계산하기 전에 잠시 추가해 확인한다.
PREREQUISITES["이산수학"] = ("알고리즘",)
이 장에서 얻을 결과는 높은 점수보다 설명할 수 있는 빈틈의 위치다. 자신의 근거를 적고, 연결된 과목을 찾고, 작은 예로 다시 확인하는 일을 반복하면 다음에 무엇을 배울지 스스로 정할 수 있다.