소개
작은 보드게임 도서관에서 오늘 밤 즐길 대안을 찾습니다. 좋아하는 게임과 비슷해도 대여할 수 없거나 인원에 맞지 않을 수 있습니다. 이 실습에서는 어떤 게임을 사용할 수 있는지와 수치 특성을 어떻게 비교할지를 구분합니다.
다시 실행할 수 있는 적합성 프로그램과 실제 JSON 결과를 만듭니다. Python 함수·딕셔너리, 기본 NumPy 배열, 표 읽기가 선수 지식입니다. k-최근접 이웃 분류를 알면 도움이 되지만 예측할 레이블은 없습니다. Python과 NumPy는 준비되어 있고 scikit-learn은 필요 없습니다. 45분은 읽기와 일반적인 수정 시간을 포함한 저자의 예상입니다.
오늘 사용할 수 있는 게임 판정하기
이 단계에서는 실제 도서관 목록을 살펴보고, 준비된 함수의 한 영역에 오늘 밤의 적합성 규칙을 구현합니다.
Terminal 탭을 여세요. 저장 파일 리더를 포함한 모든 명령을 여기서 실행합니다. Terminal을 열거나 재연결해도 작업 공간을 다시 설치하거나 초기화하지 않습니다. cd는 현재 터미널의 디렉터리를 바꾸며 다른 터미널은 자기 디렉터리를 가집니다.
cd /home/labex/project/board-games
실제 입력부터 살펴보세요. CSV는 헤더 뒤에 쉼표로 구분된 행이 이어집니다. cat은 파일을 바꾸지 않고 출력합니다. 이것은 오늘 밤에 이미 적합하다고 판정한 게임 목록이 아니라 필터링 전의 전체 목록입니다.
cat catalog.csv
각 행은 서로 다른 게임 판본입니다. 유일하고 비어 있지 않은 game_id는 그 판본을 찾고 연결하는 키입니다. title은 표시용 텍스트입니다. 여러 Harbor 판본의 ID가 다르므로 구분해야 합니다. minutes는 양수인 플레이 시간 추정치이며 complexity는 값이 클수록 복잡한 1–5의 합성 평가입니다. 정수 min_players, max_players는 양 끝을 포함합니다. 합성 입력은 완전하며 ID마다 한 행입니다. 잘못된 데이터 정리는 이 연습의 범위 밖입니다.
cat availability.csv
별도의 대여 표에는 모든 목록 ID에 대한 행이 하나씩 다른 순서로 있습니다. CSV 텍스트 true/false는 준비된 로더에서 Python 불리언 True/False가 됩니다. 키로 연결하기는 첫 행끼리 붙이는 것이 아니라 같은 game_id를 찾는 것입니다. 유일한 키와 ID마다 하나인 대여 상태 행 덕분에 모호하거나 누락된 일치가 없습니다. 이 조회를 직접 구현할 것입니다.
JSON은 이름 있는 값, 목록, 객체를 저장합니다. 오늘 밤의 요청을 읽으세요.
cat night.json
reference_id는 닮을 기준 예시, players는 인원, k는 원하는 가까운 대안 수이며 다음 실습에서 사용합니다. 기준은 검색 예시이므로 그 자체가 대여 불가이거나 부적합할 수 있습니다. 오늘 조건은 반환할 대안만 만족하면 됩니다. 정확한 ID만 제외하고 제목이나 특성이 같다는 이유로 모두 제외하지 마세요. 원시 입력 개요는 아직 이 규칙을 적용하거나 적합성 결과를 만들지 않았습니다.
대여 가능하고 네 명을 지원하는 세 게임을 생각해 보세요. Harbor는 60분·복잡도 3, Quay는 61분·복잡도 5, Bridge는 90분·복잡도 3입니다. 원단위 차이는 후보 빼기 기준입니다. Quay는 1분 길고 복잡도 2점 높으며, Bridge는 30분 길고 복잡도는 같습니다. Python의 -c는 따옴표로 감싼 계산을 인수 하나로 실행합니다. 세미콜론은 Python 문장 두 개를 나눕니다.
python3 -c "print('Quay minutes:', 61 - 60); print('Bridge minutes:', 90 - 60)"
1과 30이 나와야 합니다. 원단위 유클리드 거리는 특성 차이를 제곱해 더한 뒤 제곱근을 구합니다. 이 단위를 그대로 섞으면 30분이 복잡도 2점을 압도할 수 있습니다. 다음으로 공통 척도를 정의할 것입니다. 먼저 누가 비교에 참여할 수 있는지 정하세요.
suitability.py, ranking.py, 결과 파일, 자신의 스크립트 백업을 편집할 수 있습니다. 제공된 모든 CSV/JSON 입력, planner.py, coursekit.py, show_results.py, 두 starter-*.py 복구 사본은 보존하세요. planner.py는 공개 함수를 고르고 파일 읽기·타입 변환·JSON 쓰기를 coursekit.py에 맡깁니다. 둘 다 업무 판단을 하지는 않습니다. 이 실습에서는 ranking.py를 미완성으로 두세요.
준비된 모듈을 읽으세요.
cat suitability.py
choose_games(catalog, availability, request)는 로드된 딕셔너리 목록들과 요청 딕셔너리를 받습니다. 표시된 두 영역은 eligible = [], scale = {}로 시작합니다. 적합한 게임이 없다는 주장이 아니라 미완성 자리 표시자입니다. 마지막 return은 실행기에 ID와 척도를 묶어 돌려줍니다. 지금은 적합성 영역을 교체하고 척도 자리 표시자는 2단계까지 유지하세요. setup과 입력 개요는 채점할 결과를 만들지 않습니다.
이 모듈을 백업하세요. cp SOURCE DESTINATION은 지정한 파일만 복사하며 .bak는 백업 접미사입니다. 실수한 뒤 이 백업을 덮어쓰지 마세요.
cp suitability.py suitability.py.bak
nano는 Terminal 안에서 기존 파일을 편집합니다. 명령에 한정된 LC_ALL=C.UTF-8은 전역 셸 설정을 바꾸지 않고 이 편집기에만 UTF-8을 선택합니다. nano의 단축키 표시에서 ^는 Ctrl을 누르라는 뜻입니다. 화살표는 커서를 옮기고 입력하면 텍스트가 삽입됩니다. Ctrl+W, 검색어, Enter로 텍스트를 찾습니다. Home은 줄 시작으로, Ctrl+K는 그 줄 전체를 자릅니다. Python은 들여쓰기를 사용합니다. 아래 줄은 함수 안에서 공백 네 개로 시작하며 반복문과 조건 안에는 더 들여씁니다.
LC_ALL=C.UTF-8 nano suitability.py
먼저 원하지 않는 편집을 버리는 연습을 하세요. Ctrl+W, Enter로 ## Candidate identities를 찾고 End를 누른 뒤 그 주석에 temporary를 추가하세요. Ctrl+X를 누르고 저장 질문에서 N을 누르세요. 파일을 바꾸지 않고 나갑니다. 다시 여세요.
LC_ALL=C.UTF-8 nano suitability.py
eligible = []를 찾아 Home, Ctrl+K 한 번으로 그 자리 표시자 한 줄만 지우세요. 함수 헤더, 척도 영역, 마지막 return은 유지하세요. 그 자리에 아래 들여쓴 줄을 넣으세요. 딕셔너리 컴프리헨션은 ID와 대여 가능 여부를 연결하는 조회 표를 만듭니다. 반복문은 독립적인 세 조건에 and를 사용하고 연쇄 <= 비교는 두 인원 한계를 포함합니다. append는 표시 제목이 아니라 일치한 ID를 추가합니다.
status = {row['game_id']: row['available'] for row in availability}
eligible = []
for game in catalog:
different_id = game['game_id'] != request['reference_id']
fits_group = game['min_players'] <= request['players'] <= game['max_players']
if different_id and fits_group and status[game['game_id']]:
eligible.append(game['game_id'])
Ctrl+O, 변경하지 않은 파일 이름을 확인하는 Enter, 나가는 Ctrl+X로 저장하세요. 저장은 함수를 실행하지 않습니다. 기존 return은 sorted(eligible)로 입력 행 순서와 무관한 알파벳순 ID 목록을 만들며 scale은 빈 객체로 둡니다.
실행기의 --mode suitable은 choose_games를 선택합니다. 필수 --output suitable.json은 저장 결과의 이름입니다. 기본 입력은 이 디렉터리의 catalog.csv, availability.csv, night.json입니다. --catalog, --availability, --request로 다른 경로를 고를 수 있습니다. 이 입력은 계속 읽기 전용입니다.
python3 planner.py --mode suitable --output suitable.json
제공된 리더는 그 저장 JSON만 엽니다. 없는 파일을 생성하거나 목록에서 무언가 계산하지 않습니다.
python3 show_results.py suitable.json
Scale: {}와 ID left, near, raw, right, same, zero가 나와야 합니다. same과 zero는 모두 다른 Harbor 판본입니다. zero는 수치 특성까지 기준과 같습니다. blocked는 대여 불가, small은 네 명 불가, ref는 정확한 기준입니다. 이 ID를 두 입력 표와 비교하세요. 적합성 판단은 이제 완성되었지만 척도는 의도적으로 아직 구현하지 않았습니다.
다음의 잘못된 디렉터리 실패는 의도적입니다. 한 디렉터리 위로 이동하세요.
cd /home/labex/project
python3 planner.py --mode suitable --output suitable.json
Python은 여기서 실행기를 찾을 수 없어서 can't open file과 No such file or directory를 보고합니다. 기존 프로그램과 결과는 초기화되지 않았습니다. pwd로 이 터미널의 디렉터리를 확인하고 작업 위치로 돌아와 실제 명령을 다시 실행하세요.
pwd
cd /home/labex/project/board-games
python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json
편집 오류는 Python의 파일 이름과 줄 번호를 읽고 nano로 모듈을 다시 열어 해당 영역만 고치세요. SyntaxError는 콜론이나 따옴표 누락을 가리키는 경우가 많고 IndentationError는 앞쪽 공백을 살펴보라는 뜻입니다. 저장하고 나가서 다시 실행하세요. Ctrl+X, N은 저장하지 않은 수정을 버립니다. 필요하다면 cp suitability.py.bak suitability.py로 이 미완성 기본 상태만 복원할 수 있으므로 이후 적합성 영역을 다시 구현해야 합니다. 백업도 보호된 starter-suitability.py도 완성된 해답은 아닙니다. 복구하려고 setup을 다시 실행하거나 입력을 변경하지 마세요.
전체 목록에 하나의 척도 유지하기
이 단계에서는 완성한 적합성 로직을 보존하며 척도 영역을 구현합니다.
Terminal 탭에서 계속합니다. 새 터미널에서도 안전하도록 위치를 명시합니다.
cd /home/labex/project/board-games
특성은 비교에 쓰는 수치 속성입니다. 순서는 항상 minutes, 그다음 complexity입니다. 인원과 대여 가능 여부는 적합 조건이지 거리 특성이 아닙니다. 기준, 대여 불가, 인원 부적합도 포함한 전체 목록에서 특성별 최소·최대를 구합니다. 범위는 최대에서 최소를 뺀 값입니다. MinMax는 x를 (x - minimum) / range로 바꿔, 분과 복잡도 차이를 각 목록 범위의 비율로 표현합니다.
전체 최소는 [10, 1], 범위는 [100, 4]입니다. Quay의 차이는 [1/100, 2/4], Bridge는 [30/100, 0/4]가 됩니다. 스케일링한 Quay 거리는 약 0.5001, Bridge는 0.3입니다. 척도 선택은 순위를 바꾸지만 어느 단위도 취향을 알려 주지 않습니다. 변환 후 동일 가중치는 이 연습에서 공개한 설계 선택입니다.
여기서 목록은 척도를 정의하는 기준이지 지도 학습 훈련 집합이 아닙니다. 목표 레이블이 없어 훈련/테스트 분할도 없습니다. 오늘 대여 가능 여부만 바뀌면 척도와 계속 적합한 후보의 거리가 바뀌면 안 됩니다. 필터 후 재계산하면 게임을 빌릴 때마다 자가 움직입니다.
전체에서 값이 일정한 특성은 범위가 0입니다. 실제 범위 0을 저장하되 이후 거리 계산의 나눗수는 1로 합니다. 기준도 목록에 속하므로 그 특성의 모든 차이는 0이고 거리 기여도 0입니다. 정보를 만들지 않고 0으로 나누는 일을 피합니다.
작동하는 적합성 단계의 새 백업을 만들고 원래 기본 백업은 유지하세요.
cp suitability.py suitability-ids.py.bak
LC_ALL=C.UTF-8 nano suitability.py
Ctrl+W, Enter로 scale = {}를 찾고 Home, Ctrl+K로 그 자리 표시자 한 줄만 자르세요. 위의 완성된 ID 조회와 반복문은 바꾸지 마세요. 아래 네 줄을 들여써 넣으세요. np.array는 공개된 특성 순서로 두 열의 수치 표를 만듭니다. axis=0은 각 열을 따라 최소·최대를 찾습니다. tolist()는 JSON에 저장할 수 있는 일반 목록으로 배열을 바꿉니다.
features = np.array([[game['minutes'], game['complexity']] for game in catalog])
minimum = features.min(axis=0)
ranges = features.max(axis=0) - minimum
scale = {'min': minimum.tolist(), 'range': ranges.tolist()}
기존 return은 이제 계산한 scale과 같은 정렬된 ID를 포함합니다. Ctrl+O, Enter, Ctrl+X로 저장하고 나가세요. 앞의 작업을 교체하거나 다른 프로그램을 덧붙이지 않고 영역 하나를 추가했습니다.
결과에는 각 적합 ID가 알파벳순으로 한 번씩 필요하며 scale.min과 scale.range 목록에는 숫자가 두 개씩 있어야 합니다. 서식 문자열이 아니라 수치를 저장하세요. 해롭지 않은 추가 설명 필드도 허용됩니다. 절대·상대 수치 허용 오차는 1e-8로 일반적인 부동소수점 차이용이지 다른 척도를 허용하는 값은 아닙니다.
원본 입력으로 실제 결과를 다시 만들고 자기 출력만 덮어씁니다.
python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json
최소 10과 1, 범위 100과 4, 같은 적합 ID 여섯 개가 나와야 합니다. 리더는 10 대신 10.0을 표시할 수도 있지만 둘 다 숫자 10입니다. 저장 구조를 직접 보세요.
cat suitable.json
편집으로 적합성 영역이 손상되었다면 cp suitability-ids.py.bak suitability.py는 작동하던 그 단계만 복원합니다. nano를 다시 열어 척도 영역을 다시 구현하세요. 반면 cp starter-suitability.py suitability.py는 미완성 기본 상태로 돌아가 두 영역을 모두 잃게 하므로 의도적으로 처음부터 시작할 때만 사용하세요. 일반적인 수정은 해당 줄을 고치고 저장하고 나가서 다시 실행해야 합니다. 제공된 두 starter 파일과 모든 입력을 보존하세요. Terminal 재연결은 setup을 실행하거나 백업을 자동 복원하지 않습니다.
자체 확인: 오늘 밤의 적합성은 이 전체 목록의 척도를 바꾸지 않고도 달라질 수 있습니다. 다음 독립 실습에는 완성된 적합성 함수가 제공되므로 거리와 선택에 집중할 수 있습니다.
요약
오늘 밤의 적합성과 내용 유사도를 구분하고, 안정적인 ID로 독립 기록을 연결했습니다. 같은 제목이나 수치 차이가 0인 적법한 대안도 남기고 전체 목록에서 고정 MinMax 척도를 구했습니다. suitable.json은 다시 생성할 수 있는 실제 결과입니다. 다음 실습은 독립 환경에서 이 지식으로 후보를 측정하고 설명합니다.



