📝

Ruby CSVで重複したヘッダーを置換

2022/04/29に公開

はじめに

1行目をヘッダーとするCSVファイルで、重複しているものをRubyで取り込む機会がありました。
その際、重複しているヘッダーの項目も取得できるように重複しているものを置換する方法を調査しました。
(最終的にはCSVファイルのヘッダーを重複しないように出力してもらうようになったので、実際に使用することはなかったです)

環境

  • Ruby 3.1.1

コード

require 'csv'

raw_data = "a,b,b,c,d,,e\naa,bb,bbbb,cc,dd,,ee"
csv_table = CSV.new(raw_data, headers: :first_row).read

headers = csv_table.headers
duplicated_headers = headers.select { |header| headers.count(header) > 1 }.uniq

convert_header_map = {}
convert_header_seq = {}
headers.each_with_index do |header, index|
  if duplicated_headers.include?(header)
    seq = convert_header_seq[header]
    seq = seq.nil? ? 1 : seq + 1
    convert_header_map[index] = "#{header}_#{seq}"
    convert_header_seq[header] = seq
  end
end

header_converters = [lambda { |field, field_info| convert_header_map[field_info.index] || field }]
csv_table2 = CSV.new(raw_data, headers: :first_row, header_converters: header_converters).read
irb(main):068:0> csv_table2[0]
=> #<CSV::Row "a":"aa" "b_1":"bb" "b_2":"bbbb" "c":"cc" "d":"dd" nil:nil "e":"ee">
irb(main):069:0> csv_table2[0]['b_1']
=> "bb"
irb(main):070:0> csv_table2[0]['b_2']
=> "bbbb"

内容

1回CSVを読み込み、重複しているヘッダーを抽出

csv_table = CSV.new(raw_data, headers: :first_row).read

headers = csv_table.headers
duplicated_headers = headers.select { |header| headers.count(header) > 1 }.uniq

重複しているヘッダーに対して、変換用のハッシュを生成

convert_header_map = {}
convert_header_seq = {}
headers.each_with_index do |header, index|
  if duplicated_headers.include?(header)
    seq = convert_header_seq[header]
    seq = seq.nil? ? 1 : seq + 1
    convert_header_map[index] = "#{header}_#{seq}"
    convert_header_seq[header] = seq
  end
end

変換用ハッシュを使用し、ヘッダーを変換を指定してCSVを再度読み込み

ヘッダーの変換はheader_convertersで指定
https://docs.ruby-lang.org/ja/latest/method/CSV/i/header_converters.html

header_converters = [lambda { |field, field_info| convert_header_map[field_info.index] || field }]
csv_table2 = CSV.new(raw_data, headers: :first_row, header_converters: header_converters).read

まとめ

1度CSVを読み込んだあと、ヘッダーが重複する場合はヘッダーを置換する対象を取得し、再度CSVの読み込みでヘッダーを置換する方法にしてみました。
2回CSVの全体を読み込んでいるので、もっとよい方法があると思いますがパッと見当たらなかったので、一旦はこの方法で。

Discussion