CSV - 处理对某些字段具有相同值的每组连续行

  • 本文关键字:连续 处理 字段 CSV ruby
  • 更新时间 :
  • 英文 :


我有一个大的CSV文件,其中包含以下标题:"sku","年份","颜色","价格","折扣","库存","published_on","费率","人口统计"和"标签"。

我想对具有相同"sku"、"year"和"color"值的每个连续行组执行各种计算。我将文件的此分区称为每组行。例如,如果文件如下所示:

sku,year,color,price,discount,...
100,2019,white,24.61,2.3,...
100,2019,white,29.11,2.1,...
100,2019,white,33.48,2.9,...
100,2019,black,58.12,1.3,...
200,2018,brown,44.15,3.1,...
200,2018,brown,53.07,3.2,...
100,2019,white,16.91,2.9,...

将有四组行:第 1、2 和 3 行(在标题行之后(、单独的第 4 行、第 5 行和第 6 行以及单独的第 7 行。请注意,第一组中不包含最后一行,即使前三个字段的值相同。那是因为它与第一组不相邻。

可以对每组行执行的计算的一个示例是确定该组的总库存。通常,要计算的度量值是行组的所有行中包含的值的某个函数。每组行的具体计算不是我的问题的核心。让我们简单地假设每组行都传递给某个返回感兴趣度量的方法。

我希望返回一个数组,每组行包含一个元素,每个元素(可能是数组或哈希(包含"sku"、"year"和"color"的常见值以及计算出的兴趣度量。

因为文件很大,所以必须逐行读取,而不是将其吞噬到数组中。

最好的方法是什么?

Enumerator#chunk

非常适合此。

CSV.foreach('path/to/csv', headers: true).
chunk { |row| row.values_at('sku', 'year', 'color') }.
each do |(sku, year, color), rows|
# process `rows` with the current `[sku, year, color]` combination
end

显然,最后一个each可以根据需要替换为mapflat_map

下面是如何做到这一点的示例。我将逐行读取 CSV 文件以最大程度地减少内存需求。

法典

require 'csv'
def doit(fname, common_headers)
CSV.foreach(fname, headers: true).
slice_when { |csv1,csv2| csv1.values_at(*common_headers) !=
csv2.values_at(*common_headers) }.
each_with_object({}) { |arr,h|
h[arr.first.to_h.slice(*common_headers)] = calc(arr) }
end

def calc(arr)
arr.sum { |csv| csv['price'].to_f }.fdiv(arr.size).round(2)
end

需要针对应用程序自定义方法calc。在这里,我计算的是每个连续记录组的平均价格,这些记录对"sku""year""color"具有相同的值。

请参阅 CSV::foreach、Enumerable#slice_when、CSV::Row#values_at、CSV::Row#to_h 和 Hash#slice。

现在让我们构造一个 CSV 文件。

str =<<~END
sku,year,color,price
1,2015,red,22.41
1,2015,red,33.61
1,2015,red,12.15
1,2015,blue,36.18
2,2015,yellow,9.08
2,2015,yellow,13.71
END
fname = 't.csv'
File.write(fname, str)
#=> 129

必须给出通用标头:

common_headers = ['sku', 'year', 'color']

平均价格是通过执行doit获得的:

doit(fname, common_headers)
#=> {{"sku"=>"1", "year"=>"2015", "color"=>"red"}=>22.72,
#    {"sku"=>"1", "year"=>"2015", "color"=>"blue"}=>36.18,
#    {"sku"=>"2", "year"=>"2015", "color"=>"yellow"}=>11.4}

注意:

((22.41 + 33.61 + 12.15)/3).round(2)
#=> 22.72
((36.18)/1).round(2)
#=> 36.18 
((9.08 + 13.71)/2).round(2)
#=> 11.4 

方法foreachslice_when都返回枚举器。因此,对于文件中每个连续的行块,common_headers中的键具有相同的值,将获取内存,对这些行执行计算,然后释放该内存(由 Ruby(。此外,还需要内存来保存最后返回的哈希。

最新更新