外观
第9周:第六次上机课
一、列表的基本用法
对应 01 · 列表练习、02 · 校门外的树 和 06 · 去除重复数字。
1. 下标与切片
python
numbers = [10, 20, 30, 40]
print(numbers[0]) # 10,第一个元素
print(numbers[-1]) # 40,最后一个元素
print(numbers[1:3]) # [20, 30]下标从 0 开始;切片 numbers[1:3] 包含下标 1,但不包含下标 3。
2. 用下标记录状态
列表的下标可以表示位置,元素可以记录该位置的状态。例如,用 1 表示有树,0 表示没有树:
python
trees = [1] * 6 # 位置 0~5,初始都有树
for i in range(1, 4): # 处理位置 1、2、3
trees[i] = 0
print(trees) # [1, 0, 0, 0, 1, 1]
print(trees.count(1)) # 3,还有树的位置数位置从 0 到 5,一共需要 6 个元素。range(1, 4) 不包含 4;重复把同一位置标记为 0,不会重复减少树的数量。
3. 添加和删除元素
python
numbers = [10, 20, 30]
numbers.append(40) # [10, 20, 30, 40]:添加到末尾
numbers.insert(1, 15) # [10, 15, 20, 30, 40]:插入到下标 1
del numbers[0] # [15, 20, 30, 40]:按下标删除
numbers.remove(30) # [15, 20, 40]:删除值为 30 的元素
print(numbers)这些操作会修改原列表。remove(30) 删除第一个值为 30 的元素;如果列表中没有这个值,会报错。
也可以用切片一次删除多个元素:
python
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]
print(numbers) # [10, 40, 50]4. 判断、统计与排序
python
numbers = [5, 2, 5, 8]
print(5 in numbers) # True,判断元素是否存在
print(numbers.count(5)) # 2,统计元素出现次数
print(len(numbers)) # 4,列表长度
print(max(numbers)) # 8,最大值
print(sorted(numbers)) # [2, 5, 5, 8],从小到大排序sorted(numbers) 返回一个排好序的新列表,原列表不会因此改变。max() 需要列表中至少有一个元素。
二、二维列表与方向数组
对应 04 · 矩阵换行求边缘和 和 05 · 图像模糊处理。
用方向数组访问上下左右
二维列表用 image[行][列] 访问元素。要访问当前位置 (i, j) 的上下左右,可以把每个方向写成一对行、列的偏移量:
| 方向 | 行、列的偏移量 |
|---|---|
| 上 | (-1, 0) |
| 下 | (1, 0) |
| 左 | (0, -1) |
| 右 | (0, 1) |
这些偏移量可以存入列表,通常称为“方向数组”。逐个取出 (di, dj),加到当前下标 (i, j) 上,就得到相邻位置的行、列下标。
一个内部像素的新值从哪里来?
原图:中心和上下左右,共 5 个值。
求平均:(100 + 0 + 50 + 200 + 50) ÷ 5 = 80
写入:新图的中心位置写 80;计算其他位置时仍读取原图。
下面从图中的中心位置出发,按上、下、左、右的顺序访问相邻元素:
python
image = [[100, 0, 100],
[50, 100, 200],
[50, 50, 100]]
i, j = 1, 1
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < len(image) and 0 <= nj < len(image[0]):
print(ni, nj, image[ni][nj])输出依次为 0 1 0、2 1 50、1 0 50、1 2 200;每行分别是相邻位置的行下标、列下标和值。if 用来防止下标越界,移动方向时不要把行和列写反。
交换行与判断边缘
text
原矩阵 交换第 1、2 行后
1 2 3 4 5 6
4 5 6 1 2 3
7 8 9 7 8 9交换后,只有中间的 2 不在边缘,所以边缘和是 43。题面行号从 1 开始,列表下标从 0 开始;换行时要先转换下标。
判断位置 (i, j) 是否为边缘,可以看它是否在首行、末行、首列或末列。每个位置只判断一次,四个角不会重复加。
想一想: 只有一行、一列或者一个元素时,哪些位置属于边缘?
从原图读取,写到新图
四周边缘保持不变。对于内部像素,从原图读取中心和上下左右五个值,将平均值舍入后写入新图。不能一边修改原图,一边继续使用它作为下一次计算的输入。
想一想: 如果先把左边的值改掉,下一格计算时还能读到原来的左邻居吗?只有一行或一列的图像有内部像素吗?
三、区间合并
对应 07 · 小鹿的工作和休息时间。
先按开始时间排序,再逐段观察:后一段的起点不大于当前段的终点,说明重叠或相接,可以合并;否则中间有空隙,需要另起一段。
把重叠或相接的时间段连起来
已按起点排序;点击“下一步”逐段观察。
[1, 4]
[3, 7]
[7, 9]
[11, 14]
015
0 / 5
注意右端点要取两段右端的较大值:例如 [1, 8] 包含 [2, 3],合并后仍是 [1, 8]。所有连续段合并后再比较长度;如果一样长,选开始较早的那一段。
想一想: [1, 3] 和 [3, 5] 为什么可以连起来?如果 [1, 4] 和 [6, 9] 长度一样,选哪一段?
四、字典的用法与排序
对应 03 · 成绩排序、08 · 字典练习、09 · 单词出现频率统计、10 · 网站网页访问统计 和 11 · 单词打分。
判断键、值和键值对
python
scores = {"a": 8, "b": 1, "c": 0}
print("c" in scores) # True:检查键
print(0 in scores.values()) # True:检查值
print(scores.get("b") == 1) # True:检查 b 对应的值注意 "1" 是字符串,1 是整数。读入键值对后,数字要转换成整数再判断。
按单词计数
使用字典可以为每个键累计次数:
python
counts = {}
for word in ["about", "send", "about", "me"]:
counts[word] = counts.get(word, 0) + 1
print(counts) # {'about': 2, 'send': 1, 'me': 1}get(word, 0) 在单词还没有出现时返回 0。第 9 题的单词一行一个,读到输入结束为止;第一行不是单词数量。
按多个条件排序
counts.items() 可以取出每个 (单词, 次数)。先按次数降序,再在次数相同时按单词升序:
python
counts = {"about": 2, "send": 1, "me": 1}
ranking = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
print(ranking) # [('about', 2), ('me', 1), ('send', 1)]item[0] 是单词,item[1] 是次数。次数前加负号,使出现更多的词排在前面;次数相同时再按单词升序比较。
成绩排序也使用相同思路:以 (-分数, 姓名) 为排序关键字。姓名没有保证唯一时,应先用列表保留每条成绩记录。
按网页累计时长
把时间统一换算成秒:小时 × 3600 + 分钟 × 60 + 秒,每次把结束秒数减去开始秒数,再累加到对应网页。
| 网页 | 访问与累计时长 |
|---|---|
| index.html | 6 + 183 = 189 秒 |
| study.html | 185 秒 |
单次最长的是 study.html,累计最长的是 index.html。
平均分需要总分和次数
同一个单词获得 2、8、8 分,平均分是 (2 + 8 + 8) / 3 = 6。如果另一个单词只获得一个 5 分,它的平均分是 5,更简单。
不要把“旧平均分 + 新分数”再除以 2,这样会给后来的评分更大权重。除了分数,还要保存给这个单词评分的人的 id,才能按题目要求输出。
想一想: 只保存最低分或总分,能直接判断哪个单词平均分最低吗?