如何使ruby脚本执行得更快?

时间:2014-05-01 22:29:35

标签: ruby

在test.csv中,我有~25k行。当我执行下面的脚本时,运行需要很长时间。我相信这是因为当我迭代所有独特的用户(最后一个for循环)时,它会重新遍历每一条记录。有没有办法让这个表现更快?也许在迭代后删除该行,以便它不必再次通过它?还有别的吗?

require 'csv'

file = "test.csv"
customers = CSV.read(file)

all_users = []

# for each unique user, put his/her path in a hash.
# check if that path exists in the hash, if it doesn't add it. if it does, increment the count for that path


# Every user record
CSV.foreach(file) do |row|
    all_users << row[0]
end

# Only unique user records
unique_users = all_users.uniq


i = 0
paths = Hash.new

for user in unique_users
    path = ""
    CSV.foreach(file) do |row|
        if row[0] == user
            path = path + row[1] + ","
        end
    end

    if paths.key?(path)
        paths[path] = paths[path] + 1
    else
        paths[path] = 1
    end

    i = i + 1
    puts i
    puts paths

end

2 个答案:

答案 0 :(得分:3)

我不确定你要对你的代码做什么,但我已经做了一些假设,你可以审查并评论它们是否错误。此外,由于您没有提供示例代码,我无法轻松测试我的。一个例子,连同所需的输出,会有所帮助 - 仍然会有所帮助。

我希望我犯了一些错误。如果读者能够报告他们的任何发现,我将不胜感激。

这是一种重构代码的方法,可以加快它的速度,因为文件的每条记录只读一次。

<强>代码

require 'csv'

def count_by_path(fname)
  CSV.foreach(fname).with_object({}) { |(cust, path), path_by_cust|
    (path_by_cust[cust] = (path + ",")) unless path_by_cust.key?(cust) }
      .values
      .each_with_object({}) { |path, count_by_path|
        count_by_path[path] = (count_by_path[path] || 0) + 1 }
end

调用
count_by_path(fname)

并返回一个哈希,其键是路径,其值是拥有该路径的客户数。

<强>解释

让我们逐行完成。首先,我们有:

CSV.foreach(fname).with_object({}) { |(cust, path), path_by_cust| ... }

类方法CSV#foreach一次从CSV文件中读取一个数组(记录)。我相信我们可以在Enumerator#with_object上创建一个由块变量path_by_cust表示的空哈希。第一项任务是构建该哈希值。

块变量custpath对应于foreach读取的每个数组的前两个元素。 (Ruby将忽略数组中的任何其他元素。)

对于我们执行的每条记录:

(path_by_cust[cust] = (path + ",")) unless path_by_cust.key?(cust)

(从您的代码中可以看出,路径可能会连接在一起,但考虑到每位客户只需要考虑一条记录,我无法看到这种可能性。)

读取文件中的所有记录后,此哈希将用于计算路径。

我们需要客户密钥来构建这个哈希,但现在没有进一步使用它们(如果我的理解是正确的,你只需要计算每个路径;即,拥有的客户数量的计数给定的路径)。因此,我们在哈希上调用方法Hash#values,它返回数组中的值(路径)。

我们现在可以构建感兴趣的哈希,使用键路径和值来表示每条路径的客户数量:

.each_with_object({}) { |path, count_by_path|
  count_by_path[path] = (count_by_path[path] || 0) + 1

如果count_by_path已有密钥path(因此count_by_path[path]不是nil),则表达式

count_by_path[path] = (count_by_path[path] || 0) + 1

评估为

count_by_path[path] = count_by_path[path] + 1

另一方面,如果count_by_path没有密钥path,则表达式的计算结果为:

count_by_path[path] = 0 + 1

这正是我们所需要的。

答案 1 :(得分:3)

它很慢的原因是你的代码有一个指数增长区域,这是for循环。您正在检查每一行与其他每一行。因此,如果你有10行,for循环运行10次,CSV.foreach运行10次=&gt;您在CSV.foreach块中运行代码的次数为100次。

因此,对于25k行,您正在运行此块

{|row| path = path + row[1] + "," if row[0] == user}

625,000,000次,当它只能运行25k次+唯一名称的数量时。

我也不是100%肯定你想做什么,但这是我的猜测。

require 'csv'

CSV.foreach("test.csv").with_object({}) { |(name, path), paths|
  #build the path for each user
  paths[name] = paths.fetch(name) {""} + path + ',' 
}.each_with_object({}) do |(name, full_path), counted_paths|
  #count each unique full path
  counted_paths[full_path] = counted_paths.fetch(full_path) {0} + 1
end