【问题标题】:Faster Approach of Double for loop when iterating large list (18,895 elements)迭代大型列表(18,895 个元素)时更快的双 for 循环方法
【发布时间】:2015-04-28 13:35:42
【问题描述】:

代码如下:

import csv
import re

with open('alcohol_rehab_ltp.csv', 'rb') as csv_f, \
    open('cities2.txt', 'rb') as cities, \
    open('drug_rehab_city_state.csv', 'wb') as out_csv:
    writer = csv.writer(out_csv, delimiter = ",")
    reader = csv.reader(csv_f)
    city_lst = cities.readlines()

    for row in reader:
        for city in city_lst:
            city = city.strip()
            match = re.search((r'\b{0}\b').format(city), row[0])
            if match:
                writer.writerow(row)
                break

“alcohol_rehab_ltp.csv”有 145 行,“cities2.txt”有 18,895 行(转换为列表后变为 18,895)。这个过程需要一段时间才能运行,我没有计时但可能大约 5 分钟。我在这里忽略了一些简单(或更复杂)的东西,可以使这个脚本运行得更快。我将使用其他 .csv 文件来针对“cities.txt”的大文本文件运行,这些 csv 文件可能最多有 1000 行。任何关于如何加快速度的想法将不胜感激! 这是 csv 文件:关键字(144),平均。每次点击费用、本地搜索、广告商竞争

[alcohol rehab san diego],$49.54,90,High
[alcohol rehab dallas],$86.48,110,High
[alcohol rehab atlanta],$60.93,50,High
[free alcohol rehab centers],$11.88,110,High
[christian alcohol rehab centers],–,70,High
[alcohol rehab las vegas],$33.40,70,High
[alcohol rehab cost],$57.37,110,High

文本文件中的一些行:

san diego
dallas
atlanta
dallas
los angeles
denver

【问题讨论】:

  • 嵌套循环的大小相对较小(18895 x 145 迭代)。您是否以任何方式对代码进行计时?您确定让您等待 5 分钟的瓶颈确实是循环吗?如果是这样,我会尝试摆脱正则表达式,转而使用由非字母字符分割的字符串并执行if city in row.split(r'\W'):(将分割从“城市”循环中提升出来)
  • 我实际测量的时候是2分30秒。我以前没有使用过 datetime,所以我很高兴 Shawn Zhang 将它添加到他的代码中,这样我就可以了解如何使用它了。
  • 你为什么用re?
  • 你能否展示一些你的意见,因为我认为你做的工作比你需要的要多得多
  • 我使用 re 是因为城市名称将包含在 csv 每一行的第一列中。例如,第 2 行第 1 列包含“san diego Alcohol rehab center”,“san diego”将是文本文件中的行之一。就我所知,我还没有想出更好的选择。

标签: python list python-2.7 csv for-loop


【解决方案1】:

使用所有城市名称构建一个正则表达式:

city_re = re.compile(r'\b('+ '|'.join(c.strip() for c in cities.readlines()) + r')\b')

然后做:

for row in reader:
    match = city_re.search(row[0])
    if match:
        writer.writerow(row)

这会将循环迭代次数从 18895 x 145 减少到仅 18895,正则表达式引擎在这 145 个城市名称的字符串前缀匹配方面发挥了最大作用。

为了您的方便和测试,以下是完整列表:

import csv
import re

with open('alcohol_rehab_ltp.csv', 'rb') as csv_f, \
    open('cities2.txt', 'rb') as cities, \
    open('drug_rehab_city_state.csv', 'wb') as out_csv:
    writer = csv.writer(out_csv, delimiter = ",")
    reader = csv.reader(csv_f)

    city_re = re.compile(r'\b('+ '|'.join(c.strip() for c in cities.readlines()) + r')\b')

    for row in reader:
        match = city_re.search(row[0])
        if match:
            writer.writerow(row)

【讨论】:

    【解决方案2】:

    首先,正如@Shawn Zhang 建议的那样,(r'\b{0}\b').format(c.strip()) 可以在循环之外,您可以创建结果列表,以避免在每次迭代中写入文件。

    其次,您可以尝试re.compile 编译正则表达式,这可能会提高您在正则表达式上的性能。

    第三,尝试对它进行一些分析以找到瓶颈,例如使用 timeit 或其他分析器,如 ica,如果你有 SciPy。

    另外,如果城市总是在第一列,并且我假设它被命名为“城市”,为什么不使用csv.DictReader() 来读取 csv?我敢肯定它比正则表达式更快。

    编辑

    正如您提供的文件示例,我摆脱了re(因为您似乎真的不需要它们),并使用以下代码将其速度提高了 10 倍以上:

    import csv
    
    with open('alcohol_rehab_ltp.csv', 'rb') as csv_f, \
        open('cities2.txt', 'rb') as cities, \
        open('drug_rehab_city_state.csv', 'wb') as out_csv:
        writer = csv.writer(out_csv, delimiter = ",")
        output_list = []
        reader = csv.reader(csv_f)
        city_lst = cities.readlines()
    
        for row in reader:
            for city in city_lst:
                city = city.strip()
                if city in row[0]:
                    output_list.append(row)
        writer.writerows(output_list)
    

    【讨论】:

    • 迭代元组并没有更快,您只是通过将列表转换为元组增加了更多开销。该算法是二次的,这是瓶颈。
    • 好的,我留下'元组'的想法。我听说他们更快,但也许我失去了一些东西。
    • 它们可以更快地创建,因为它们是不可变的,因此它们不需要分配任何额外的空间,但迭代一个没有区别。
    【解决方案3】:

    我认为您可以使用set 和索引:

    with open('alcohol_rehab_ltp.csv', 'rb') as csv_f, \
        open('cities2.txt', 'rb') as cities, \
        open('drug_rehab_city_state.csv', 'wb') as out_csv:
        writer = csv.writer(out_csv, delimiter = ",")
        space = ""
        reader = csv.reader(csv_f)
        # make set of all city names, lookups are 0(1)
        city_set = {line.rstrip() for line in cities}
        output_list = []
        header = next(reader) # skip header
        for row in reader:
            try:
                # names are either first or last with two words preceding or following 
                # so split twice on whitespace from either direction
                if row[0].split(None,2)[-1].rstrip("]") in city_set or row[0].rsplit(None, 2)[0][1:] in city_set:
                    output_list.append(row)
            except IndexError as e:
                print(e,row[0])
        writer.writerows(output_list)
    

    运行时间现在是0(n),而不是二次方。

    【讨论】:

      【解决方案4】:

      尽管我不认为循环/IO 是大瓶颈,但如果你可以尝试从它们开始。

      我可以提供两个提示: (r'\b{0}\b').format(c.strip()) 可以在循环外,这将提高一些性能,因为我们不必在每个循环中进行 strip(),格式化。

      另外,您不必在每个循环中写入输出结果,而是可以创建一个结果列表ouput_list在循环期间保存结果并在循环后写入一次。

      import csv
      import re
      import datetime
      
      start = datetime.datetime.now()
      
      with open('alcohol_rehab_ltp.csv', 'rb') as csv_f, \
          open('cities2.txt', 'rb') as cities, \
          open('drug_rehab_city_state.csv', 'wb') as out_csv:
          writer = csv.writer(out_csv, delimiter = ",")
          space = ""
          reader = csv.reader(csv_f)
          city_lst = [(r'\b{0}\b').format(c.strip()) for c in cities.readlines()]
          output_list = []
          for row in reader:
              for city in city_lst:
                  #city = city.strip()
                  match = re.search(city, row[0])
                  if match:
                      output_list.append(row)
                      break
          writer.writerows(output_list)
      
      
      
      end = datetime.datetime.now()
      
      print end -  start
      

      【讨论】:

      • 谢谢,从 2 分 30 秒到 2 分 25 秒,这缩短了 5 秒。
      【解决方案5】:

      请注意,我假设您可以使用比使用re.search 更好的方法来连续查找城市,因为通常城市将由空格等分隔符分隔。否则复杂度大于 O(n*m)

      一种方法是使用哈希表。

      ht = [0]*MAX
      

      读取所有城市(假设有数千个)并填写一个哈希表

      ht[hash(city)] = 1
      

      现在,当您遍历阅读器中的每一行时,

      for row in reader:
          for word in row:
              if ht[hash(word)] == 1:
                  # found, do stuff here
                  pass
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-07
        • 2016-06-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-07
        相关资源
        最近更新 更多