在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
答案 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
表示的空哈希。第一项任务是构建该哈希值。
块变量cust
和path
对应于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