01 / 本课学习路线
本课学习路线
阅读与推演约 105 分钟,练习约 60 分钟,进阶练习另需约 38 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
读题时写规则清单,写代码时让每条规则对应一个位置,写完逐条核对。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 把一道题的自然语言规则整理成编号清单,并标出执行顺序依赖 | 第 03、04 节 | 自查第 2 条、代码自测 |
| 用一个布尔状态位逐字符扫描,区分「引号内 / 引号外」,并在循环结束后收尾 | 第 05 节 | 自查第 3、6 条、练习 4 |
| 不看参考代码写出 AI022 并通过(AC),能解释规则顺序为什么影响结果 | 第 04、06 节 | 必做任务 1、自查第 5 条 |
| 写对 P2501 的空命令字与连续下划线两个边界 | 第 05、06 节 | 必做任务 2 |
| 例外先于替换(嵌套深度 + 转义)与内容、排版分两步 | 第 07 节 | 进阶练习 1、2 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你会上一课的计数表与集合,会字符串的切分、大小写方法和逐字符遍历。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | flag = False,遇到字符 " 就执行 flag = not flag。对字符串 a"b"c 逐字符走完后,flag 是什么?中途各是什么? | 布尔与比较与 for 循环 |
| 自测 2 | "Cat".lower()、"a-b_1".lower() 各得到什么?非字母会变吗? | 字符串 |
| 自测 3 | "a" in {"a", "b"} 与 "A" in {"a", "b"} 各返回什么? | 集合 |
| 自测 4 | "abcdefg"[2:5] 是什么?"abcdefg"[5:9] 呢(越过末尾会报错吗)? | 字符串与 切片 |
| 自测 5 | "a-b--c".split("-") 得到几个元素? | 上一课第 02 节自测 5(分隔符切分) |
展开先修自测答案
自测 1:a 时 False,第一个引号后 True,b 时仍 True,第二个引号后 False,c 时 False;走完是 False。这就是「状态位」:它记录的是「现在处在引号内还是引号外」。
自测 2:"cat"、"a-b_1"。lower() 只改大写字母,数字、连字符、下划线不变——AI022 的 lower 规则正是这个含义。
自测 3:True、False。集合的成员判断区分大小写;AI022 的 dedup 按「当前」字符串值判重,所以 lower 之前 Cat 与 cat 是两条不同记录。
自测 4:"cde";"fg"——切片越过末尾不报错,只取到末尾。P2518 每 K 个字符切组时最后一组不足 K 个就靠这一点。
自测 5:4 个:["a", "b", "", "c"]。连续两个分隔符之间是空串——P2501 的连续下划线不能用 split 直接处理,正文第 05 节会解释。
03 / 概念与术语
规则清单、状态变量、分支、扫描与收尾
规则模拟题没有新算法,有的是五个固定动作。把它们的名字和含义先定下来,后面每道题都按同一套做。
| 术语 | 含义 | 本课的例子 |
|---|---|---|
| 规则清单 | 把题目的每条规则编号抄下来,注明固定文本与执行顺序 | AI022 四条规则;P2501 五条规则 |
| 状态变量 | 记录「扫描到当前位置时处于什么情形」的变量,随字符变化 | P2501 的引号内外布尔位;P2479 的方括号嵌套深度 |
| 分支 | 对当前字符按状态决定做什么:翻转状态 / 当作分隔符 / 当作普通字符 | P2501 的三类分支 |
| 扫描 | 从头到尾逐字符处理一遍,不回头 | for ch in s: |
| 收尾 | 循环结束后处理仍留在缓冲里的内容 | P2501 最后一个命令字 |
| 执行顺序 | 多条规则按题目规定的先后作用,后一条看到的是前一条处理后的结果 | AI022 的规则按输入顺序;P2479 先判例外再替换 |
| 固定文本 | 题目要求原样输出的字符串 | ERROR、******、(^|$|[,+]) |
规则清单模板(每题动手前复制一份到草稿或注释里)
① 规则一:原文……(固定文本连引号原样抄) ② 规则二:…… ③ 规则三:…… ← 依赖:必须在 ② 之后执行 边界:空串 / 连续分隔符 / 索引越界 / 最后一个元素 输出:第一行是什么,随后每行是什么,分隔符是什么 写完代码:每条规则能指到具体某几行;指不到的那条就是漏掉的
清单的三个写法细节:一是编号,代码注释里写「规则③」,写完能逐条核对;二是固定文本连引号原样记录(ERROR、星号个数),写代码时复制不重敲;三是标注顺序依赖,像「例外先于替换」「删除多余下划线在替换之后」这类先后关系在清单上画箭头——执行顺序错误是规则模拟题的常见问题之一。
补充学习(选学)什么时候不能用拆分函数(split)约 4 分钟带引号/转义/嵌套结构的字符串,split 会拆错
split 只能按固定分隔符切分,无法识别引号或括号改变的上下文含义。P2501 引号内的下划线不是分隔符,P2479 方括号内的下划线不能替换——这些「同一个字符在不同语境含义不同」的题,必须逐字符扫描并维护状态。判断标准:当分隔符的含义取决于引号、转义或括号配对状态时,应逐字符扫描并维护相应状态,而不是使用 split。P2518 的连字符没有这种语境差别,用 split 就可以。
04 / 完整手算例:AI022
四条规则与两个题面样例每一步的数据集
AI022「训练数据清洗管线」:n 条记录,q 个步骤按输入顺序逐个执行,每个步骤把一种规则作用于当前整个数据集;输出剩余条数 r,再按顺序输出前 min(r, m) 条。四条规则的精确含义如下表,之后用题面两个样例逐步推演。
| 编号 | 规则名 | 精确含义(按题面逐条翻译) | 容易读偏的地方 |
|---|---|---|---|
| ① | dedup | 按当前字符串值去重,保留首次出现,删除后续重复 | 判重依据是执行这一步「当时」的值:lower 之后再 dedup,就按小写后的值判 |
| ② | dropna | 删除值恰好等于 NULL 或恰好等于 - 的记录(区分大小写) | 是「恰好等于」不是「包含」;null 小写不算 |
| ③ | short | 删除长度小于 3 的记录 | 是「小于 3」不是「小于等于 3」:长度 3 保留 |
| ④ | lower | 把每条记录里的大写英文字母改为小写,其他字符不变 | 数字、下划线、连字符、点号不变 |
| — | 顺序 | q 个步骤按输入顺序执行;q 可以是 0 | 不能写死一个固定顺序 |
| — | 输出 | 第一行 r;随后 min(r, m) 行 | m 大于 r 时全部输出,不能越界 |
| 阶段 | 数据集(按顺序) | 说明 |
|---|---|---|
| 读入 | Cat, cat, NULL, Do | 4 条 |
| 执行 lower | cat, cat, null, do | Cat → cat;NULL → null(它不再是缺失标记) |
| 执行 dedup | cat, null, do | 第二个 cat 是重复,删掉 |
| 输出 | 3 / cat / null / do | r=3,m=10 > 3,全部输出 |
题面样例输出就是 3⏎cat⏎null⏎do⏎。如果把顺序换成 dedup → lower:dedup 时 Cat 与 cat 不同(都保留),lower 之后变成 cat, cat, null, do,输出 4 条——第 09 节练习 2 让你自己算一遍。
| 阶段 | 数据集(按顺序) | 说明 |
|---|---|---|
| 读入 | NULL, -, abc, abc | 4 条 |
| 执行 dropna | abc, abc | NULL 与 - 恰好等于缺失标记,删掉 |
| 执行 dedup | abc | 保留首次出现 |
| 输出 | 1 / abc | r=1 |
再看一组对照输入:记录 NULL, abc, DEF,规则 lower → dropna 输出 3 条(null, abc, def,NULL 已变小写逃过删除);规则 dropna → lower 输出 2 条(abc, def)。同样的三条记录,只换规则顺序,条数就不同。
05 / 完整手算例:P2501
一个布尔位区分引号内外:逐字符切出命令字
P2501「敏感字段加密」:第一行索引 K(从 0 起),第二行命令字符串;命令字之间以一个或多个下划线分隔,一对双引号里的内容是一个命令字(内部下划线不分隔,两个引号紧挨是空命令字),把第 K 个命令字换成 ******,删除前后多余的下划线,找不到第 K 个输出 ERROR。
| 编号 | 规则 | 对应的代码动作 |
|---|---|---|
| ① | 一个或多个下划线分隔命令字 | 引号外遇到下划线:若当前命令字已开始就收下它;连续下划线时后续的什么都不做 |
| ② | 一对双引号内是一个命令字,内部下划线不分隔;引号保留在命令字里 | 遇到引号翻转状态位,并把引号本身放进当前命令字 |
| ③ | 两个紧挨的引号是空命令字(内容 "") | 引号让「命令字已开始」为真,所以它会被收下 |
| ④ | 第 K 个命令字替换成 ******(6 个星号) | 切完后 words[K] = "******" |
| ⑤ | 删除前后多余的下划线;找不到第 K 个输出 ERROR | 输出时用单个下划线连接;K >= len(words) 时输出 ERROR |
手算:题面样例 2 的字符串 aaa_password_"a12_45678"_timeout__100_"",K=2
状态位 inq 初始 False;当前命令字 cur 为空 a a a → cur = aaa _ → 引号外,收下 [aaa] p…d → cur = password _ → 收下 [aaa, password] " → inq=True,cur = " a 1 2 _ 4 5 6 7 8 → 引号内,下划线是普通字符:cur = "a12_45678 " → inq=False,cur = "a12_45678" _ → 收下 [aaa, password, "a12_45678"] t…t → cur = timeout;_ → 收下;第二个 _ → 当前命令字未开始,什么都不做 1 0 0 → cur = 100;_ → 收下 [aaa, password, "a12_45678", timeout, 100] " " → 空命令字:cur = "";字符串结束 → 收尾收下 → 共 6 个命令字 K=2 → 第 2 个(从 0 起)是 "a12_45678" → 换成 ****** 输出 aaa_password_******_timeout_100_""
| K | 被替换的命令字 | 输出 |
|---|---|---|
| 1 | password | aaa_******_"a12_45678"_timeout_100_"" |
| 4 | 100 | aaa_password_"a12_45678"_timeout_******_"" |
| 5 | ""(空命令字) | aaa_password_"a12_45678"_timeout_100_****** |
| 6 | 不存在(只有 6 个,下标最大 5) | ERROR |
题面样例 1:K=1,字符串 password__a12345678_timeout_100 → password_******_timeout_100(两个连续下划线只算一个分隔)。引号保留在命令字里、空命令字也计入索引,是这道题最容易漏的两条。
扫描练习模板(按注释补全)
Python# 练习模板:请补全下面标注为「步骤」的部分,其余可直接保留
import sys
def main() -> None:
k = int(sys.stdin.readline()) # P2501 输入第一行:整数 K
line = sys.stdin.readline().rstrip("\n") # 第二行:命令字符串(只对这一行扫描)
# 规则清单(按题目原文逐条抄录并编号;写完代码逐条核对):
# ① ...
# ② ...
tokens: list[str] = []
cur: list[str] = []
inq = False
for ch in line:
# 步骤 1:在这里写引号翻转 / 分隔符判定 / 普通字符累积三类分支
pass
# 步骤 2:扫描结束后,把尚未写入结果的缓冲内容加入结果列表
# 步骤 3:按规则清单逐条执行后续处理并输出
main()模板的输入初始化已按 P2501 的两行格式写好:第一行读整数 K,第二行读命令字符串,扫描只对第二行进行——不要改成一次读入全部再扫描,否则首行的 K 会被当成第一个命令字的开头。收尾是常见的遗漏点:循环结束后最后一个词还留在 cur 里,必须补一次收词。空词(两个引号中间什么都没有)按 P2501 的规则是合法命令字,不要过滤掉。补全后的完整程序在第 06 节展开区,练习 4 也会用到。
06 / 从规则清单到程序
两道必做题的完整参考程序,每条规则落在哪几行
先自己按清单写完并提交一次,再展开对照;对照时用题面样例和第 08 节的错误表逐条核对。
| 规则 | 代码位置 | 说明 |
|---|---|---|
| 按输入顺序执行、q 可为 0 | for rule in rules: | 规则名列表按读入顺序遍历;q=0 时循环不执行,直接输出原数据 |
| ① dedup 保留首次出现 | seen 集合 + kept 列表 | 用集合记已见值,按顺序保留首次出现——不能用 set(records),那会丢顺序 |
| ② dropna 恰好等于 | r != "NULL" and r != "-" | 是相等比较,不是 in |
| ③ short 小于 3 | len(r) >= 3 保留 | 长度 3 保留 |
| ④ lower | r.lower() | 只改大写字母 |
| 输出 r 与前 min(r, m) 条 | records[:m] | 切片越过末尾不报错,自动只取到末尾 |
展开完整参考程序 1:AI022 训练数据清洗管线(先自己写完并提交一次,再展开对照)
完整程序:AI022(标准输入 → 标准输出)
Pythonimport sys
lines = sys.stdin.read().split("\n")
n, q, m = map(int, lines[0].split())
records = lines[1:1 + n] # 当前数据集:保持先后顺序的列表
rules = lines[1 + n:1 + n + q] # q 个规则名,按输入顺序执行(q 可以是 0)
for rule in rules:
if rule == "dedup": # 按「当前」字符串值去重,保留首次出现
seen = set()
kept = []
for r in records:
if r not in seen:
seen.add(r)
kept.append(r)
records = kept
elif rule == "dropna": # 恰好等于 NULL 或 -(区分大小写)才删
records = [r for r in records if r != "NULL" and r != "-"]
elif rule == "short": # 长度小于 3 才删
records = [r for r in records if len(r) >= 3]
elif rule == "lower": # 只改大写字母,其他字符不变
records = [r.lower() for r in records]
out = [str(len(records))] + records[:m] # 第一行条数,再输出前 min(r, m) 条
print("\n".join(out))自测用例:题面两个样例,以及第 09 节练习 1、2 的输入。n 到 10⁵、q 到 10,每条规则整表扫一遍,总量 10⁶ 级别,按量级估算在 2.5 秒时限内;实际耗时以判题结果为准。
展开完整参考程序 2:P2501 敏感字段加密
完整程序:P2501(标准输入 → 标准输出)
Pythonimport sys
k = int(sys.stdin.readline())
s = sys.stdin.readline().rstrip("\n")
words = [] # 切出的命令字
cur = [] # 当前正在累积的命令字(按字符收集)
inq = False # 是否在一对双引号之内
started = False # 当前命令字是否已经开始(用来区分「空命令字」和「还没有命令字」)
for ch in s:
if ch == '"':
inq = not inq # 引号翻转状态;引号本身保留在命令字里
cur.append(ch)
started = True
elif ch == "_" and not inq:
if started: # 引号外的下划线是分隔符:收下当前命令字
words.append("".join(cur))
cur = []
started = False
# 连续的下划线只算一个分隔:没有开始的命令字就什么都不做
else:
cur.append(ch)
started = True
if started: # 收尾:最后一个命令字还留在 cur 里
words.append("".join(cur))
if k >= len(words):
print("ERROR")
else:
words[k] = "******"
print("_".join(words))自测用例:题面两个样例、第 05 节表里 K=1/4/5/6 的四组变式。题目页参考题解用「遇下划线就新开一个空串、最后过滤空串」的写法,结果相同。started 变量区分「空命令字」与「还没有命令字」:两个紧挨的引号让它为真,所以空命令字被收下;连续下划线之间它为假,所以不会收下多余的空串。
07 / 进阶两题
例外先于替换(P2479)与内容、排版分两步(P2518)
两道进阶题各练一个固定动作。都用题面示例逐字符手算,再给完整程序。
P2479「正则表达式替换」:把字符串里每个下划线替换成 (^|$|[,+]),两种例外不替换——在一对方括号内的下划线(方括号可嵌套)、被转义的下划线(前一个字符是反斜杠)。输入长度 0 到 10000。题面示例:hello_world_test → hello(^|$|[,+])world(^|$|[,+])test;(_9494) → ((^|$|[,+])9494)。
手算:a[b_c]d_e\_f(深度用 depth 记,替换记为 R)
a depth=0 普通字符 → a [ depth=1 记入并加深 → a[ b depth=1 → a[b _ depth=1 在方括号内:不替换 → a[b_ c ] depth 回到 0 → a[b_c] d → a[b_c]d _ depth=0,前一个字符 d 不是反斜杠 → 替换 R → a[b_c]dR e \ → a[b_c]dRe\ _ 前一个字符是反斜杠:转义,不替换 → a[b_c]dRe\_ f → a[b_c]dRe\_f 输出 a[b_c]d(^|$|[,+])e\_f
为什么用深度计数而不是布尔位:方括号可以嵌套,[x[y_y]x_x]_z 里第一个 ] 之后仍在外层方括号内,布尔位会在这里翻成「括号外」而把 x_x 的下划线错误替换;深度从 2 减到 1 仍大于 0,才是正确判断。练习 5 让你手算这个例子。
展开完整参考程序:P2479 正则表达式替换
完整程序:P2479(标准输入 → 标准输出)
Pythonimport sys
s = sys.stdin.readline().rstrip("\n")
out = [] # 逐字符构建结果,最后一次拼接
depth = 0 # 方括号嵌套深度:大于 0 表示在方括号内
for i, ch in enumerate(s):
if ch == "_" and depth == 0 and (i == 0 or s[i - 1] != "\\"):
out.append("(^|$|[,+])") # 两个例外都不命中,才替换
else:
out.append(ch)
if ch == "[":
depth += 1
elif ch == "]":
depth -= 1
print("".join(out))自测用例:题面两个示例与上面两个手算例。逐字符构建、用列表收集最后一次拼接(长度到 10⁴ 时在循环里做字符串拼接也能过,但列表拼接是稳妥写法)。
P2518「字符串分割(二)」:字符串被连字符分成若干段,第一段原样保留;其余段拼成一串,每 K 个字符切成新的一组;每组小写多于大写就全转小写,大写多就全转大写,相等不变;输出用连字符连接。题面示例:K=3,12abc-abCABc-4aB@ → 12abc-abc-ABC-4aB-@。
| 步骤 | 结果 | 说明 |
|---|---|---|
| 按 - 切分 | 12abc | abCABc | 4aB@ | 第一段 12abc 原样保留 |
| 其余段拼接 | abCABc4aB@ | 10 个字符 |
| 每 3 个切组 | abC | ABc | 4aB | @ | 最后一组不足 3 个,切片自动取到末尾 |
| 逐组数大小写 | abC(2 小 1 大) → abc;ABc(1 小 2 大) → ABC;4aB(1 小 1 大) → 4aB;@(0,0) → @ | 相等的两组都不转换 |
| 用 - 连接 | 12abc-abc-ABC-4aB-@ | 与题面示例一致 |
展开完整参考程序:P2518 字符串分割(二)
完整程序:P2518(标准输入 → 标准输出)
Pythonimport sys
k = int(sys.stdin.readline())
s = sys.stdin.readline().rstrip("\n")
parts = s.split("-")
first = parts[0] # 第一段原样保留
rest = "".join(parts[1:]) # 其余段拼成一串,再每 K 个字符切组
def convert(group):
lower = sum(1 for ch in group if ch.islower())
upper = sum(1 for ch in group if ch.isupper())
if lower > upper:
return group.lower()
if upper > lower:
return group.upper()
return group # 相等:不转换(第三个分支不能漏)
groups = [convert(rest[i:i + k]) for i in range(0, len(rest), k)]
print("-".join([first] + groups))自测用例:题面示例,以及第 08 节表里「相等不转换」的反例。convert 里三个分支缺了「相等不转换」就会把 4aB 错改。
08 / 边界、反例与复杂度
错误做法在具体输入上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| AI022 把规则顺序写死为 dedup → lower | 样例 1(Cat, cat, NULL, Do;规则 lower → dedup) | 4⏎cat⏎cat⏎null⏎do | 3⏎cat⏎null⏎do | 答案错误(WA) |
AI022 的 dropna 写成「包含」("-" in r) | 记录 a-b, NULL, x;规则 dropna | 只剩 x(a-b 被误删) | a-b, x | 答案错误(WA) |
| AI022 的 short 写成「小于等于 3」 | 记录 abc, ab;规则 short | 空(abc 被误删) | abc | 答案错误(WA) |
AI022 用 set(records) 去重 | 多条互不相同的记录,如 Do, Cat, abc;规则 dedup | 顺序不确定:集合不保存插入顺序,每次运行可能不同 | 按首次出现顺序 Do, Cat, abc | 答案错误(WA) |
| P2501 扫描后不收尾 | K=1,password__a12345678_timeout_100 | 最后的 100 丢失 → password_******_timeout | password_******_timeout_100 | 答案错误(WA) |
P2501 用 split("_") 切分 | 样例 2 的字符串 | 引号内的 a12_45678 被拆成两段、空段混入,索引全错 | 见第 05 节 | 答案错误(WA) |
P2479 用 replace 全局替换 | a[b_c]d_e\_f | a[b(^|$|[,+])c]d(^|$|[,+])e\(^|$|[,+])f | a[b_c]d(^|$|[,+])e\_f | 答案错误(WA) |
| P2479 用布尔位而不是深度 | [x[y_y]x_x]_z | 内层 ] 后误判为括号外,x_x 的下划线被替换 | [x[y_y]x_x](^|$|[,+])z | 答案错误(WA) |
| P2518 漏掉「相等不转换」分支(相等也转小写) | K=3 的题面示例 | 12abc-abc-ABC-4ab-@ | 12abc-abc-ABC-4aB-@ | 答案错误(WA) |
| 题目 | 输入规模 | 参考程序的时间与空间 | 结论 |
|---|---|---|---|
| AI022 | n ≤ 10⁵,q ≤ 10,记录长 ≤ 30 | O(总字符数 × q) 时间;O(总字符数) 空间 | 10⁶ 级别;实际耗时以判题结果为准 |
| P2501 | 长度 ≤ 127 | O(L) 时间与空间 | 常数级 |
| P2479 | 长度 ≤ 10⁴ | O(L) 时间;O(L) 空间(结果最长约 10 倍) | 一趟扫描 |
| P2518 | 长度以题目页为准 | O(L) 时间与空间 | 一趟扫描 |
四道题按量级估算都远小于时限;本课的错误几乎都是规则漏读或顺序错,第 03 节的清单就是为这个准备的。
09 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):不运行程序,按第 04 节的表格式写出 AI022 输入「n=3,记录 NULL, abc, DEF;规则 lower → dropna;m=10」每一步的数据集和最终输出。
展开练习 1 答案
读入 NULL, abc, DEF → lower:null, abc, def → dropna:null 不是恰好等于 NULL(区分大小写),三条都保留 → 输出 3⏎null⏎abc⏎def。把顺序换成 dropna → lower,NULL 先被删,输出 2⏎abc⏎def。
练习 2(改一个条件):题面样例 1 的规则顺序改成 dedup → lower(记录仍是 Cat, cat, NULL, Do),输出什么?
展开练习 2 答案
dedup 时四条互不相同(Cat 与 cat 大小写不同),都保留;lower 后为 cat, cat, null, do → 输出 4⏎cat⏎cat⏎null⏎do(第二个 cat 不会再被删,因为 dedup 已经执行过了)。做错最常见的原因:以为 dedup 会「延后」到 lower 之后生效。
练习 3(改一个条件):P2501 输入 K=0、字符串 __abc__,输出什么?K=1 呢?
展开练习 3 答案
前后的下划线是「多余的下划线」,切出的命令字只有一个 abc。K=0 → ******;K=1 → 只有 1 个命令字,找不到 → ERROR。做错最常见的原因:把开头的下划线当成分隔出了一个空命令字——扫描时「当前命令字未开始」的下划线什么都不做,就是为了这条。
练习 4(独立实现):把第 05 节的扫描练习模板补全成完整的 P2501 程序(模板已按题目格式读入两行:第一行 K、第二行命令字符串,保留即可),用题面两个样例和第 05 节表里 K=1/4/5/6 的四组变式自测,再提交。
展开练习 4 答案
参考程序在第 06 节展开区。自测通不过时先看输入:K 应来自第一行、扫描对象只是第二行——若第一个命令字前面多出了「1」这样的数字,就是把两行一起读进去扫描了。再按三类分支逐个查:引号分支是否既翻转状态又把引号放进命令字;下划线分支是否只在「引号外且当前命令字已开始」时收词;循环结束后是否收尾。
练习 5(迁移):手算 P2479 对 [x[y_y]x_x]_z 的输出,并写出扫描到每个下划线时的深度。
展开练习 5 答案
第一个下划线(y_y)时深度 2,不替换;第二个(x_x)时深度 1,不替换;第三个(]_z)时深度 0 且前一个字符是 ] 不是反斜杠,替换。输出 [x[y_y]x_x](^|$|[,+])z。用布尔位的程序会在第二个下划线处错误替换——这就是「深度计数」存在的理由。
10 / 读题要求与复习自评
四道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | AI022 训练数据清洗管线 | P2501 敏感字段加密 | P2479 正则表达式替换 | P2518 字符串分割(二) |
|---|---|---|---|---|
| 输入 | n q m;n 行记录;q 行规则名 | K;命令字符串 | 一个字符串(长度 0~10⁴) | K;字符串 |
| 状态 / 结构 | 有序列表 + 去重集合 | 引号内外布尔位 + 当前命令字 | 方括号嵌套深度 + 前一个字符 | 切分 + 每 K 个切组 |
| 固定文本 | 无 | ******(6 个)、ERROR | (^|$|[,+]) | 无 |
| 顺序依赖 | 规则按输入顺序 | 切词完成后才替换 | 先判两类例外再替换 | 先切组再转换 |
| 样例 | 样例 1 → 3⏎cat⏎null⏎do | K=1 → password_******_timeout_100 | (_9494) → ((^|$|[,+])9494) | K=3 → 12abc-abc-ABC-4aB-@ |
题解入口:需要对照解法时,先展开本课第 06、07 节的完整参考程序;四道题的题目页另有思路与参考代码,可在题目页查看。复习与自评:本课算完成 = 两道必做题 AI022、P2501 都通过判题,并勾选全部六条「学习完成检查」;进阶练习与复习题不影响完成状态。六条检查是自评,不改变题目的通过(AC)状态。复习时用三个问题自测:① 不看正文,写出 AI022 四条规则的精确含义与两个容易读偏的地方;② 不看表格,手算题面样例 2 的 P2501 切词结果并说出为什么引号要保留;③ 说出 P2479 为什么用深度而不是布尔位。答不出哪一条,就回到对应的节重读,再做第 09 节对应的练习。
11 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道;进阶练习已通过 0/2 道
为 AI022 整理一份规则清单
代码自测自主练习练习重点:四种规则各自的精确语义+执行顺序的含义;预计用时:10 分钟
完成标准:清单里写清了 dedup 保留首次出现、dropna 只删恰好 NULL 或 -(区分大小写)、short 删长度小于 3、lower 只改大写字母
需要时查看提示
逐字读题目说明:dropna 是「恰好等于」不是「包含」;short 是「小于 3」不是「小于等于」;dedup 按「当前」字符串值判重——lower 之后再 dedup,判重依据就是小写后的值。第 04 节的规则表可以对照。
AI022 · 训练数据清洗流程
必做任务 1练习重点:四种规则按输入顺序作用于整个数据集;输出条数与前 m 条;预计用时:30 分钟
完成标准:能解释 lower→dedup 与 dedup→lower 的结果差异(样例 1 就是前者)
需要时查看提示
数据集用列表(list)维护顺序。dedup 用集合(set)记已见值、按顺序保留首次出现;n 到 10⁵、q 到 10,总量 10⁶ 级别,逐步整表扫描足够。输出第一行是条数 r,再输出 min(r, m) 条——m 大于 r 时全部输出,注意不要越界。第 04 节给了两个样例每一步的数据集。
P2501 · 敏感字段加密
必做任务 2练习重点:引号状态位切词+索引替换+ERROR 分支;预计用时:20 分钟
完成标准:空命令字(两个引号)能被正确计入索引
需要时查看提示
沿用第 05 节手算的模板:状态位切词,引号保留在命令字里,空词合法、连续下划线只算一个分隔。切完后按索引替换成 ******(6 个星号),索引越界输出 ERROR。输出时命令字之间用单个下划线连接。第 05 节的表给了同一字符串四个 K 的输出。
P2479 · 正则表达式替换
进阶练习 1进阶练习练习重点:方括号嵌套深度+转义判断,都通过才替换;预计用时:20 分钟
完成标准:能说出为什么用深度计数而不是布尔位(方括号可嵌套)
需要时查看提示
维护嵌套深度(depth):遇 [ 加一、遇 ] 减一,depth>0 时下划线不替换。转义判断看前一个字符是不是反斜杠。两个例外都不命中,才把 _ 换成 (^|$|[,+])。逐字符构建结果串,不要用全局替换方法(replace)。第 07 节有逐字符手算。
P2518 · 字符串分割(二)
进阶练习 2进阶练习练习重点:首段保留+其余按 K 重组+组内大小写多数规则;预计用时:18 分钟
完成标准:能说清「等于时不转换」这条边界写在了哪个分支
需要时查看提示
先按 - 切分,第一段原样保留;其余段拼接成一串再每 K 个字符切组。每组数小写和大写的个数:小写多全转小写、大写多全转大写、相等不动——三个分支不要漏最后一个。第 07 节的表给了题面示例每一步。
提交结果
提交结果说明与处理方法
- WA
答案错误
按清单逐条对:漏规则(转义/空词/收尾)、顺序错(先替换后判例外、规则顺序写死)、规则读偏(dropna 的「恰好等于」、short 的「小于 3」)——第 08 节的表给出了每种错误的具体输出
- PE
格式错误
AI022 第一行的条数 r 不要漏输出;P2501 输出的连接符是单个下划线
- RE
运行错误
索引替换前先判范围;切分后取段前先判段数
- TLE
超时
P2479 逐字符构建用 list 收集最后拼接(join),不要在循环里做字符串拼接
- AC
通过
把规则清单和代码的对应关系再看一遍:每条规则都能指到具体某几行
12 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。