在不使用表格的情况下加入 Ruby 中的两个 CSV 文件

Join two CSV files in Ruby without using tables

我有 2 个 CSV 文件,其中包含 A, B, C.. 和 D, E, F 等列。我想将这两个 CSV 文件合并到一个新文件中,其中包含 File1.B = File2.E 行,并且该行将包含 A, B/E, C, D, F 列。如何在不使用表的情况下实现此 JOIN?

如果您有这样的 CSV 文件:

first.csv:

A | B | C
1 | 1 | 1
2 | 2 | 2
3 | 4 | 5
6 | 9 | 9

second.csv:

D  | E | F
21 | 1 | 41
22 | 5 | 42
23 | 8 | 45
26 | 9 | 239

你可以这样做:

require 'csv'

first = CSV.read('first.csv')
second = CSV.read('second.csv')

CSV.open("result.csv", "w") do |csv|
  csv << %w[A B.E C D F]
  first.each do |rowF|
    second.each do |rowS|
      csv << [rowF[0],rowF[1],rowF[2],rowS[0],rowS[2]] if rowF[1] == rowS[1]
    end
  end

end

得到这个:

result.csv:

A | B.E | C | D  | F
1 | 1   | 1 | 21 | 41
6 | 9   | 9 | 26 | 239

吉文斯

我们得到以下内容。

两个输入文件的路径:

fname1 = 't1.csv'
fname2 = 't2.csv'

输出文件的路径:

fname3 = 't3.csv'

要在两个输入文件中匹配的 header 的名称:

target1 = 'B'
target2 = 'E'

我假设(与示例中的情况一样)这两个文件必然包含相同的行数。

创建测试文件

让我们先创建两个文件:

str = [%w|A B C|, %w|1 1 1|, %w|2 2 2|, %w|3 4 5|, %w|6 9 9|].
        map { |a| a.join(",") }.join("\n")
  #=> "A,B,C\n1,1,1\n2,2,2\n3,4,5\n6,9,9"
File.write(fname1, str)
  #=> 29

str = [%w|D E F|, %w|21 1 41|, %w|22 5 42|, %w|23 8 45|, %w|26 9 239|].
        map { |a| a.join(",") }.join("\n")
  #=> "D,E,F\n21,1,41\n22,5,42\n23,8,45\n26,9,239" 
File.write(fname2, str)
  #=> 38

将输入文件读入CSV::Tableobjects

阅读fname1时,我将使用:header_converters选项将header "B"转换为"B/E"。请注意,这不需要知道带有 header "B"(或任何可能的内容)的列的位置。

require 'csv'

new_target1 = target1 + "/" + target2
  #=> "B/E"

csv1 = CSV.read(fname1, headers: true,
  header_converters: lambda { |header| header==target1 ? new_target1 : header})
csv2 = CSV.read(fname2, headers: true)

构造要从每个输入文件写入的 header 的数组

headers1 = csv1.headers
  #=> ["A", "B/E", "C"]
headers2 = csv2.headers - [target2]
  #=> ["D", "F"]

创建输出文件

我们首先将新的headers headers1 + headers2写入输出文件。

接下来,对于满足条件的每个行索引ii = 0对应每个文件中header行之后的第一行),我们写为单行 csv1[i]csv2[i] 的元素在 headers1headers2 中具有 header 的列中。写索引i处的行要满足的条件是i满足:

csv1[i][new_target1] == csv2[i][target2] #=> true

现在打开 fname3 进行写入,写入 header,然后写入 body。

CSV.open(fname3, 'w') do |csv|
  csv << headers1 + headers2
  [csv1.size, csv2.size].min.times do |i|
    csv << (headers1.map { |h| csv1[i][h] } +
            headers2.map { |h| csv2[i][h] }) if
             csv1[i][new_target1] == csv2[i][target2]
  end
end
  #=> 4

让我们确认所写内容是否正确。

puts File.read(fname3)
A,B/E,C,D,F
1,1,1,21,41
6,9,9,26,239

答案是使用 group by 创建哈希 [​​=17=],然后迭代哈希 table 的键。假设您加入的专栏在每个 table:

中都是唯一的
join_column = "whatever"
csv1 = CSV.table("file1.csv").group_by { |r| r[join_column] }
csv2 = CSV.table("file2.csv").group_by { |r| r[join_column] }

joined_data = csv1.keys.sort.map do |join_column_values|
  csv1[join_column].first.merge(csv2[join_column].first)
end

如果列在每个 table 中都不是唯一的,那么您需要决定如何处理这些情况,因为数组 csv1[join_column] 中的第一个元素和csv2[join_column]。您可以按照其他答案之一(即嵌套地图调用)中的建议进行 O(mxn) 连接,或者您可以根据某些条件过滤或组合它们。选择实际上取决于您的用例。