簡體   English   中英

Ruby,如何解析具有多行數據類型的csv文件

[英]Ruby, how to parse csv file with multiple row data type

所以給定這個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庫。 但它僅解析要檢測為標頭的第一行。 但是在這種情況下,我有兩種標題,分別是user dataitem data 即時通訊使用Ruby在Rails上使用耙任務將csv文件輸入數據庫

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)
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}]}

這適用於具有任何新標題和值的以下結構:

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值,您可以在zip之前添加compact方法調用。

數據

這是來自問題的數據,但已進行了一些重新排列(原因將很清楚)。

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:清理和整理數據

該文件有點像狗的早餐 它需要清潔和重新訂購。 這應視為單獨的第一步。 這里的目標是構造一個st數組,其形式使我們可以使用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方法來獲得所需的結果。

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計算相似。

兩步走法的優點

分兩步執行此操作的一個重要原因是簡化調試。 第一步的初始測試是直觀的,可以逐行進行。 一旦數據采用適當的格式,第二步就很簡單。

同樣,如果將來文件格式發生更改,則僅必須修改第一步。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM