簡體   English   中英

Ruby 中的 CSV 迭代,並按列值分組以獲取每組的最后一行

[英]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'

foreachEnumerable#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#nextEnumerator#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.

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