案例:表格合并 List Merge¶
(date: 2025-11-18)
学习建议:建议先完成 dict-tutorial-1.md 的基础学习,再学习本案例。
提出问题,需求分析,技术方案,掌握工具,实践验证——五大环节,完成一个案例。
学习目标:
使用二维 List 解决 Table 类型数据的合并问题
练习解包, zip 等操作
体会 List 的能力 (优点与缺点),为进一步学习 Dict 作准备
问题与分析¶
原数据¶
# 语文老师提供
chinese_scores = [["张三", "S001", 85], ["李四", "S002", 92], ["王五", "S003", 78]]
# 数学老师提供
math_scores = [["张三", "S001", 90], ["李四", "S002", 87], ["王五", "S003", 85]]
# 英语老师提供
english_scores = [["S001", "张三", 88], ["S002", "李四", 85], ["S003", "王五", 80]]
期望的结果¶
期望得到一个合并后的成绩表,包含每个学生的所有科目成绩:
[
["学号", "姓名", "语文", "数学", "英语"],
["S001", "张三", 85, 90, 88],
["S002", "李四", 92, 87, 85],
["S003", "王五", 78, 85, 80]
]
初步思路¶
先按行打印数组,看看怎么做?
>>> for student in chinese_scores:
... print(student)
...
# 语文
# ['张三', 'S001', 85]
# ['李四', 'S002', 92]
# ['王五', 'S003', 78]
练习要求: 继续完成数学和英语科目的表格打印
数学
['张三', 'S001', 90]
['李四', 'S002', 87]
['王五', 'S003', 85]
英语
['S001', '张三', 88]
['S002', '李四', 85]
['S003', '王五', 80]
结论: 先把表格中的字段按相同的顺序排列,补齐可能的缺失值,然后合并。
需求分析¶
用表格法 (二维 List) 方法来合并成绩,怎样解决问题?
合并前字段要统一排序,不能有乱序
不能有成员缺失的情况,否则无法合并 (暂时不存在)
选择合并的主键必须是唯一的,否则合并结果会出错(姓名是不合适的,学号是合适的)
使用下标索引访问数据
调整数据的顺序¶
阶段性期望结果¶
首先需要统一所有数据表的字段顺序,这里我们选择以学号为主键, 希望得到下面的效果:
# 统一为 [学号, 姓名, 成绩] 的顺序
chinese_scores = [["S001", "张三", 85], ["S002", "李四", 92], ["S003", "王五", 78]]
math_scores = [["S001", "张三", 90], ["S002", "李四", 87], ["S003", "王五", 85]]
english_scores = [["S001", "张三", 88], ["S002", "李四", 85], ["S003", "王五", 80]]
核心观察¶
对于语文和数学,记录顺序为:
name, sid, score = record
对于英语科目,记录顺序为:
sid, name, score = record
英语不变,但语文和数学两科要交换 sid, name 的顺序
ordered_record = sid, name, score
一次性把语文的顺序调整了:
for record in chinese_scores:
name, sid, score = record
ordered_record = sid, name, score
采用解包的写法更简洁:
for name, sid, score in chinese_scores:
new_record = sid, name, score
练习: 用解包的方法调整数学的顺序
调序函数¶
将以上重排序写成函数,方便将相同的逻辑应用到不同的数据 (数学):
def rearrange(scores):
ordered_records = []
for name, sid, score in scores:
ordered_records.append((sid, name, score))
rearrange(chinese_scores)
练习: 采用函数调整数学科目的顺序
rearrange(math_scores)
整合数据¶
输入为整齐的三个数据表¶
# 语文
['S001', '张三', 85]
['S002', '李四', 92]
['S003', '王五', 78]
# 数学
['S001', '张三', 90]
['S002', '李四', 87]
['S003', '王五', 85]
# 英语
['S001', '张三', 88]
['S002', '李四', 85]
['S003', '王五', 80]
如何合并?¶
取语文表的第1,2,3列,数学的第3列,英语的第3列,拼合。下面涉及到3种不同的思路和方法。
方法1 行列转换法¶
从 Excel 的解法类比入手(图示)
(1) 整体取其中某1列(行列转换)
def select_column(table, index):
colmun = []
for row in table:
column.append(row[index])
return column
用 List comprehension 改写:
def select_column(table, index):
return [row[index] for row in table]
(2) 拼合
column1 = select_column(chinese_scores, 0)
column2 = select_column(chinese_scores, 1)
column3 = select_column(chinese_scores, 2)
column4 = select_column(math_scores, 2)
column5 = select_column(english_scores, 2)
columns = [column1, column2, column3, column4, column5]
# [学号行, 姓名行, 分数1行, 分数2行]
(3) 行列转换,二层 for 循环,把第i行,第j列取出来,重新组合
table = [[None] * len(columns) for _ in range(len(columns[0]))] # 3个人,每人5个记录
for i in range(len(columns)): # 5列
column = columns[i]
for j in range(len(column)): # 3行
table[j][i] = column[j]
print("使用循环转换的结果:")
for record in table:
print(record)
练习:将解法1的步骤 (1), (2), (3) 所述思路整合成可以运行的正确代码
(4) 优化: 使用 zip() 实现行列转换
table = zip(*columns)
for record in table:
print(record)
总结: 从excel 表格处理的角度讲,这个解法最直观,但最后一步需要对结果进行行列转换,对有些人来讲则最难理解。
解法2 查找写入法¶
为了避免行列转换,可采起查找写入法,其核心是: 主表要实现向某个指定学号第 i 个位置写入 score
# 向某个学号写入第 i 个成绩
append_score_to_table(merged_tables, sid, score)
然后,顺序将语文、数学、英语分数表中的每个成绩按 sid 插入
# 初始化:用语文表创建基础结构
merged_table = []
for name, sid, score in chinese_scores:
merged_table.append([sid, name, score]) # [学号, 姓名, 语文]
for sid, name, score in math_scores:
append_score_to_table(merged_table, sid, score)
for sid, name, score in english_scores:
append_score_to_table(merged_table, sid, score)
提示: 可以对上面重复的代码重构
定义和实现核心 api: append_score_to_table(merged_table, sid, score)
# 行索引表
# s001 -> 0
# s002 -> 1
# s003 -> 2
def append_score_to_table(merged_table, sid, score):
for i, record in enumerate(merged_table):
if sid in record:
merged_table[i].append(score)
练习:将解法2的思路整合编写成可以运行的正确代码,保持思路不变
解法3 简洁但抽象(选学)¶
把数据弄整齐需要的工具: 解包
# 使用解包重新排列字段顺序
def rearrange_scores(scores_list, order_type):
"""根据类型重新排列字段顺序"""
rearranged = []
for item in scores_list:
if order_type == "name_first": # [姓名, 学号, 成绩]
name, sid, score = item
rearranged.append([sid, name, score])
elif order_type == "sid_first": # [学号, 姓名, 成绩]
sid, name, score = item
rearranged.append([sid, name, score])
return rearranged
# 重新排列数据
chinese_rearranged = rearrange_scores(chinese_scores, "name_first")
math_rearranged = rearrange_scores(math_scores, "name_first")
english_rearranged = rearrange_scores(english_scores, "sid_first")
将数据整合的工具: for in 循环
# 使用循环合并数据
def merge_scores(chinese_list, math_list, english_list):
"""合并多个成绩列表"""
merged = []
# 添加表头
merged.append(["学号", "姓名", "语文", "数学", "英语"])
# 假设三个列表的顺序是一致的,按顺序遍历
for i in range(len(chinese_list)):
chinese_item = chinese_list[i]
math_item = math_list[i]
english_item = english_list[i]
sid, name, chinese_score = chinese_item
_, _, math_score = math_item
_, _, english_score = english_item
# 验证学号和姓名是否一致
if (chinese_item[0] == math_item[0] == english_item[0] and
chinese_item[1] == math_item[1] == english_item[1]):
merged.append([sid, name, chinese_score, math_score, english_score])
else:
print(f"数据不一致: {chinese_item} vs {math_item} vs {english_item}")
return merged
# 合并数据
final_result = merge_scores(chinese_rearranged, math_rearranged, english_rearranged)
print(final_result)
进阶:使用 zip 的简化版本(选学)¶
如果你已经掌握了基础循环,可以学习使用 zip 函数来简化代码:
# 使用 zip 合并数据(进阶版本)
def merge_scores_with_zip(chinese_list, math_list, english_list):
"""使用 zip 合并多个成绩列表"""
merged = []
# 添加表头
merged.append(["学号", "姓名", "语文", "数学", "英语"])
# 使用 zip 并行遍历所有列表
for chinese_item, math_item, english_item in zip(chinese_list, math_list, english_list):
sid, name, chinese_score = chinese_item
_, _, math_score = math_item
_, _, english_score = english_item
# 验证学号和姓名是否一致
if (chinese_item[0] == math_item[0] == english_item[0] and
chinese_item[1] == math_item[1] == english_item[1]):
merged.append([sid, name, chinese_score, math_score, english_score])
else:
print(f"数据不一致: {chinese_item} vs {math_item} vs {english_item}")
return merged
# 使用 zip 版本合并数据
final_result_zip = merge_scores_with_zip(chinese_rearranged, math_rearranged, english_rearranged)
print("使用 zip 版本的结果:")
print(final_result_zip)
zip 函数说明:
zip函数可以将多个列表"打包"在一起,同时遍历比如
zip([1,2,3], ['a','b','c'])会生成(1,'a'), (2,'b'), (3,'c')这样可以在一个循环中同时处理多个列表的对应元素