[英]CSV iteration in Ruby, and grouping by column value to get last line of each group
我有一個交易數據的csv,列如下:
ID,Name,Transaction Value,Running Total,
5,mike,5,5,
5,mike,2,7,
20,bob,1,1,
20,bob,15,16,
1,jane,4,4,
etc...
我需要遍歷每一行並對交易值做一些事情,當我到達每個 ID 的最后一行時做一些不同的事情。
我目前做這樣的事情:
total = ""
id = ""
idHold = ""
totalHold = ""
CSV.foreach(csvFile) do |row|
totalHold = total
idHold = id
id = row[0]
value = row[2]
total = row[3]
if id != idHold
# do stuff with the totalHold here
end
end
但這有一個問題——它跳過了最后一行。 此外,關於它的某些東西感覺不對。 我覺得應該有更好的方法來檢測“ID”的最后一行。
有沒有辦法對 id 進行分組,然后檢測 id 組中的最后一項?
注意:所有 id 都在 csv 中組合在一起
是的.. ruby 支持你。
grouped = CSV.table('./test.csv').group_by { |r| r[:id] }
# Then process the rows of each group individually:
grouped.map { |id, rows|
puts [id, rows.length ]
}
提示:您可以使用CSV.table
作為哈希訪問每一行
CSV.table('./test.csv').first[:name]
=> "mike"
讓我們首先構建一個 CSV 文件。
str =<<~END
ID,Name,Transaction Value,Running Total
5,mike,5,5
5,mike,2,7
20,bob,1,1
20,bob,15,16
1,jane,4,4
END
CSVFile = 't.csv'
File.write(CSVFile, str)
#=> 107
我將首先創建一個接受兩個參數的方法:一個CSV::row實例和一個布爾值,用於指示 CSV 行是否是組的最后一個(如果是,則為true
)。
def process_row(row, is_last)
puts "Do something with row #{row}"
puts "last row: #{is_last}"
end
這個方法當然會被修改為執行需要為每一行執行的任何操作。
以下是處理文件的三種方法。 這三個都使用CSV::foreach方法來逐行讀取文件。 此方法使用兩個參數調用,文件名和選項 hash { header: true, converters: :numeric }
表示文件的第一行是標題行,並且表示數字的字符串將被轉換為適當的數字對象。 這里的"ID"
、 "Transaction Value"
和"Running Total"
將被轉換為整數。
盡管文檔中沒有提到它,但是當在沒有塊的情況下調用foreach
時,它會返回一個枚舉器(與IO::foreach 的方式相同)。
我們當然需要:
require 'csv'
將foreach
到Enumerable#chunk
我選擇使用chunk
,而不是Enumerable#group_by ,因為文件的行已經按ID
分組。
CSV.foreach(CSVFile, headers:true, converters: :numeric).
chunk { |row| row['ID'] }.
each do |_,(*arr, last_row)|
arr.each { |row| process_row(row, false) }
process_row(last_row, true)
end
顯示
Do something with row 5,mike,5,5
last row: false
Do something with row 5,mike,2,7
last row: true
Do something with row 20,bob,1,1
last row: false
Do something with row 20,bob,15,16
last row: true
Do something with row 1,jane,4,4
last row: true
注意
enum = CSV.foreach(CSVFile, headers:true, converters: :numeric).
chunk { |row| row['ID'] }.
each
#=> #<Enumerator: #<Enumerator::Generator:0x00007ffd1a831070>:each>
此枚舉器生成的每個元素都傳遞給塊,塊變量通過稱為數組分解的過程分配值:
_,(*arr,last_row) = enum.next
#=> [5, [#<CSV::Row "ID":5 "Name":"mike" "Transaction Value":5 "Running Total ":5>,
# #<CSV::Row "ID":5 "Name":"mike" "Transaction Value":2 "Running Total ":7>]]
結果如下:
_ #=> 5
arr
#=> [#<CSV::Row "ID":5 "Name":"mike" "Transaction Value":5 "Running Total ":5>]
last_row
#=> #<CSV::Row "ID":5 "Name":"mike" "Transaction Value":2 "Running Total ":7>
參見Enumerator#next 。
我遵循了對塊計算中使用的塊變量使用下划線的慣例(以提醒讀者您的代碼)。 請注意,下划線是有效的塊變量。 1
使用Enumerable#slice_when代替chunk
CSV.foreach(CSVFile, headers:true, converters: :numeric).
slice_when { |row1,row2| row1['ID'] != row2['ID'] }.
each do |*arr, last_row|
arr.each { |row| process_row(row, false) }
process_row(last_row, true)
end
這顯示了使用chunk
時產生的相同信息。
使用Kernel#loop 單步執行枚舉器CSV.foreach(CSVFile, headers:true)
enum = CSV.foreach(CSVFile, headers:true, converters: :numeric)
row = nil
loop do
row = enum.next
next_row = enum.peek
process_row(row, row['ID'] != next_row['ID'])
end
process_row(row, true)
這顯示了使用chunk
時產生的相同信息。 請參閱Enumerator#next和Enumerator#peek 。
enum.next
返回最后一個CSV::Row
對象后enum.peek
會產生一個StopIteration
異常。 正如其文檔中所解釋的, loop
通過跳出loop
處理該異常。 row
必須在進入循環之前初始化為任意值,以便在循環終止后可以看到row
。 那時row
包含文件最后一行的CSV::Row
對象。
1 IRB 將下划線用於其自身目的,導致在運行上述代碼時為塊變量_
分配了錯誤的值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.