在不使用表格的情况下加入 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::Table
objects
阅读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
写入输出文件。
接下来,对于满足条件的每个行索引i
(i = 0
对应每个文件中header行之后的第一行),我们写为单行 csv1[i]
和 csv2[i]
的元素在 headers1
和 headers2
中具有 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) 连接,或者您可以根据某些条件过滤或组合它们。选择实际上取决于您的用例。
我有 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::Table
objects
阅读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
写入输出文件。
接下来,对于满足条件的每个行索引i
(i = 0
对应每个文件中header行之后的第一行),我们写为单行 csv1[i]
和 csv2[i]
的元素在 headers1
和 headers2
中具有 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) 连接,或者您可以根据某些条件过滤或组合它们。选择实际上取决于您的用例。