🐸

破损数据柜

数据与文本工具python-data-tinkerer-36-the-broken-cabinet
奖励: 180 XP
|

破损数据柜

Hoppy 走进试炼的第三个房间时,发现桌上已经摆着一份别人写好的小工具。它会读入数据柜记录,也会打印出一份总结,看起来好像一切正常。可档案官摇了摇头:最麻烦的脚本,往往不是直接报错的那种,而是“能跑、也有输出、但结果 quietly 不对”的那种。

所以这节课的重点不是再发明一套新流程,而是练真正会做事的人必须有的能力:读懂已有代码,发现变量名和实际逻辑哪里对不上,再把这份小工具修回可信的样子。

先练一种很重要的感觉:变量名说了什么,代码真的有做到吗?

修旧代码时,第一步通常不是盯着报错,而是顺着“数据长什么样”一路往下看。变量名、拆分位置、循环对象,这三样如果有一处对不上,结果就会开始悄悄歪掉。

row = "name=fern seal | room=north | count=2"
parts = row.split(" | ")

room_name = parts[0].split("=")[1]
print(room_name)

这里代码能运行,但 room_name 实际拿到的是 "fern seal",不是 "north"。所以读旧代码时,一个很好用的问题是:这个变量名和它真正取到的那一段,真的一致吗?今天的 starter 里,就有几处这种“小地方不对,后面整串都会偏”的问题。

今天的任务:修好一份已经能跑、但答案不可信的小工具

starter 已经帮你读好了 broken_cabinet.txtcabinet_index.json。脚本也已经写出了大部分流程:清理文本、拆字段、补上 JSON 记录、去重、统计、做 summary。你的任务不是重写,而是把几处错误逻辑修回来,让这些步骤重新对齐。

1
先检查 clean_line(raw_line) 真的有没有把脏行清干净

这一步应该把前缀 "## " 去掉,把 "~" 变回空格,再把尾巴上的 "??" 清掉。只要这里少做一步,后面的名字和字段内容都会继续带着脏痕迹。

2
检查 build_record(cleaned_line) 取的字段是不是取对了位置

这份记录里有 drawercabinetstatusdust。修的时候重点不是背更多语法,而是确认每个变量到底对应哪一段,再用 cabinet_index[cabinet_code] 补上 room_namekeeper_name

3
让去重和统计都对着正确的对象工作

这份工具真正想保留的是“每个抽屉第一次出现的记录”,所以 seen_drawers 应该记住的是 drawer_name,而 room_counts 应该统计修好后的 unique_records,不是原始全部记录。

4
最后再检查 summary 里的词义有没有被兑现

raw_row_count 应该真的是原始行数,unique_drawer_count 应该真的是唯一抽屉数,ready_unique_count 也应该建立在修正后的 status 上。修旧代码时,最后这一步特别重要:名字看起来对,不代表结果真的对。

这不是大型 debug 教程

这节课不是要你学习异常处理,也不是要你在一团谜题里乱试。starter 里只有几处明确的旧能力错误:清理动作不完整、字段读错、去重对象错了、统计范围偏了。你的任务是冷静地读懂它,然后把它修好。

参考答案
点击展开
参考答案:
import json

with open("broken_cabinet.txt", "r", encoding="utf-8") as file:
  cabinet_text = file.read().strip()

with open("cabinet_index.json", "r", encoding="utf-8") as file:
  cabinet_index = json.load(file)

print("Broken cabinet text:")
print(cabinet_text)
print("Cabinet index:", cabinet_index)

cabinet_lines = cabinet_text.splitlines()
print("Cabinet lines:", cabinet_lines)


def clean_line(raw_line):
  cleaned = raw_line.strip().replace("## ", "")
  cleaned = cleaned.replace("~", " ")
  cleaned = cleaned.replace("??", "")
  return cleaned


def build_record(cleaned_line):
  parts = cleaned_line.split(" | ")
  drawer_name = parts[0].split("=")[1]
  cabinet_code = parts[1].split("=")[1]
  status = parts[2].split("=")[1]
  dust_level = parts[3].split("=")[1]
  cabinet_record = cabinet_index[cabinet_code]

  return {
      "drawer_name": drawer_name,
      "cabinet_code": cabinet_code,
      "status": status,
      "dust_level": dust_level,
      "room_name": cabinet_record["room_name"],
      "keeper_name": cabinet_record["keeper_name"],
  }


cleaned_lines = []
for raw_line in cabinet_lines:
  cleaned_lines.append(clean_line(raw_line))

all_records = []
for cleaned_line in cleaned_lines:
  all_records.append(build_record(cleaned_line))

seen_drawers = set()
unique_records = []
for record in all_records:
  drawer_name = record["drawer_name"]
  if drawer_name not in seen_drawers:
      seen_drawers.add(drawer_name)
      unique_records.append(record)

room_counts = {}
for record in unique_records:
  room_name = record["room_name"]
  if room_name not in room_counts:
      room_counts[room_name] = 0
  room_counts[room_name] += 1

ready_unique_count = 0
for record in unique_records:
  if record["status"] == "ready":
      ready_unique_count += 1

cabinet_summary = {
  "raw_row_count": len(all_records),
  "unique_drawer_count": len(unique_records),
  "duplicate_row_count": len(all_records) - len(unique_records),
  "ready_unique_count": ready_unique_count,
  "room_counts": room_counts,
}

print("Cleaned lines:", cleaned_lines)
print("All records:", all_records)
print("Seen drawers:", seen_drawers)
print("Unique records:", unique_records)
print("Room counts:", room_counts)
print("Cabinet summary:", cabinet_summary)
高级技巧
想更进一步?点击展开

这节课最值得带走的,不只是“我修对了几个 bug”,而是你开始会检查一条数据流程有没有前后对齐:清理后的文本是什么,拆出来的字段是不是那一段,去重和统计到底在对谁工作。

下一课会把这种整合感再推到终点:不只是修一段现成脚本,而是完成整章世界观里的最后一次试炼。到那时,你会更明显地发现,真正学会的人,已经不只会跟着写,还会判断、修正、收束整个流程。

Loading...
终端输出
Terminal
Ready to run...