🐸

重影卷轴

数据与文本工具python-data-tinkerer-35-the-duplicate-scroll
奖励: 180 XP
|

重影卷轴

Hoppy 走到档案馆试炼的第二道门,看到桌上摆着一卷让人头疼的抄录记录:同一批卷轴被记了不止一次,像是留下了几层重影。档案官轻声提醒他,最危险的不是“看起来有点乱”,而是这些重复行会悄悄把后面的统计带偏。

所以这节课的重点不是再学一个新容器,而是开始更稳地做判断:什么该拿来记“见过没有”,什么该保留第一次出现的顺序,什么该负责做次数统计。Chapter 6 到这里,会更像一次真正的小任务,而不是单点按钮练习。

先想清楚:你到底想保留什么

遇到重复记录时,常见的失误不是不会写循环,而是把所有东西都塞进同一种结构里。可在这种任务里,不同结构负责的事情其实不一样:set 很适合回答“这个名字我见过吗”,list 适合保留第一次出现的顺序,dict 则适合把“位置 -> 次数”这种统计结果收起来。

sample_titles = ["Moon Thread", "Ember Ink", "Moon Thread", "Fern Seal"]

seen_titles = set()
ordered_unique = []

for title in sample_titles:
  if title not in seen_titles:
      seen_titles.add(title)
      ordered_unique.append(title)

print(ordered_unique)

这个玩具例子只演示一件事:set 不是拿来展示顺序的,而是帮你快速记住“看过了”;真正按第一次出现顺序留下来的,是 ordered_unique 这个 list。等会儿进入 starter 时,你还会再加上一份 dict 统计,三种结构就会一起工作。

今天的任务:把重影卷轴收成唯一档案

starter 已经帮你读好了 duplicate_scroll.txtshelf_map.json。你要先完成 clean_line(raw_line)build_record(cleaned_line),再把步骤组织起来:做出 cleaned_linesall_records,然后用 seen_scrollsunique_records 去掉重影,最后用 wing_countsarchive_summary 收出统计结果。

1
先把每一行卷轴记录清回可读形状

每一行都带着同样的噪声:前缀 "## "、被 "~" 挤在一起的名字,以及尾巴上的 "??"。先把这些动作收进 clean_line(raw_line)

2
把清理后的文本行变成完整记录

build_record(cleaned_line) 里,先拆出 scroll_nameshelf_codestatus,再用 shelf_map[shelf_code] 补上 wing_namekeeper_name

3
让不同结构各做自己最适合的事

set 记录哪些卷轴名已经见过,用 list 保留第一次出现的完整记录顺序,再用 dict 统计每个档案翼区留下了多少份唯一卷轴。这里的重点不是代码越花越好,而是结构职责要清楚。

4
把结果收成一份小而完整的总结

最后整理 archive_summary,让它至少告诉你:原始行数、唯一卷轴数、删掉了多少重影行、唯一且 ready 的卷轴有多少,以及 wing_counts。这就是一份很典型的中型整理任务收口。

这不是算法课

我们不是在追求最炫的去重写法,也不是要把任务做成复杂优化题。真正想练的是:你能不能在一个稍微完整的任务里,把 set、list、dict 放到它们各自最合适的位置上。

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

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

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

print("Duplicate scroll text:")
print(scroll_text)
print("Shelf map:", shelf_map)

scroll_lines = scroll_text.splitlines()
print("Scroll lines:", scroll_lines)


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


def build_record(cleaned_line):
  parts = cleaned_line.split(" | ")
  scroll_name = parts[0].split("=")[1]
  shelf_code = parts[1].split("=")[1]
  status = parts[2].split("=")[1]
  shelf_record = shelf_map[shelf_code]

  return {
      "scroll_name": scroll_name,
      "shelf_code": shelf_code,
      "status": status,
      "wing_name": shelf_record["wing_name"],
      "keeper_name": shelf_record["keeper_name"],
  }


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

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

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

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

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

archive_summary = {
  "raw_row_count": len(all_records),
  "unique_scroll_count": len(unique_records),
  "duplicate_row_count": len(all_records) - len(unique_records),
  "ready_unique_count": ready_unique_count,
  "wing_counts": wing_counts,
}

print("Cleaned lines:", cleaned_lines)
print("All records:", all_records)
print("Seen scrolls:", seen_scrolls)
print("Unique records:", unique_records)
print("Wing counts:", wing_counts)
print("Archive summary:", archive_summary)
高级技巧
想更进一步?点击展开

这节课最值得带走的,不是“去重”两个字本身,而是你开始会分工:set 管见过没有,list 管保留顺序,dict 管统计结果。结构一旦选对,步骤就会自然稳定下来。

下一课会换一种方式检验这些能力:不是让你从零搭,而是让你修一份出问题的小脚本。到那时,你会更明显地感受到,真正学会结构选择的人,也更容易看懂并修正别人的数据处理流程。

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