我借鉴了 Dimitrius 使用保留字符表示数组的想法,并通过创建自定义数据转换器将包含保留字符的字符串转换为数组,然后将其元素转换为适当的数据类型,对此进行了扩展.
数据
arr = [{ name: 'Dimitrius', errors: ['e1', 3, 4.1] },
{ name: 'Wilma', errors: ['f1', 7, 9.5] }]
写入 CSV 文件
require 'csv'
FName = 't.csv'
我假设如果 CSV 文件中逗号之间的子字符串包含指定的保留字符,则它表示一个数组,保留字符分隔数组的元素。
RESERVED_CHAR = '|'
keys = arr.first.keys
#=> [:name, :errors]
rows = arr.map(&:values)
#=> [["Dimitrius", ["e1", 3, 4.1]],
# ["Wilma", ["f1", 7, 9.5]]]
CSV.open(FName, "wb") do |csv|
csv << keys
rows.each { |name,arr| csv << [name, arr.join(RESERVED_CHAR)] }
end
#=> [["Dimitrius", ["e1", 3, 4.1]], ["Wilma", ["f1", 7, 9.5]]]
让我们看看写了什么。
File.read(FName).each_line { |line| p line }
"name,errors\n"
"Dimitrius,e1|3|4.1\n"
"Wilma,f1|7|9.5\n"
读取 CSV 文件
CSV 类有六个内置数据转换器(在“常量”下查看)。但是,可以根据需要添加额外的转换器。这些转换行中的值,但不转换标题。
require 'csv'
FName = 't.csv'
RESERVED_CHAR = '|'
CSV::Converters[:array] = ->(v) do
if v.include?(RESERVED_CHAR)
v.split(RESERVED_CHAR).map do |e|
case e
when /\A\-?\d+\z/
e.to_i
when /\A\-?\d+\.\d+\z/
e.to_f
else
e
end
end
else
v
end
end
我们现在可以将 CSV 文件读入 CSV::Table 对象:
tbl = CSV.read(FName, headers: true, converters: :array)
#=> #<CSV::Table mode:col_or_row row_count:3>
我们可以使用它来提取标题和行:
headers, *body = tbl.to_a
#=> [["name", "errors"],
# ["Dimitrius", ["e1", 3, 4.1]],
# ["Wilma", ["f1", 7, 9.5]]]
headers
#=> ["name", "errors"]
body
#=> [["Dimitrius", ["e1", 3, 4.1]],
# ["Wilma", ["f1", 7, 9.5]]]
或个别列:
tbl["name"]
#=> ["Dimitrius", "Wilma"]
tbl["errors"]
#=> [["e1", 3, 4.1],
# ["f1", 7, 9.5]]
请注意,数组中的值已根据需要进行了转换,并且标题现在是字符串。如果它们要成为最后需要单独步骤的符号。