가장 가까운 후보 설명하기

PythonBeginner
지금 연습하기

소개

도서관은 오늘 사용할 수 있는 게임을 판정할 수 있습니다. 이제 사람이 고를 수 있도록 충분히 설명된 투명한 유사 후보 목록이 필요합니다. 공개된 두 특성으로 거리를 구하고 원래 단위의 부호 있는 차이를 보이며 선택 경계의 동률을 모두 보존합니다.

이전 실습의 Python 적합성 판정과 MinMax를 전제하지만 환경은 독립적입니다. 입력과 완성된 보조 함수가 준비되어 이전 파일이나 프로세스가 필요 없습니다. 순위를 구현하고 실제 JSON을 만들며 리더는 결과를 보충하지 않습니다. 50분은 저자의 예상입니다. 내용 유사도는 개인 취향을 예측하지 않으며 대규모 목록 성능을 입증하지도 않습니다.

적합한 각 대안 측정하기

이 단계에서는 완전한 거리표를 만들고 후보마다 기준과 무엇이 달라지는지 보여 줍니다.

Terminal 탭을 열고 독립 작업 공간으로 갑니다. 이 입구를 열거나 재연결해도 작업 공간을 다시 설치하거나 진행 내용을 지우지 않습니다. cd는 현재 터미널의 디렉터리만 바꾸므로 새 터미널에서는 반복합니다.

cd /home/labex/project/board-games

목록에는 유일한 game_id, 표시용 제목, 시간 minutes, 1~5의 complexity, 양 끝을 포함한 min_players/max_players가 있습니다. 별도 CSV는 ID마다 true/false 대여 상태를 다른 순서로 가집니다. night.json은 기준 예시, players, 희망 k를 지정합니다. 기준이 대여 불가나 인원 부적합이어도 검색은 유효합니다. 오늘 조건은 반환 후보에만 적용됩니다. 같은 제목·특성이라도 다른 ID이면 0 거리도 적법하고, 정확한 reference_id만 제외합니다.

cat은 파일을 바꾸지 않고 표시합니다. 요청과 이전 실습 내용을 구현한 보조 함수를 읽습니다.

cat night.json
cat suitability.py

거리 계산을 추가하기 전에 이미 있는 인터페이스를 실행하세요. --mode suitable은 choose_games를 호출하며 필수 --output은 쓸 JSON 파일의 이름입니다. 다른 입력 옵션이 없으면 실행기는 현재 디렉터리의 catalog.csv, availability.csv, night.json을 읽습니다. 리더는 저장한 파일만 출력합니다.

python3 planner.py --mode suitable --output suitable.json
python3 show_results.py suitable.json

아래 척도와 함께 알파벳순 적합 ID 여섯 개 left, near, raw, right, same, zero가 보여야 합니다. 이것은 작동하는 적합성 결과이지 완성된 추천이 아닙니다. 순위 모듈에는 아직 비어 있는 구현 영역이 있습니다.

choose_games는 ID로 상태를 연결하고 모든 적합 ID와 필터 전 전체 목록에서 얻은 척도를 반환합니다. 순서는 minutes, complexity입니다. scale.min은 [10, 1], scale.range는 [100, 4]입니다. 같은 매개변수를 기준과 후보에 재사용해야 대여 때마다 자가 변하지 않습니다. 목표 레이블이나 지도 학습 훈련/테스트 분할은 없습니다.

planner.py는 coursekit.py를 통해 옵션과 형식을 다루지만 선택·정렬은 하지 않습니다. suitability.py, ranking.py, 생성 결과를 수정할 수 있고, 모든 CSV/JSON, planner.py, coursekit.py, show_results.py, 두 starter-*.py는 보존합니다.

값 x의 MinMax는 (x - minimum) / range입니다. 일정 특성은 범위 0을 저장하고 나눗수 1을 쓰며, 기준도 목록에 속하므로 해당 차이는 전부 0입니다. 변환된 기준 q와 후보 c의 유클리드 거리는 sqrt((c_minutes-q_minutes)^2 + (c_complexity-q_complexity)^2)입니다. 변환 뒤 두 특성의 가중치는 같습니다. 작은 거리는 이 규칙상 유사함이지 더 재미있다는 뜻이 아닙니다.

원래 설명은 별도로 구합니다. minutes_delta, complexity_delta는 변환 전 후보에서 기준을 뺀 값입니다. 분 차이가 음수면 더 짧은 게임입니다. 이 열은 해당 ID의 실제 후보에서 구해야 하며 다른 행이나 거리로 꾸며내면 안 됩니다.

편집 전에 순위 코드의 틀을 살펴보세요. from suitability import choose_games는 로컬 보조 함수를 가져옵니다. 제공된 prepared, by_id, reference 줄은 그 출력과 요청을 ID 기반 목록 기록에 연결합니다. 빈 rows 목록, 후보 반복문, 정렬, 마지막 return은 주변 구조를 제공합니다. 주석 세 개는 완성할 영역을 표시할 뿐 빠진 거리나 선택을 계산하지 않습니다.

cat ranking.py

소스를 백업하세요. cp는 현재 파일을 복사합니다. 이 백업은 완성된 답이 아니라 미완성 기본 상태입니다.

cp ranking.py ranking.py.bak

np.where는 범위가 0이면 1, 아니면 실제 범위를 씁니다. np.linalg.norm은 두 요소 차이 벡터의 유클리드 길이, float는 NumPy 값을 JSON용 숫자로 바꿉니다. 정렬 키는 반올림하지 않은 거리와 정확히 같은 거리일 때의 game_id 쌍이며, 짧은 lambda가 각 행의 키를 정의합니다. 정렬 전에 반올림하지 마세요. 동등한 수식도 끝자리 부동소수점 차이로 거의 같은 값의 순서가 달라질 수 있으며 표시 소수 자릿수가 저장 정렬 규칙은 아닙니다.

nano로 모듈을 여세요. 첫 실습처럼 LC_ALL=C.UTF-8은 이 편집기 실행에만 적용됩니다. Python은 들여쓰기로 문장을 묶으므로 수준마다 공백 네 개를 유지하세요.

LC_ALL=C.UTF-8 nano ranking.py

Ctrl+W로 ## Metric parameters를 검색하고 Enter를 누르세요. 다음 pass로 내려가 Home, Ctrl+K 한 번으로 자리 표시자 한 줄을 지우세요. 앞에 공백 네 개가 있는 다음 다섯 줄을 넣으세요. rows = []와 주변 함수는 보존하세요.

    minimum = np.array(prepared['scale']['min'])
    ranges = np.array(prepared['scale']['range'])
    divisor = np.where(ranges == 0, 1, ranges)
    query = np.array([reference['minutes'], reference['complexity']])
    query_scaled = (query - minimum) / divisor

이어서 Ctrl+W로 ## Candidate record를 검색하고 Enter를 누르세요. 그 영역의 pass로 내려가 Home, Ctrl+K 한 번을 누르세요. 아래 블록을 넣으세요. 기존 반복문 안이므로 문장 앞에는 공백 여덟 개, rows.append 안의 필드에는 열두 개를 씁니다. for 줄, game = by_id[game_id], 이후 rows.sort는 바꾸지 마세요.

        values = np.array([game['minutes'], game['complexity']])
        candidate_scaled = (values - minimum) / divisor
        distance = float(np.linalg.norm(candidate_scaled - query_scaled))
        difference = values - query
        rows.append({
            'game_id': game_id, 'title': game['title'], 'distance': distance,
            'minutes_delta': float(difference[0]),
            'complexity_delta': float(difference[1]),
        })

지금은 ## Cutoff selection 영역을 cutoff = None, selected = []로 두세요. 선택을 미완성으로 정직하게 남기면서 이 중간 표를 읽을 수 있게 합니다. Ctrl+O, Enter, Ctrl+X로 저장하고 나가세요. 저장 전에 원하지 않는 편집을 발견했다면 Ctrl+X, N으로 이 편집기 세션의 수정을 버리고 다시 열어 의도한 영역만 고치세요.

planner.py를 recommend 모드로 실행하고 --output으로 실제 파일을 지정합니다. 다른 입력 옵션이 없으면 현재 위치의 catalog.csv, availability.csv, night.json을 읽습니다. --catalog, --availability, --request는 명시적 경로를 받습니다.

python3 planner.py --mode recommend --output recommendations.json

리더로 저장 JSON을 읽습니다. 해당 파일만 열며 목록에서 다시 계산하거나 빠진 행을 채우지 않습니다.

python3 show_results.py recommendations.json

여섯 후보 행이 나와야 합니다. zero는 0, near·same은 약 0.1, left/right는 0.3입니다. raw는 1분만 길어도 복잡도가 2점 높아 약 0.5001입니다. Minutes +/-, Complexity +/-는 원단위 부호 있는 차이를 표시합니다. 첫 버전은 의도적으로 Cutoff: None과 빈 선택을 남기며 다음 단계에서 완성합니다.

SyntaxError나 IndentationError가 나타나면 파일과 줄 번호를 읽고 ranking.py를 다시 열어 그 줄이나 영역을 고치세요. 한 줄을 고치려고 모듈 전체를 덮어쓰지 마세요. 파일 없음 오류는 대개 터미널 디렉터리가 잘못된 경우입니다. 첫 실습에서 연습한 대로 pwd로 확인하고 시작 부분의 cd로 올바른 위치에 돌아오세요. cp ranking.py.bak ranking.py는 이 단계의 작업을 의도적으로 포기할 때만 미완성 기본 상태를 복원합니다. 결과를 통과시키려고 입력을 편집하지 말고 보존하세요.

경계의 전체 후보 집합 보존하기

이 단계에서는 선택 규칙을 완성한 다음, 같은 프로그램을 후보 부족과 후보 없음에도 사용합니다.

Terminal 탭에서 아래 작업 위치로 계속합니다.

cd /home/labex/project/board-games

k는 후보가 충분할 때 목표 최소 수이지 정확히 k행 약속이 아닙니다. 먼저 모든 적합 거리를 계산해 정렬합니다. N개라면 경계는 순서상 min(k, N)번째 거리입니다. Python 인덱스는 0부터라 min(k, N) - 1을 씁니다. 반올림하지 않은 거리가 cutoff + 1e-9 이하인 모든 후보를 남깁니다. 절대 여유 1e-9는 경계 동률 정의이지 정렬 구간이나 미리 반올림할 권한이 아닙니다.

k보다 적으면 실제 항목을 모두 반환합니다. 0개면 candidates, selected_ids는 빈 목록, JSON 경계는 null(Python None)입니다. 대여 불가나 인원 부적합 게임으로 대체하지 않습니다. 전체 목록에서 가까운 k부터 고르고 나중에 필터하면 뒤의 적법 후보를 놓칩니다. 먼저 자격을 거르는 함수가 이를 막습니다.

보통 요청은 k=2입니다. zero가 가장 가깝고 near·same이 동률 규칙상 경계를 공유하므로 셋 다 선택해야 합니다. 순서는 실제 비반올림 행을 따르고 정확한 동률에 game_id를 씁니다. 검토한 대상을 알 수 있게 전체 여섯 행을 남깁니다.

선택을 추가하기 전에 작동하는 거리표를 백업하세요. 방금 시험한 척도와 후보 기록을 이 사본에 유지합니다.

cp ranking.py ranking-distances.py.bak

이전 작업을 교체하지 말고 같은 모듈을 여세요.

LC_ALL=C.UTF-8 nano ranking.py

Ctrl+W로 ## Cutoff selection을 검색하고 Enter를 누르세요. cutoff = None으로 내려가 Home, Ctrl+K 두 번으로 그 줄과 selected = []만 지우세요. if, else는 공백 네 개, 각 분기 안은 여덟 개로 다음 블록을 넣으세요. 마지막 return은 기존 들여쓰기로 블록 뒤에 남습니다.

    if rows:
        cutoff = rows[min(request['k'], len(rows)) - 1]['distance']
        selected = [row['game_id'] for row in rows
                    if row['distance'] <= cutoff + 1e-9]
    else:
        cutoff = None
        selected = []

if rows 검사는 빈 목록의 인덱스 접근을 막습니다. 목록 컴프리헨션은 기존 정렬 순서대로 일치하는 ID를 유지합니다. 1단계의 척도 매개변수, 후보 반복문, 정렬을 보존하세요. Ctrl+O, Enter, Ctrl+X로 저장하고 나가세요. 이 단계를 되돌려야 하면 cp ranking-distances.py.bak ranking.py가 선택은 미완성인 채로 시험한 거리표 버전을 복원합니다. 파일 전체를 다시 쓰지 말고 경계 편집을 다시 하세요.

JSON은 특성 순서의 scale.min, scale.range, 각 후보의 game_id, title, 수치 distance, 부호 있는 minutes_delta, complexity_delta, 그리고 cutoff, selected_ids를 가져야 합니다. 계산 정밀도의 유한 숫자를 저장하고 반올림한 문자열을 쓰지 않습니다. 해롭지 않은 추가 설명은 허용합니다. ID별 절대·상대 수치 오차 1e-8은 경계 규칙 1e-9와 다릅니다. 동등한 식과 보통의 구현을 허용하고 취향은 평가하지 않습니다. 두 공개 인터페이스도 유지해야 합니다. choose_games는 알파벳순 eligible_ids와 전체 척도, rank_games는 추천 결과를 반환합니다. 순위는 독립적으로 구현해도 되며 내부 보조 호출을 강제하지 않습니다. 이 실습에서 작업 공간에 추가 적합성 결과 파일을 만들 필요는 없습니다.

보통 결과를 다시 생성해 읽습니다.

python3 planner.py --mode recommend --output recommendations.json
python3 show_results.py recommendations.json

zero, near, same이 선택되고 나머지 후보도 남습니다. cat recommendations.json으로 비반올림 거리를 포함한 JSON을 직접 볼 수 있습니다.

두 추가 요청은 밤의 조건만 바꾸고 목록이나 대여 입력은 그대로입니다. 먼저 cat으로 봅니다. --request는 해당 명령에만 적용하며 night.json이나 터미널 변수를 바꾸지 않습니다. 별도 출력 경로로 일반 결과를 보존합니다.

cat sparse-night.json
python3 planner.py --mode recommend --request sparse-night.json --output sparse.json
python3 show_results.py sparse.json

여덟 명은 right만 이용할 수 있습니다. k=5여도 한 후보가 정직한 답입니다. 전체 척도와 거리 0.3은 그대로입니다.

cat empty-night.json
python3 planner.py --mode recommend --request empty-night.json --output empty.json
python3 show_results.py empty.json

스무 명에게는 적합한 게임이 없습니다. 행 없음, Cutoff: None, 명확한 부재 메시지가 나옵니다. Terminal에서 cat empty.json으로 저장 경계가 null임을 확인할 수 있습니다. 리더는 빈 결과를 보여 줄 뿐 대체 후보를 고르지 않습니다.

recommendations.json, sparse.json, empty.json 세 파일이 각각 요청과 일치하고 원본으로 재실행 가능해야 합니다. 실수한 코드 편집은 nano를 다시 열어 해당 영역을 고치고 다른 완성 영역을 보존한 뒤 세 명령을 모두 다시 실행하세요. 다시 연습하려면 cp starter-ranking.py ranking.py가 미완성 starter만 돌려줍니다. 터미널 재열기는 데이터 초기화나 setup 실행을 하지 않습니다.

요약

전체 목록의 같은 척도로 모든 적합 후보를 측정하고 원단위의 실제 부호 있는 차이를 설명했으며, 먼저 반올림하지 않고 경계 동률을 모두 선택했습니다. 세 파일은 보통의 밤, 후보 부족, 후보 없음에 대응합니다. 추천은 투명한 내용 유사도이며 즐거움을 예측하는 모델이 아닙니다.