控制流综合
A 段十题练习条件与循环,从这次起所有函数都要写类型提示;B 段用两种完全不同的算法计算 π,并比较它们的好坏;C 段学习在终端里查看和修改文件。
本页目录
本次目标
- 熟练组合条件与循环解决完整问题
- 判断该用
for还是while - 开始给所有函数写类型提示
- 为下周的个人项目 A(递归)做准备
A 段 · 必做
4-1 成绩分级
实现 grade(score: int) -> str:90 及以上 "A",80–89 "B",70–79 "C",60–69 "D",60 以下 "F"。
分数不在 0–100 之间时返回 "Invalid"。
grade(95) → "A"
grade(60) → "D"
grade(59) → "F"
grade(101) → "Invalid"
grade(-1) → "Invalid"
4-2 数位之和
实现 digit_sum(n: int) -> int,求一个非负整数各位数字之和。不许把它转成字符串。
digit_sum(0) → 0
digit_sum(12345) → 15
digit_sum(999) → 27
4-3 判断质数
实现 is_prime(n: int) -> bool。
is_prime(1) → False
is_prime(2) → True
is_prime(97) → True
is_prime(91) → False # 91 = 7 × 13,容易漏判
4-4 考拉兹步数
实现 collatz_steps(n: int) -> int:偶数除以 2,奇数乘 3 加 1,返回到达 1 所需的步数。
collatz_steps(1) → 0
collatz_steps(6) → 8
collatz_steps(27) → 111
然后在提交说明里回答:1 到 1000 之间,哪个起点需要的步数最多?是多少步?
4-5 打印图形
实现 triangle(n: int) -> str,返回一个 n 行的星号三角形字符串,行之间用 \n 分隔,末尾不要换行。
triangle(4) 返回的字符串打印出来是:
*
**
***
****
4-6 猜数游戏
写一个程序(不是函数):随机生成 1–100 之间的一个整数,反复读入用户的猜测,提示「大了」或「小了」,猜中后输出一共猜了几次。
import random
answer = random.randint(1, 100)
这道题是交互式的,用 print 输出,不用 return。在提交说明里贴上完整玩一局的过程。
4-7 项目预热:汉诺塔的步数
实现 hanoi_steps(n: int) -> int,返回把 n 个盘子从一根柱子移到另一根所需的最少步数。
hanoi_steps(1) → 1
hanoi_steps(3) → 7
hanoi_steps(10) → 1023
这道题用循环就能做。 下周学了递归,会用完全不同的方式再做一遍。 两种写法的对比,是个人项目 A 的起点。
在提交说明里写下你发现的规律。
4-8 姓名缩写
实现 initials(full_name: str) -> str,把用空白隔开的姓名变成大写的首字母缩写,每个字母后面跟一个点。
initials("Zhang San Feng") → "Z.S.F."
initials("ada lovelace") → "A.L."
initials(" Ada Lovelace ") → "A.L."
initials("Plato") → "P."
initials("") → ""
4-9 凯撒密码
实现 caesar(text: str, k: int) -> str,把每个英文字母沿字母表向后移 k 位,超过 z 就从 a 接着数。
大写字母和小写字母分别在各自的范围里移动;数字、空格、标点保持不变。
caesar("Hello, World!", 3) → "Khoor, Zruog!"
caesar("xyz", 3) → "abc"
caesar("Khoor", -3) → "Hello"
caesar("abc", 29) → "def"
k 可以是任意整数:负数表示向前移,大于 26 也可以。
4-10 手写进制转换
实现 to_base(n: int, b: int) -> str,把非负整数 n 写成 b 进制(2 <= b <= 16)的字符串。
大于 9 的数位用大写字母 A–F 表示(十六进制里 A 表示 10,F 表示 15)。
to_base(96, 2) → "1100000"
to_base(96, 16) → "60"
to_base(255, 16) → "FF"
to_base(0, 2) → "0"
不许使用 bin()、oct()、hex()、format(),要自己一位一位算出来。
B 段 · 深入:两种方法算 π,比一比
这一段要你用两种完全不同的算法算同一个常数,然后比较它们。 这是这门课第一次用实验的方法评价算法:哪个算法好,自己运行出来比较。
只需要循环和算术,不用列表。
B-1 莱布尼茨级数
三百多年前莱布尼茨发现:
π/4 = 1 − 1/3 + 1/5 − 1/7 + 1/9 − ⋯
实现 leibniz(n: int) -> float,取前 n 项算出 π 的近似值。
分别取 n = 10, 100, 1000, 10000,把结果和真值 math.pi 一起打印出来,
并算出误差(abs(近似值 - math.pi))。
B-2 找出误差的规律
把 B-1 的误差列成一张表。仔细观察,会发现一个很整齐的规律。
请回答:
- n 每乘以 10,误差大致变成原来的几分之一?
- 照这个规律,要让误差小于
1e-6(小数点后六位准),大概需要多少项? - 运行一次,验证你的估计。运行之前,先估计一下要花多长时间。
B-3 蒙特卡洛:用随机数算 π
换一个完全不同的思路:在边长为 1 的正方形里随机撒点,落在四分之一圆内的点所占的比例
应该接近 π/4。
import random
x, y = random.random(), random.random() # 各是 [0, 1) 里的随机数
# 点 (x, y) 落在四分之一圆内 ⟺ x² + y² ≤ 1
实现 monte_carlo(n: int) -> float,撒 n 个点估计 π。
同样取 n = 1000, 10000, 100000,记录结果和误差。
B-4 比较与结论
把两种方法的误差放在一张表里比较,回答:
- 同样撒一百万个点 / 取一百万项,哪种更准?
- 蒙特卡洛的误差随 n 怎么变?(提示:和 B-2 的规律不一样,试试看误差乘以 √n)
- 如果要算到小数点后六位,两种方法各需要多少次运算?
- 那蒙特卡洛还有什么用? 想一想:什么样的问题只能用它?
C 段 · 基本功:在终端里编辑和查看文件
上周学了把输出重定向到文件。这周学怎么查看这个文件,以及怎么在终端里直接修改它。
看文件:四个命令
| 命令 | 作用 |
|---|---|
cat 文件 | 全部打印出来。只适合小文件 |
head 文件 | 只看前 10 行;head -n 3 看前 3 行 |
tail 文件 | 只看后 10 行;tail -f 会持续跟踪文件,有新内容就显示 |
less 文件 | 翻页看。空格下翻,b 上翻,/词 搜索,q 退出 |
wc -l 文件 | 数有多少行 |
改文件:nano
nano 文件名 打开一个最简单的终端编辑器。屏幕最下面两行就是帮助:
| 按键 | 作用 |
|---|---|
Ctrl+O 然后回车 | 保存(O 是 write Out) |
Ctrl+X | 退出 |
Ctrl+W | 搜索(Where is) |
C-1 走一遍
把命令和输出截图放进提交说明:
- 把 B-1 的输出重定向到
pi.txt wc -l pi.txt看它有多少行- 分别用
head -n 3 pi.txt和tail -n 3 pi.txt查看开头和末尾 less pi.txt翻一遍,用/搜一个数字,然后按q退出nano pi.txt,在文件开头加一行标题,保存退出,再head -n 1 pi.txt确认
C-2 想一想
- 一个一百万行的文件,为什么不该用
cat看? tail -f在什么场景下有用?(提示:程序正在运行,日志文件在不断变长)
提交前自查
- 每个函数都有类型提示
- 4-1 的合法性检查放在最前面
- 4-2 处理了
n = 0 - 4-3 用
91测过 - 4-4、4-6、4-7 的问题都在提交说明里回答了
- 4-9 用负数的
k测过,4-10 用n = 0测过