在Ruby中如何对比两个CSV文件并删除CSV1中键匹配的行
Ruby CSV匹配删除行解决方案
原代码问题说明
你写的代码无法实现预期效果,存在两个核心错误:
each迭代器的返回值是被遍历的原数组本身,不是迭代过程中最后一个表达式的布尔结果,因此delete_if会永远拿到真值,导致csv1所有行被全部删除- 取值语法错误,csv2的行字段取值应该是
csv2row[:id],你写的csv2[:id]是取整个csv2表的id列,不是当前行的id值
正确实现方案
为了提升匹配效率,建议先把csv2中需要匹配的字段提取为Set集合,避免每次判断都遍历整个csv2表,数据量越大性能优势越明显。
单键匹配(仅匹配xyz_id和id)
如果你的匹配规则只要xyz_id等于csv2的id就算匹配,用以下代码:
require 'csv' require 'set' # 读取两个CSV文件 csv1 = CSV.table('./csv/csv1.csv', headers: true) csv2 = CSV.table('./csv/csv2.csv', headers: true) # 提前提取csv2的所有id存入集合 match_ids = csv2.map { |row| row[:id] }.to_set # 删除csv1中匹配的行 csv1.delete_if { |row| match_ids.include?(row[:xyz_id]) }
多键匹配(匹配id+姓名)
如果需要和示例一样同时匹配xyz_id、first_nm、last_nm三个字段才算匹配,调整为:
require 'csv' require 'set' csv1 = CSV.table('./csv/csv1.csv', headers: true) csv2 = CSV.table('./csv/csv2.csv', headers: true) # 提取多字段组合作为匹配键 match_keys = csv2.map { |row| [row[:id], row[:first_name], row[:last_name]] }.to_set csv1.delete_if do |row| current_key = [row[:xyz_id], row[:first_nm], row[:last_nm]] match_keys.include?(current_key) end
结果写入说明
- 你修改的只是内存中的csv1对象,不会自动同步到本地文件,必须手动写入
- 调试阶段不建议直接覆盖原文件,优先写入新文件避免原数据丢失,写入新文件代码如下:
# 写入新文件 File.open('./csv/csv1_filtered.csv', 'w') do |f| f.write(csv1.to_csv) end
- 确认逻辑完全正确后,可以直接写入原csv1路径覆盖原有内容,覆盖前建议备份原文件。
注意事项
CSV.table会自动把表头转换为小写的符号格式,如果你的实际CSV表头存在大小写、特殊符号,需要对应调整字段取值的键名- 如果csv2数据量很小,也可以直接在
delete_if里用any?判断,性能差异可以忽略:
csv1.delete_if do |csv1row| csv2.any? { |csv2row| csv1row[:xyz_id] == csv2row[:id] } end
内容的提问来源于stack exchange,提问作者nyphur
相关产品推荐
相关产品推荐

