--- tid: 32iv5d title: Pb List Merge --- # 案例:表格合并 List Merge (date: 2025-11-18) **学习建议**:建议先完成 [`dict-tutorial-1.md`](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] ] ``` ### 初步思路 先按行打印数组,看看怎么做? ```python >>> for student in chinese_scores: ... print(student) ... # 语文 # ['张三', 'S001', 85] # ['李四', 'S002', 92] # ['王五', 'S003', 78] ``` **练习要求**: 继续完成数学和英语科目的表格打印 ``````{only} show_answer ``` >>> for student in math_scores: ... print(student) ... >>> for student in english_scores: ... print(student) ``` `````` 数学 ``` ['张三', 'S001', 90] ['李四', 'S002', 87] ['王五', 'S003', 85] ``` 英语 ``` ['S001', '张三', 88] ['S002', '李四', 85] ['S003', '王五', 80] ``` **结论**: 先把表格中的字段按相同的顺序排列,补齐可能的缺失值,然后合并。 ### 需求分析 用表格法 (二维 List) 方法来合并成绩,怎样解决问题? 1. 合并前字段要统一排序,不能有乱序 1. 不能有成员缺失的情况,否则无法合并 (暂时不存在) 1. 选择合并的主键必须是唯一的,否则合并结果会出错(姓名是不合适的,学号是合适的) 1. 使用下标索引访问数据 ## 调整数据的顺序 ### 阶段性期望结果 首先需要统一所有数据表的字段顺序,这里我们选择以学号为主键, 希望得到下面的效果: ```python # 统一为 [学号, 姓名, 成绩] 的顺序 chinese_scores = [["S001", "张三", 85], ["S002", "李四", 92], ["S003", "王五", 78]] math_scores = [["S001", "张三", 90], ["S002", "李四", 87], ["S003", "王五", 85]] english_scores = [["S001", "张三", 88], ["S002", "李四", 85], ["S003", "王五", 80]] ``` ### 核心观察 1. 对于语文和数学,记录顺序为: ```python name, sid, score = record ``` 2. 对于英语科目,记录顺序为: ```python sid, name, score = record ``` 3. 英语不变,但语文和数学两科要交换 sid, name 的顺序 ```python ordered_record = sid, name, score ``` 一次性把**语文**的顺序调整了: ```python for record in chinese_scores: name, sid, score = record ordered_record = sid, name, score ``` 采用**解包**的写法更简洁: ```python 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列(行列转换) ```python def select_column(table, index): colmun = [] for row in table: column.append(row[index]) return column ``` 用 List comprehension 改写: ```python def select_column(table, index): return [row[index] for row in table] ``` (2) 拼合 ```python 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列取出来,重新组合 ```python 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() 实现行列转换 ```python table = zip(*columns) for record in table: print(record) ``` **总结**: 从excel 表格处理的角度讲,这个解法最直观,但最后一步需要对结果进行行列转换,对有些人来讲则最难理解。 ### 解法2 查找写入法 为了避免行列转换,可采起查找写入法,其核心是: 主表要实现向某个指定学号第 i 个位置写入 score ```python # 向某个学号写入第 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) ```python # 行索引表 # 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 简洁但抽象(选学) 把数据弄整齐需要的工具: 解包 ```python # 使用解包重新排列字段顺序 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 循环 ```python # 使用循环合并数据 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` 函数来简化代码: ```python # 使用 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')` - 这样可以在一个循环中同时处理多个列表的对应元素