Ruby中的CSV迭代,并按列值分组以获得每组的最后一行



我有一个交易数据的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 中分组在一起

是。。鲁比支持你了。

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方法逐行读取文件。此方法由两个参数调用,即文件名和选项散列{ header: true, converters: :numeric },该散列指示文件的第一行是头行,表示数字的字符串将被转换为适当的数字对象。这里,"ID""Transaction Value""Running Total"的值将被转换为整数。

虽然文档中没有提到它,但当在没有块的情况下调用foreach时,它会返回一个枚举器(与IO::foreach相同(。

我们当然需要:

require 'csv'

foreach到可枚举#区块

我选择使用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>

请参阅枚举器#下一页。

我遵循了对块计算中使用的块变量使用下划线的惯例(以提醒读者注意您的代码(。请注意,下划线是一个有效的块变量1

替换chunk时使用可枚举#slice_

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#循环逐步遍历枚举器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时产生的信息相同的信息。请参阅枚举器#next和枚举器#peek。

enum.next返回最后一个CSV::Row对象后,enum.peek将生成一个StopIteration异常。正如文档中所解释的,loop通过中断循环来处理该异常。在进入循环之前,row必须初始化为任意值,以便在循环终止后row可见。届时,row将包含文件最后一行的CSV::Row对象。

1 IRB将下划线用于其自身目的,导致在运行上述代码时,块变量_被分配了一个错误的值

最新更新