Ruby如何合并两个具有略微不同标头的CSV文件

时间:2014-08-01 17:34:23

标签: ruby csv merge

我有两个带有一些常用标题的CSV文件,其他只出现在一个或另一个中的文件,例如:

# csv_1.csv
H1,H2,H3
V11,V22,V33
V14,V25,V35
# csv_2.csv
H1,H4
V1a,V4b
V1c,V4d

我想合并两者并获取一个新的CSV文件,该文件结合了以前CSV文件的所有信息。在需要时注入新列,并使用 null 值为新单元格提供。

结果示例:

H1,H2,H3,H4
V11,V22,V33,
V14,V25,V35,
V1a,,,V4b
V1c,,,V4d 

4 个答案:

答案 0 :(得分:3)

接受挑战:)

#!/usr/bin/env ruby
require "csv"

module MergeCsv
  class << self
    def run(csv_paths)
      csv_files = csv_paths.map { |p| CSV.read(p, headers: true) }
      merge(csv_files)
    end

    private

    def merge(csv_files)
      headers    = csv_files.flat_map(&:headers).uniq.sort
      hash_array = csv_files.flat_map(&method(:csv_to_hash_array))

      CSV.generate do |merged_csv|
        merged_csv << headers

        hash_array.each do |row|
          merged_csv << row.values_at(*headers)
        end
      end
    end

    # Probably not the most performant way, but easy
    def csv_to_hash_array(csv)
      csv.to_a[1..-1].map { |row| csv.headers.zip(row).to_h }
    end
  end
end

if(ARGV.length == 0)
  puts "Use: ruby merge_csv.rb <file_path_csv_1> <file_path_csv_2>"
  exit 1
end

puts MergeCsv.run(ARGV)

答案 1 :(得分:2)

简单的第一个回答:

如何使用它:

    listPart_A = CSV.read(csv_path_A, headers:true)

    listPart_B = CSV.read(csv_path_B, headers:true)

    listPart_C = CSV.read(csv_path_C, headers:true)

    list = merge(listPart_A,listPart_B,listPart_C)

功能:

def merge(*csvs)
    headers = csvs.map {|csv| csv.headers }.flatten.compact.uniq.sort
    csvs.flat_map(&method(:csv_to_hash_array))
end

def csv_to_hash_array(csv)
    csv.to_a[1..-1].map do |row|
      Hash[csv.headers.zip(row)]
    end
end

答案 2 :(得分:1)

我有答案,我只是想帮助那些正在寻找相同解决方案的人

require "csv"

module MergeCsv
  def self.run(csv_1_path, csv_2_path)
    merge(File.read(csv_1_path), File.read(csv_2_path))
  end

  def self.merge(csv_1, csv_2)
    csv_1_table = CSV.parse(csv_1, :headers => true)
    csv_2_table = CSV.parse(csv_2, :headers => true)

    return csv_2_table.to_csv if csv_1_table.headers.empty?
    return csv_1_table.to_csv if csv_2_table.headers.empty?

    headers_in_1_not_in_2 = csv_1_table.headers - csv_2_table.headers
    headers_in_1_not_in_2.each do |header_in_1_not_in_2|
      csv_2_table[header_in_1_not_in_2] = nil
    end

    headers_in_2_not_in_1 = csv_2_table.headers - csv_1_table.headers
    headers_in_2_not_in_1.each do |header_in_2_not_in_1|
      csv_1_table[header_in_2_not_in_1] = nil
    end

    csv_2_table.each do |csv_2_row|
      csv_1_table << csv_1_table.headers.map { |csv_1_header| csv_2_row[csv_1_header] }
    end

    csv_1_table.to_csv
  end
end

if(ARGV.length != 2)
  puts "Use: ruby merge_csv.rb <file_path_csv_1> <file_path_csv_2>"
  exit 1
end

puts MergeCsv.run(ARGV[0], ARGV[1])

以这种方式从控制台执行:

$ ruby merge_csv.rb csv_1.csv csv_2.csv 

欢迎任何其他可能更清洁的解决方案。

答案 3 :(得分:0)

我不得不做一些非常相似的事情
合并可能共享某些列但可能不共享某些列的n个CSV文件
如果您想保留结构并轻松进行操作,
我认为最好的方法是将其转换为哈希,然后重新转换为CSV文件

我的解决办法:

#!/usr/bin/env ruby
require "csv"

def join_multiple_csv(csv_path_array)
  return nil if csv_path_array.nil? or csv_path_array.empty?
  f = CSV.parse(File.read(csv_path_array[0]), :headers => true)
  f_h = {}
  f.headers.each {|header| f_h[header] = f[header]}
  n_rows = f.size
  csv_path_array.shift(1)
  csv_path_array.each do |csv_file|
    curr_csv = CSV.parse(File.read(csv_file), :headers => true)  
    curr_h = {}
    curr_csv.headers.each {|header| curr_h[header] = curr_csv[header]}
    new_headers = curr_csv.headers - f_h.keys
    exist_headers = curr_csv.headers - new_headers
    new_headers.each { |new_header|
      f_h[new_header] = Array.new(n_rows) + curr_csv[new_header]
    }
    exist_headers.each {|exist_header|
      f_h[exist_header] = f_h[exist_header] + curr_csv[exist_header]
    }
    n_rows = n_rows + curr_csv.size
  end
  csv_string = CSV.generate do |csv|
    csv << f_h.keys
    (0..n_rows-1).each do |i|
      row = []
      f_h.each_key do |header|
        row << f_h[header][i]
    end
    csv << row
  end  
    end
    return csv_string
end


if(ARGV.length < 2)
  puts "Use: ruby merge_csv.rb <file_path_csv_1> <file_path_csv_2> .. <file_path_csv_n>"
  exit 1
end

csv_str = join_multiple_csv(ARGV)
f = File.open("results.csv", "w")
f.write(csv_str)
puts "CSV merge is done"