如何加速我的Ruby / Rake任务,它计算300K日期字符串中日期的出现次数?

时间:2011-10-07 15:51:57

标签: ruby-on-rails ruby performance optimization rake

我有一个300K字符串的数组,代表日期:

date_array = [
  "2007-03-25 14:24:29",
  "2007-03-25 14:27:00",
  ...
]

我需要计算此数组中每个日期的出现次数(例如,“2011-03-25”的所有日期字符串)。确切的时间无关紧要 - 只是日期。我知道文件中的日期范围。所以我有:

Date.parse('2007-03-23').upto Date.parse('2011-10-06') do |date_to_count|
  count = 0
  date_array.each do |date_string|
    if Date.parse(date_string) >= date_to_count && 
       Date.parse(date_string) <= date_to_count
      count += 1
    end
  end
  puts "#{date_to_count} occurred #{count} times."
end

在我的机器上计算仅一个日期的出现时间超过60秒。我可以通过哪些方式优化此任务的性能?

可能有用的注释:我正在使用Ruby 1.9.2。此脚本使用rake 0.9.2在Rake任务中运行。从{CSV}文件加载date_array。在每次迭代中,count将保存为我的Rails项目数据库中的记录。

2 个答案:

答案 0 :(得分:5)

是的,如果日期格式相同,则根本不需要解析日期。了解您的数据是您可以拥有的最强大的工具之一。

如果日期时间字符串都采用相同的格式(yyyy-mm-dd HH:MM:SS),那么您可以执行类似

的操作
data_array.group_by{|datetime| datetime[0..9]}

这会给你一个哈希,比如日期字符串作为键,日期数组作为值

{
  "2007-05-06" => [...],
  "2007-05-07" => [...],
  ...
}

所以你必须得到每个数组的长度

data_array.group_by{|datetime| datatime[0..9]}.each do |date_string, date_array|
  puts "#{date_string} occurred #{date_array.length} times."
end

当然,当你不需要日期时,这种方法会浪费内存。

那怎么样?

更节省内存的方法

date_counts = {}
date_array.each do |date_string|
  date = date_string[0..9]
  date_counts[date] ||= 0 # initialize count if necessary
  date_counts[date] += 1
end

您最终会得到一个哈希,其中日期字符串为键,计数为值

{
  "2007-05-06" => 123,
  "2007-05-07" => 456,
  ...
}

将所有内容放在一起

date_counts = {}
date_array.each do |date_string|
  date = date_string[0..9]
  date_counts[date] ||= 0 # initialize count if necessary
  date_counts[date] += 1
end

Date.parse('2007-03-23').upto Date.parse('2011-10-06') do |date_to_count|
  puts "#{date_to_count} occurred #{date_counts[date_to_count.to_s].to_i} times."
end

答案 1 :(得分:2)

这是一个非常糟糕的算法。您正在扫描整个列表中的每个日期,而且,您正在解析相同的日期两次,原因无关紧要。这意味着对于范围内的N个日期和列表中的M个日期,您正在进行N * M * 2个日期解析。

您真正需要的是使用group_by并一次性完成:

dates = date_array.group_by do |date_string|
  Date.parse(date_string)
end

然后您可以将其用作计数的参考:

Date.parse('2007-03-23').upto Date.parse('2011-10-06') do |date_to_count|
  puts "#{date_to_count} occurred #{dates[date_to_count] ? dates[date_to_count].length : 0} times."
end