# 参考解 · lab 0006 · 读完输入再动手

**六个任务都做过一遍再打开这里。** 这里的六个 `.py` 与任务目录里的文件同名：

```text
把 solutions/0006-python-io-and-debug/ 里的 .py 拷进 lab/0006-python-io-and-debug/ 覆盖同名文件
py run_tests.py       → 六个任务全过，退出码 0（本机实测 2 秒左右）
```

参考解是自包含的：`run_tests.py` 与八个 `sample*.in` 在任务目录里也有，
拷这六个 `.py` 过去直接就能跑。

## 逐个任务看什么

| 任务 | 参考解补上的关键几行 | 要看的现象 |
| --- | --- | --- |
| 1 · 五种格式 | `[int(x) for x in text.split()]`；`[line for line in text.splitlines() if line.strip()]`；`vals = tokens[2:2 + n*m]` 配 `[vals[i*m:(i+1)*m] for i in range(n)]`；`pos` 指针一段一段推；每组 `pos += n` | 五组样例各打出课件写死的那份结构；`sample6.in`（折行）与 `sample3.in` 打出同一个矩阵 |
| 2 · 先读完再算 | `sum_rows_tokens` 里 `tokens = text.split()` 之后只用 `vals`；`parse_matrix` 与 `sum_matrix` 分成两段 | 对照版在整齐数据上 78、在折行数据上 36；两版新写法在两种排版上**都是 78** |
| 3 · 字符串坑 | 直接写 `'10' < '9'`；`sorted(raw)` 与 `sorted(raw, key=int)` 对照；`max(raw)` 与 `max(int(x) for x in raw)` 对照；`try/except TypeError` 取 `type(e).__name__` | `True` / `False` / `['10', '100', '2', '9']` / `['2', '9', '10', '100']` / `9` / `100` / `TypeError` |
| 4 · 打印定位 | 切片右端 `i*m + m - 1` 改回 `(i + 1)*m`；诊断三行 `诊断 n m` / `诊断 第 i 行长度` / `诊断 总和`；`show_repr` 按第一个换行符切、把 `\n` 留在左边 | 诊断打出「n m 3 4」与「行长度 4」；修好之后每行长度 `[4, 4, 4]`、总和 78；repr 打出 `'3 1 4 1 5 9 2 6\n'` |
| 5 · 提示混进 stdout | `clean` 里 `int(input())` 不带参数；`dirty` 里 `input('请输入 N：')` 配 `print('答案是：', n*2)` | clean 的 stdout 逐字是 `20\n`；dirty 的 stdout 是 `请输入 N：答案是： 20\n`；判分的探针把 clean 换成带提示的版本后仍能验出污染 |
| 6 · 提交前检查 | `actual.splitlines() == expected.splitlines()`；`['input(', 'print(']` | 四行自检 `True/True/False/False`；探针的八组文本逐组对上（含只差末尾换行、只差一个空格、多一行、多一个前缀） |

## 每个任务的「为什么」

- **任务 1 的要害是「按空白读」而不是「按行读」**。题面只约定元素的个数与先后顺序，
  所以 `text.split()` 是按题面办事，不是取巧：先把 $n \times m$ 个数取成一段 `vals`，
  再按 `m` 个一段切成 `grid`，换行排在哪里都不影响结果。`sample6.in` 与 `sample3.in`
  是同一份数、只把换行位置换掉——按行切会读出 `[[1, 2, 3, 4], [5, 6], [7, 8]]`，
  按空白读两条都给出同一个矩阵。这也是判分在任务 1 末尾单列一条折行检查的原因。
- **任务 2 是这一课的核心证据**。对照版（给好的，别改）就是课件里那份「边读边算」：
  它在整齐数据上打出 78 是对的，折行数据上打出 36 而**一个错字都没报**。
  36 不是「几乎对」，是一个完全合法的错误答案——因为折行后的每一行仍是能 `int()`
  的整数，没有空行、没有多余 token，所以没有任何异常可抛。
  修法不是把循环次数改对，而是换掉前提：`split()` 只认空白、不区分空白长什么样。
- **任务 3 解释「为什么静默」**。两个字符串比较是合法操作，Python 按定义好的规则
  给出结果，不会拦下你；真正会报错的是混着比（`'10' < 5` 得 `TypeError`）。
  字符串比较**按字符逐个进行**：`'1'` 的编码是 49、`'9'` 是 57，第一位就分出大小，
  长度根本轮不到参与。所以 `'10' < '9'` 是 `True`、`max(['10', '9'])` 得 `'9'`。
  **注意两个 max 不是一个答案**：字符串序里最大的是 `'9'`，转成整数之后最大的是 `100`
  ——这一组对照正是这一课要看到的差别。
- **任务 4 的手法就是「先把出错的位置缩小，再改代码」**。切片右端写错这类 bug
  不报错、只让结果偏小，属于必须靠打印长度才能找出来的那一类。两行输出就够了：
  「诊断 n m 3 4」说每行该有 4 个数，「诊断 第 0 行长度 3」说只有 3 个，结论当场就出来。
  用 `repr()` 那一条有个细节：**别用 `splitlines()` 取这一行**——它会把行尾的换行符
  去掉，`repr` 出来就看不见 `\n` 了，这一条手法也就白做了。
- **任务 5 的依据是官方文档**：`stdout` 用于 `print()` 和表达式语句的输出，
  **以及用于 `input()` 的提示符**——提示与答案走同一条流，而评测机读的正是它。
  判分不只比对你的两版输出，还会**把 clean 临时换成带提示的版本**再跑一次：
  如果它验不出污染，这条检查就是假的。这是「检查本身也要被检查」。
- **任务 6 的分寸在「放过什么、抓什么」上**。放过的是文本文件末尾那一个换行
  （`'78\n'` 与 `'78'` 算相同），抓的是行尾多一个空格、多一行、少一行、
  多一个与答案无关的前缀。所以**别用 `strip()` 把两边空白都抹掉**——那样前两条
  都会漏。两边都 `splitlines()` 再比，行内的空格与行数就都保留下来了。

## 可以接受的别的写法

- `parse_one_line`：`list(map(int, text.split()))`、或循环加 `append` 都行，
  三种写法的输出逐字一致（课件那张对照表就是这么列的）。
- `parse_n_then_lines`：`[int(line) for line in lines[1:n+1]]` 与
  `list(map(int, lines[1:n+1]))` 等价；关键是**按题面给的 n 取**，别取到末尾。
- `parse_matrix`：`vals` 那一步用切片或推导式都行；分行那一步写成
  `[vals[i*m:i*m+m] for i in range(n)]` 也算对（右端 `i*m+m` 与 `(i+1)*m` 是同一个数）。
- `same_output`：`actual.splitlines() == expected.splitlines()` 最省事；
  也可以先 `rstrip('\n')` 再逐行比。只要末行换行放过、行内空格与行数不放，就算对。
- `show_repr`：`text.split('\n')[0] + '\n'` 这种切片写法也行（效果一样）。

## 常见错法（对照自己的写法）

- **矩阵按行切开**：`sample3.in` 能过，`sample6.in` 立刻错成
  `[[1, 2, 3, 4], [5, 6], [7, 8]]`。这是本 lab 最常见的错法，也正是任务 1 那条折行检查
  要治的。
- **任务 2 用 `input()` 逐行读**：`sample3.in` 得 78、`sample6.in` 得 36，
  而且不报错。把 `parse_matrix` 写成「按空白读」就两条都对了。
- **任务 3 的 `max` 两行写成同一个**：`max(raw)` 得 `'9'`、转整数后得 `100`，
  写成两遍 `max(raw)` 或两遍整数版都会对不上。
- **任务 4 先把切片改对、再补诊断**：那样「诊断 总和」打出的就是 78 而不是 54，
  看不到「它不报错、只是偏小」这一步。顺序是先让打印把现象打出来，再改。
- **任务 4 用 `splitlines()` 取第一行**：`repr` 出来是 `'3 1 4 1 5 9 2 6'`，
  末尾的 `\n` 不见了——这条手法就失去了意义。
- **任务 5 的 clean 版加了前缀**：`print('答案是：', n*2)` 会让 std 输出不是 `20\n`，
  逐字比对直接判错。clean 版的标准输出要恰好是答案那一行。
- **任务 6 用 `strip()` 两边抹白**：`'78 \n'` 与 `'78\n'` 会被判成相同，
  「行尾多一个空格」就抓不出来了。
- **调试打印留在任务 1 的 `solve()` 里**：判分按行读 stdout，多打的行会让对应的
  那一条对不上。

## 做完之后

用一句话回答：**任务 2 里，哪一行决定了折行数据算不算得对？**
（能说清「哪一行 → 它依赖还是不依赖换行位置 → 为什么」就算这一课练到位了。）
