区分适用游戏与相似游戏

PythonBeginner
立即练习

介绍

一家小型桌游图书馆需要为今晚寻找替代游戏。某款游戏可能很像你喜欢的游戏,却已被借走,或不适合今晚的人数。本实验把两个问题分开:哪些游戏能用,以及如何比较它们的数值特征。

你将编写可重复运行的适用性程序,并保存真实的 JSON 结果。前置是 Python 函数与字典、NumPy 基本数组和表格阅读。了解 K 近邻分类会有帮助,但本项目没有待预测的标签。环境已准备 Python 和 NumPy,不需要 scikit-learn。45 分钟为作者估计,包含阅读和普通纠错时间。

判断今晚能使用哪些游戏

本步骤先检查真实图书馆目录,再在已准备函数的一个区域中实现今晚的资格规则。

打开 Terminal Tab。所有命令均在此运行,包括读取已保存文件的程序。打开或重连 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 长一分钟、复杂度高两点;Bridge 长三十分钟、复杂度相同。Python 的 -c 将引号中的计算作为一个参数运行。分号分隔两条 Python 语句。

python3 -c "print('Quay minutes:', 61 - 60); print('Bridge minutes:', 90 - 60)"

应得到 1 和 30。原始欧氏距离将特征差平方相加,再开平方。直接混合这些单位时,三十分钟可能压过两点复杂度差异。接下来将定义共同尺度;先决定哪些游戏有资格参与比较。

允许编辑 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,不改变全局 shell 设置。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) 生成按字母序排列的身份列表,不受输入行序影响,并让 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 是精确参考。对照两个输入表核查这些身份。资格判断已经完成,但尺度刻意尚未实现。

接下来的目录错误是有意安排的。向上移动一级目录:

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]。缩放后的欧氏距离约为 0.5001 和 0.3。公开的度量会改变它们的顺序,但两套单位都不能预测个人喜好。缩放后特征等权,是本练习透明的设计选择。

目录定义的是度量,不是监督学习训练集:没有目标标签,也没有训练/测试划分。只改变借用状态,不能改变尺度或保留候选的距离。在资格筛选后重新计算最小值/范围,会让尺子随着游戏被借走而移动。

如果某特征在整个目录中恒定,保存真实范围 0。后续距离计算对此特征使用除数 1。查询本身是目录成员,因此每个候选在该恒定特征上的差都为零。这能避免除零,同时不虚构信息。

为已能工作的资格阶段创建新备份,保留最初的基础版本备份不变:

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.0 而不是 10,两者都表示数值十。直接查看保存的结构:

cat suitable.json

如果编辑破坏了资格区域,cp suitability-ids.py.bak suitability.py 只恢复已能工作的那个阶段。重新打开 nano,再次实现尺度区域。相反,cp starter-suitability.py suitability.py 会恢复未完成的基础版本,丢失两个区域;只有有意重新开始时才使用它。普通纠错应修复相关行,保存、退出后重跑。保留两个已提供的 starter 文件和所有输入。重连 Terminal 不会替你运行 setup 或恢复备份。

自检:今晚的资格可以变化,而完整目录定义的尺子保持不变。下一个独立实验已提供完整适用性函数,让你专注于距离和选择。

总结

你把今晚的适用资格与内容相似度分开,以稳定的游戏 ID 关联独立记录,保留合法的同名和零差异替代项,并从完整目录推导固定的 MinMax 尺度。suitable.json 是可重新生成的真实结果。下个实验将利用这些知识测量并解释候选游戏,且使用自己的独立环境。