我有一个交易数据的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将下划线用于其自身目的,导致在运行上述代码时,块变量_
被分配了一个错误的值