[英]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 data
和item data
。 即時通訊使用Ruby在Rails上使用耙任務將csv文件輸入數據庫
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)
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_data
和item_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.