目标:从错误结果定位到一个具体的错误假设。
修改:prep.py中的maximum。
预计投入:30–60 分钟。
1. 先写规格
本节路线:以全负数和空列表分别检查逻辑与边界,修复prep q1;先修为预备0的traceback阅读。命令沿用 课程根目录。离开本节要解释best在每轮代表什么,并设计一个不在正文中的失败反例,不能只把初值改完就结束。
maximum(values) 对非空数值列表返回最大元素,不修改输入;空列表抛 ValueError。本题不用 max(),因为我们要观察循环中的状态。
输入 [2, 7, 3] → 7
输入 [-5, -2, -9] → -2
输入 [-8] → -8
输入 [] → ValueError
raise ValueError("列表不能为空") 的意思是拒绝没有定义答案的输入。本题不约定空列表的最大值为 0。
2. 先跟踪错误,不急着改
starter 的核心是:
best = 0
for value in values:
if value > best:
best = value
用 [-5, -2, -9] 跟踪:
| 本轮 value | 比较前 best | value 大于 best? | 比较后 best |
|---|---|---|---|
| -5 | 0 | 否 | 0 |
| -2 | 0 | 否 | 0 |
| -9 | 0 | 否 | 0 |
程序没报异常,却返回了不在输入中的 0。这是逻辑错误。继续把输入缩成 [-8],错误仍然存在;这就是一个更小的复现。
3. 用不变式决定怎样修
我们希望每轮结束后都成立:
best是“已经看过的元素”中的最大值。
因此开始比较前,best 应有一个来自已看元素的合法值。不要改成 -100000:输入可能更小;也不要只判断某个测试列表。
先在 maximum 中处理空输入,再决定非空时的初值。你可以从第二个元素继续循环,也可以重新比较第一个元素;两种都可以得到正确答案。
4. 动手和逐层提示
只改 maximum。每次改完运行:
python course/tools/grade.py prep --question q1 --failfast
提示 1:应该改变哪个假设?
原函数假定最大值至少为 0,但题目没有这个条件。
提示 2:如何得到一个合法初值?
非空列表里,第一个元素一定存在;先看过它,手中就有一个“已经看过部分”的最大值。
提示 3:局部伪代码
如果列表为空:拒绝输入
best = 第一个元素
遍历其余元素:比 best 大时更新
返回 best5. 给自己的修复找反例
测试通过后,再自己预测并检查 [-1000000, -1000001]、[4, 4, 4]、[0, -1]。你可以临时加打印,也可以在实验目录用 Python 交互窗口调用函数;提交前移除临时打印即可。
记录:错误假设是什么?哪个最小输入推翻了它?修复为什么适用于没见过的输入?
下一步:预备 2:列表和矩阵。