CS1602计算导论
Lab 4Part 1 计算基础与 Python 入门AI Level 0

控制流综合

A 段十题练习条件与循环,从这次起所有函数都要写类型提示;B 段用两种完全不同的算法计算 π,并比较它们的好坏;C 段学习在终端里查看和修改文件。

截止:小作业 2 · 10 月 27 日 周二 23:59
本页目录

本次目标

  • 熟练组合条件与循环解决完整问题
  • 判断该用 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 的误差列成一张表。仔细观察,会发现一个很整齐的规律。

请回答:

  1. n 每乘以 10,误差大致变成原来的几分之一?
  2. 照这个规律,要让误差小于 1e-6(小数点后六位准),大概需要多少项?
  3. 运行一次,验证你的估计。运行之前,先估计一下要花多长时间。

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 比较与结论

把两种方法的误差放在一张表里比较,回答:

  1. 同样撒一百万个点 / 取一百万项,哪种更准?
  2. 蒙特卡洛的误差随 n 怎么变?(提示:和 B-2 的规律不一样,试试看误差乘以 √n)
  3. 如果要算到小数点后六位,两种方法各需要多少次运算?
  4. 那蒙特卡洛还有什么用? 想一想:什么样的问题只能用它?

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 走一遍

把命令和输出截图放进提交说明:

  1. 把 B-1 的输出重定向到 pi.txt
  2. wc -l pi.txt 看它有多少行
  3. 分别用 head -n 3 pi.txt 和 tail -n 3 pi.txt 查看开头和末尾
  4. less pi.txt 翻一遍,用 / 搜一个数字,然后按 q 退出
  5. nano pi.txt,在文件开头加一行标题,保存退出,再 head -n 1 pi.txt 确认

C-2 想一想

  1. 一个一百万行的文件,为什么不该用 cat 看?
  2. tail -f 在什么场景下有用?(提示:程序正在运行,日志文件在不断变长)

提交前自查

  • 每个函数都有类型提示
  • 4-1 的合法性检查放在最前面
  • 4-2 处理了 n = 0
  • 4-3 用 91 测过
  • 4-4、4-6、4-7 的问题都在提交说明里回答了
  • 4-9 用负数的 k 测过,4-10 用 n = 0 测过