[英]How can I optimize my code to process faster?
我编写的代码存在一些性能问题。 该代码的目的是比较2个csv文件(一个中有900k行,另一个中有50k〜80k行)。
目的是比较csv1和csv2,并将匹配的数据写入第三个csv。
我的数据如下所示:
CSV1:
address,name,order_no
add1,John,d009
add2,Smith,d019
add3,Mary,d890
.....(900k more rows)
CSV2:
address,hub_id
add3,x345
add4,x310
add1,a109
....(50k ~ 80k more rows)
预期输出:
CSV3:
order_no,hub_id
d890,x345
d009,a109
.....(etc)
我现在正在处理的代码(尽管很简单)实际上有效。 但是,比较和编写的整个过程需要很长时间才能完成。
任何指针将不胜感激。 自从我刚开始学习以来,我可能已经忽略了一些在比较大型数据时可以使用的python函数。
import csv
import time
start_time = time.time()
with open('csv1.csv', newline='', encoding='Latin-1') as masterfile:
reader = csv.DictReader(masterfile)
for row in reader:
with open('csv2.csv', newline='', encoding='Latin-1') as list1:
reader2 = csv.DictReader(list1)
for row2 in reader2:
if row2['address'] == row['address']:
with open('csv3.csv', 'a') as corder:
print(row2['wip'] + ', ' + row['lat'] + ', ' + row['long'], file=corder)
print("--- %s seconds ---" % (time.time() - start_time))
您的算法当前正在做什么:
所有这些步骤完成了900k次以上。
步骤#2(打开较小的文件)应该只执行一次。 打开文件并从磁盘加载它是一项昂贵的操作。 仅仅从一开始就加载一次并在内存中执行线性搜索(第3步),您就会看到很大的改进。
步骤#4同样:打开输出文件只能执行一次。 每次关闭文件时,系统都会将文件刷新到磁盘。 这是非常浪费的步骤。 如果使文件保持打开状态,则将数据缓冲输出,直到有足够的空间将完整的块写入磁盘为止,这是完成此操作的更快方法。
通过使用正确的数据结构,可以对步骤3进行很多优化。 哈希表是日常生活中最常见的概率用途之一。 它们无处不在,因为它们使查找成为恒定时间的操作(与线性搜索不同,线性搜索随输入大小而线性缩放)。 哈希表在Python的dict
类中实现。 通过创建一个以address
为键的dict
,可以将处理时间减少到900k + 80k
的倍数,而不是900k * 80k
的倍数。 查找算法复杂度以了解更多信息。 我特别推荐Steve Skiena的“算法设计手册”。
最后一步是在每个文件中找到地址的交集。 有一些可用的选项。 您可以将两个文件都转换为dict
并执行一set
类似键的交集,也可以将一个文件加载到dict
,然后逐行对其进行测试。 我强烈建议您使用后者,将较小的文件作为您加载到dict
。 从算法角度来看,元素减少10倍意味着您可以降低哈希冲突的可能性。 这也是最便宜的方法,因为它会在较大文件的无关行上快速失败,而不会对其进行记录。 从实际的角度来看,如果我怀疑它有多个具有相同地址的行,您甚至可能没有选择将较大的文件直接转换为字典。
这是我一直在谈论的实现:
with open('csv2.csv', newline='', encoding='Latin-1') as lookupfile:
lookup = dict(csv.reader(lookupfile))
with open('csv1.csv', newline='', encoding='Latin-1') as masterfile, open('csv3.csv', 'w') as corder:
reader = csv.reader(masterfile)
corder.write('order_no,hub_id\n')
for address, name, order_no in reader:
hub_id = lookup.get(address)
if hub_id is not None:
corder.write(f'{order_no},{hub_id}\n')
如果任何行的长度不完全是两个元素,则表达式dict(csv.reader(lookupfile))
将失败。 例如,空白行将使其崩溃。 这是因为dict
的构造函数期望两个元素的序列可迭代来初始化键值映射。
作为次要的优化,我没有使用csv.DictReader
,因为这需要对每一行进行额外的处理。 此外,我已经从输出中完全删除了csv
模块,因为您可以更快地完成工作,而无需添加包装器。 如果你的文件一样整洁格式化为你展示,你可以从身边分裂他们得到一个小的性能提升,
你自己,而不是使用csv
。
很长一段时间是因为:
O(n**2)
。 永远不会对像这样的大数据执行线性搜索 通过创建2个字典, 地址作为键 ,整行作为值,可以做得更好。
然后执行键的交点,并写入结果,并根据需要在每个字典中选择数据。
以下代码已针对您的示例数据进行了测试
import csv
with open('csv1.csv', newline='', encoding='Latin-1') as f:
reader = csv.DictReader(f)
master_dict = {row["address"]:row for row in reader}
with open('csv2.csv', newline='', encoding='Latin-1') as f:
reader = csv.DictReader(f)
secondary_dict = {row["address"]:row for row in reader}
# key intersection
common_keys = set(master_dict) & set(secondary_dict)
with open("result.csv", "w", newline='', encoding='Latin-1') as f:
writer = csv.writer(f)
writer.writerow(['order_no',"hub_id"])
writer.writerows([master_dict[x]['order_no'],secondary_dict[x]["hub_id"]] for x in common_keys)
结果是:
order_no,hub_id
d009,a109
d890,x345
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.