Ruby,如何解析具有多行数据类型的csv文件

时间:2018-08-20 05:54:59

标签: ruby-on-rails ruby

因此获得了此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 dataitem data。我使用ray在Rails上使用rake任务将csv文件输入到数据库中

4 个答案:

答案 0 :(得分:1)

Ruby CSV没有该功能,您需要自己实现该逻辑,因为CSV只是原始数据,它无法分辨什么是标题,什么不是。

就您而言,我看到您在下一个“部分”之前有一个空行,这应该使您的逻辑更容易。放置此逻辑的一个好地方是扩展CSV以支持您的多个标头,例如

class CSV
  def parse_with_multiple_headers
    ...
  end
end

为了便于解析,我将删除User DataItem 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_dataitem_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的计算相似。

两步法的优点

分两步执行此操作的重要原因是简化调试。第一步的初始测试是直观的,可以逐行进行。一旦数据采用适当的格式,第二步就很简单。

此外,如果将来要更改文件格式,则仅必须修改第一步。