因此获得了此csv文件
User Data,,
name,age,gender
jhon,15,male
nat,14,female
,,,
Item Data,,
id,name,
1,book,
我想要的是什么样子
user_data=[{name: 'jhon', age: '15', gender: 'male'},{}]
item_data=[{id: 1, name: 'book'},{}]
据我所知,我如何在红宝石上使用CSV
库。但它仅解析要检测为标头的第一行。但是在这种情况下,我有2种标头,分别是user data
和item data
。我使用ray在Rails上使用rake任务将csv文件输入到数据库中
答案 0 :(得分:1)
Ruby CSV没有该功能,您需要自己实现该逻辑,因为CSV只是原始数据,它无法分辨什么是标题,什么不是。
就您而言,我看到您在下一个“部分”之前有一个空行,这应该使您的逻辑更容易。放置此逻辑的一个好地方是扩展CSV
以支持您的多个标头,例如
class CSV
def parse_with_multiple_headers
...
end
end
为了便于解析,我将删除User Data
和Item Data
并仅保留标题:
name,age,gender
jhon,15,male
nat,14,female
,,,
id,name,
1,book,
编辑
这是一个小型程序,可以执行您想要的操作。有很大的改进空间,但是您明白了:
require 'csv'
def is_empty(row)
row["name"] == nil && row["age"] == nil && row["gender"] == nil ? true : false
end
def csv_multiple_headers(data)
users, items, p_users = [], [], true
data.each do |row|
# control write to
if is_empty(row)
p_users = false
next
end
# skip next header row
row["name"] == "id" ? next : nil
if p_users
users.push({"name": row['name'],"age": row['age'],"gender": row['gender']})
else
items.push({"id": row["name"], "name": row["age"]})
end
end
puts users
puts items
end
src = "name,age,gender
jhon,15,male
nat,14,female
,,,
id,name,
1,book,
"
csv = CSV.parse(src, :headers => true)
csv_multiple_headers(csv)
答案 1 :(得分:1)
input = 'User Data,,
name,age,gender
jhon,15,male
nat,14,female
,,,
Item Data,,
id,name,
1,book,'
只需手动拆分,然后继续:
input.
split(/#{$/},,,#{$/}/). # split into multiple CSVs
map do |csv|
head, *csv = csv.split($/) # extract CSV titles
{head[/.*?(?=,)/].downcase.gsub(' ', '_') => csv.join($/)}
end.
reduce(&:merge). # make one hash
map { |k, v| [k, CSV.parse(v)] }. # proceed with prepared CSVs
to_h
#⇒ {"user_data"=>
# [["name", "age", "gender"],
# ["jhon", "15", "male"],
# ["nat", "14", "female"]],
# "item_data"=>
# [["id", "name", nil],
# ["1", "book", nil]]}
要获取哈希数组:
_.transform_values do |v|
v[1..-1].map { |row| v.first.zip(row).to_h }
end
#⇒ {"user_data"=>[
# {"name"=>"jhon", "age"=>"15", "gender"=>"male"},
# {"name"=>"nat", "age"=>"14", "gender"=>"female"}],
# "item_data"=>[
# {"id"=>"1", "name"=>"book", nil=>nil}]}
答案 2 :(得分:0)
这应该适用于具有任何新标题和值的以下结构:
require 'csv'
input = 'User Data,,
name,age,gender
jhon,15,male
nat,14,female
,,,
Item Data,,
id,name,
1,book,'
output_array = input.split(/,,,/).map do |named_csv|
name, csv = named_csv.split(/,,/).map(&:strip)
rows = CSV.parse(csv)
header = rows.shift
rows_collection = rows.map { |row| header.zip(row).to_h }
[name, rows_collection]
end
output_hash = output_array.to_h
user_data = output_hash.fetch('User Data')
item_data = output_hash.fetch('Item Data')
puts 'user_data: ' + user_data.to_s
puts 'item_data: ' + item_data.to_s
output> user_data: [{"name"=>"jhon", "age"=>"15", "gender"=>"male"}, {"name"=>"nat", "age"=>"14", "gender"=>"female"}]
output> item_data: [{"id"=>"1", "name"=>"book", nil=>nil}]
要避免使用nil
值,可以在compact
之前添加zip
方法调用。
答案 3 :(得分:0)
数据
这里是问题的数据,但经过了一些重新排列(原因将很清楚)。
data =<<_
input = 'Item Data,,
id,name,
1,book,'
,,,
User Data,,
name,age,gender
jhon,15,male
nat,14,female
_
让我们将其写入文件。
FNAME = 'temp.csv'
File.write(FNAME, data)
#=> 98
我们需要指定文件中数据块的顺序,以便正确分配变量user_data
和item_data
。
order = ['user data', 'item data']
第1步:清理和整理数据
文件有点dog's breakfast。它需要清洁和重新订购。这应视为单独的第一步。这里的目标是构造一个st数组,该ing数组的形式允许我们使用CSV
方法来构造感兴趣的哈希数组(的数组)。
csv_data = File.read(FNAME).
gsub(/\Ainput =\s+\'|\,+\'?$/, '').
split(/(?<=\n)\n/).
sort_by { |s| order.index(s.downcase[/\A.+?$/]) }.
map { |s| s.sub(/\A.+?\n/, '') }
#=> ["name,age,gender\njhon,15,male\nnat,14,female\n",
# "id,name\n1,book\n"]
第2步:使用CSV方法获得所需的结果
我们现在可以使用CSV methods获得所需的结果。
require 'csv'
user_data, item_data =
csv_data.map { |s| CSV.parse(s, headers: true, converters: :numeric,
header_converters: :symbol).map { |row| row.to_a.to_h } }
#=> [[{:name=>"jhon", :age=>15, :gender=>"male"},
# {:name=>"nat", :age=>14, :gender=>"female"}],
# [{:id=>1, :name=>"book"}]]
user_data
#=> [{:name=>"jhon", :age=>15, :gender=>"male"},
# {:name=>"nat", :age=>14, :gender=>"female"}]
item_data
#=> [{:id=>1, :name=>"book"}]]
清理和整理数据的步骤
此任务将使用三个正则表达式。
r1 = /
\A # match the beginning of the string
input[ ]=[ ]+\' # match 'input =', then >= 1 spaces then a single quote
| # or
\,+ # match >= 1 commas
\'? # optionally match a single quote
$ # match the end of the line
/x # free-spacing regex definition mode
r2 = /
(?<=\n) # match a newline in a positive lookbehind
\n # match a newline
/x
r3 = /
\A # match the beginning of the string
.+? # match > 0 characters, lazily
\n # match newline
/x
a = File.read(FNAME)
#=> "input = 'Item Data,,\n...female\n"
b = a.gsub(r1, '')
#=> "Item Data\nid,name\n1,book\n\nUser Data\n
# name,age,gender\njhon,15,male\nnat,14,female\n"
(我为打破可读性而任意破坏了上面的返回值(字符串)。
c = b.split(r2)
#=> ["Item Data\nid,name\n1,book\n",
# "User Data\nname,age,gender\njhon,15,male\nnat,14,female\n"]
d = c.sort_by { |s| order.index(s.downcase[r3].chomp) }
#=> ["User Data\nname,age,gender\njhon,15,male\nnat,14,female\n",
# "Item Data\nid,name\n1,book\n"]
csv_data = d.map { |s| s.sub(r3, '') }
#=> ["name,age,gender\njhon,15,male\nnat,14,female\n",
# "id,name\n1,book\n"]
用于构建哈希数组的步骤
enum = csv_data.map
#=> #<Enumerator: ["name,age,gender\njhon,15,male\nnat,14,female\n",
# "id,name\n1,book\n"]:map>
生成enum
的第一个元素并将其传递给块,并为块变量s
分配其值。
s = enum.next
#=> "name,age,gender\njhon,15,male\nnat,14,female\n"
现在执行块计算。
csv_tbl = CSV.parse(s, headers: true, converters: :numeric,
header_converters: :symbol)
#=> #<CSV::Table mode:col_or_row row_count:3>
在下面,我添加了一个puts
语句以显示中间计算的值。
user_data = csv_tbl.map do |row|
puts "row=#{row.inspect}, row.to_a=#{row.to_a}"
row.to_a.to_h
end
#=> [{:name=>"jhon", :age=>15, :gender=>"male"},
# {:name=>"nat", :age=>14, :gender=>"female"}]
将打印以下内容。 (我添加了换行符,以避免读者水平滚动。)
row=#<CSV::Row name:"jhon" age:15 gender:"male">,
row.to_a=[[:name, "jhon"], [:age, 15], [:gender, "male"]]
row=#<CSV::Row name:"nat" age:14 gender:"female">,
row.to_a=[[:name, "nat"], [:age, 14], [:gender, "female"]]
接下来,item
的计算如下所示。
s = enum_outer.next
#=> "id,name\n1,book\n"
其余计算与计算user_data
的计算相似。
两步法的优点
分两步执行此操作的重要原因是简化调试。第一步的初始测试是直观的,可以逐行进行。一旦数据采用适当的格式,第二步就很简单。
此外,如果将来要更改文件格式,则仅必须修改第一步。