如何优化代码以更快地处理?

时间:2019-07-11 11:10:22

标签: python python-3.x csv

我编写的代码存在一些性能问题。该代码的目的是比较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))

2 个答案:

答案 0 :(得分:2)

您的算法当前正在做什么:

  1. 加载一行大文件。
  2. 打开较小的文件。
  3. 在磁盘上的小文件中进行线性搜索
  4. 打开输出文件并将其写入。
  5. 冲洗并重复。

所有这些步骤完成了900k次以上。

步骤2(打开较小的文件)应该只执行一次。打开文件并从磁盘加载它是一项昂贵的操作。只需从一开始就加载一次并在内存中执行线性搜索(第3步),您就会看到很大的改进。

第4步也是如此:打开输出文件只能执行一次。每次关闭文件时,系统都会将文件刷新到磁盘。这是非常浪费的步骤。如果保持文件打开状态,则将数据缓冲输出,直到有足够的空间将完整的块写入磁盘为止,这是完成此操作的更快方法。

通过使用正确的数据结构,可以对步骤3进行很多优化。哈希表是日常生活中最常见的概率用途之一。它们无处不在,因为它们使查找成为恒定时间的操作(与线性搜索不同,线性搜索随输入大小而线性缩放)。哈希表在Python的dict类中实现。通过以dict为键来创建address,可以将处理时间减少到900k + 80k的倍数,而不是900k * 80k的倍数。查找algorithmic complexity了解更多信息。我特别推荐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来获得很小的性能提升。

答案 1 :(得分:1)

很长一段时间是因为:

  • 复杂度为O(n**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