【问题标题】:Ruby how to merge two CSV files with slightly different headersRuby如何合并两个标题略有不同的CSV文件
【发布时间】:2014-09-24 23:28:45
【问题描述】:

我有两个 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 

【问题讨论】:

    标签: ruby csv merge


    【解决方案1】:

    接受挑战 :)

    #!/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)
    

    【讨论】:

    • 它有效 :).. 您没有涵盖 CSV 文件中的一个仅包含 headers 行.. 或不包含 row 的情况和我一样,但我想这是一个非常具体的场景。
    • 简单的第一个答案:
    • 不错,让我窃取你的想法让我接受 n csvs,不只是 2。
    【解决方案2】:

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

    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】:

      简单的第一个答案:

      使用方法:

          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
      

      【讨论】:

        【解决方案4】:

        我不得不做一些非常相似的事情
        合并可能共享某些列但某些列可能不共享的 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"
        

        【讨论】:

          猜你喜欢
          • 2012-12-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-03-20
          • 2019-07-18
          • 2019-10-31
          • 2019-12-19
          • 2021-10-19
          相关资源
          最近更新 更多