2016-04-22 65 views
0

我有一个相对中等大小的电子表格 - 212行×56列的数据。如何加速从小电子表格中读取数据?

我有一个循环,逐渐变得越来越慢,越接近我的搜索到我的电子表格的底部。如果可以达到200ms,最高可达7000ms,可以返回响应。

如何加快搜索速度,使时间至少保持不变或至少显着加速,从而永远不会超过500毫秒。

这里是我怎样,我打开电子表格:

wb = openpyxl.load_workbook('data/%s' % filename, read_only=True) 
sheet = wb.get_sheet_by_name('Service%s' % service) 

这里是我的循环:

for i in range(3, sheet.max_row+1): 
    if str(sheet.cell(row=i, column=1).value) == country: 
     for x in range(2, sheet.max_column+1): 
      if weight > float(sheet.cell(row=2, column=sheet.max_column).value): 
       abort(404, "Maximum Weight Exceeded for Service Class") 

      if weight < float(sheet.cell(row=2, column=2).value): 
       return float(sheet.cell(row=i, column=2).value) 

      if weight == float(sheet.cell(row=2, column=x).value): 
       return float(sheet.cell(row=i, column=x).value) 

      if weight < float(sheet.cell(row=2, column=x).value): 
       return float(sheet.cell(row=i, column=x).value) 

编辑:

经过大家的建议,我已经重构的方法。它似乎要快得多,但我不确定如何访问嵌套在for循环中的特定行。下面的新代码:

if weight > float(sheet.cell(row=2, column=sheet.max_column).value): 
    abort(404, "Maximum Weight Exceeded for Service Class") 

minweight = float(sheet.cell(row=2, column=2).value) 

for row in sheet.rows: 
    if row[0].value == country: 
     if weight < minweight: 
      return row[1].value 

     for cell in row[1:]: # skip first item 
      if weight <= float(cell.value): 
      # This is wrong. I need to compare weight to cell values in the 2nd row 
       return float(cell.value) 

编辑2 - 现在运行〜300ms的:

if weight > float(sheet.cell(row=2, column=sheet.max_column).value): 
    abort(404, "Maximum Weight Exceeded for Service Class") 

minweight = float(sheet.cell(row=2, column=2).value) 

ignore_first_row, weight_list = islice(sheet.rows, 0, 2) 

for row in islice(sheet.rows, 2, sheet.max_row): 
    if row[0].value == country: 
     if weight < minweight: 
      return row[1].value # return country's min rate 

     for ratecell, weightcell in izip(row, weight_list): 
      if weight <= float(weightcell.value): 
       return float(ratecell.value) 
+0

你得到什么那里看,你肯定可以改善你的if语句。你在'for循环范围()'中,并且你的一个逻辑检查不使用'x'或'i'。具体来说,它看起来像'if weight> float(sheet.cell(row = 2) ,column = sheet.max_column).value)'可以在循环之外移动? – MikeTGW

+0

是很好的捕获,并将它移到循环之外;虽然会对性能产生很大影响? – K997

+0

按顺序搜索平面文件显然需要更长的时间才能在文件末尾找到匹配,而与接近开始的匹配相比。这是顺序搜索的固有特性。 尽管如此,7000毫秒却非常慢,这让我怀疑你正在做大量额外的磁盘读取。而不是任意微观优化,可以使用诸如“cProfile”之类的分析器来衡量需要花费的时间。 这就是说,我会开始迭代使用'sheet.iter_rows()'或'sheet.rows'行而不是单元格查找。 –

回答

2

我生成了1张包含57列和200行的xlsx文件。每个列栏最后包含一个随机生成的100个字符的字符串,最后一列是一个6个字符的任意但非随机的序列用作搜索目标。

下面的代码,使用sheet.rows约7倍快(350毫秒):

for row in sheet.rows: 
    if str(row[sheet.max_column-1].value) == needle: 
     # needle defined to match only the last row 
     print 'found' 
     break 

比你的代码的精简当量(2400ms):

for i in xrange(1, sheet.max_row+1): 
    if str(sheet.cell(row=i, column=sheet.max_column).value) == needle: 
     # needle defined to match only the last row 
     print 'found' 
     break 

请注意,我有一个SSD和一个快速处理器 - YMMV取决于硬件和实际数据。除非数据和硬件基本上是常量,否则不能保证搜索时间会少于给定时间。

正如我在评论中所说的,你应该学会使用cProfile或类似的方法来测试你的代码。

在我的评论中,我还指出,顺序搜索固有需要更长的时间来在序列中进一步寻找匹配。要改变搜索的时间复杂度,您需要更改算法,这意味着以不同的方式构造数据(即不使用平面文件)。二进制搜索通常比顺序搜索快得多,但需要排序数据。

取决于您还需要做什么(您是否需要修改工作表中的数据?多长时间一次?您的数据有多大?是否真的必须保留在Excel工作表中?)有可能进一步提高你的搜索,甚至根本没有。

由于CharlieClark在评论中指出,row[-1]可能比row[sheet.max_column-1]更快(或者你可以把它外循环,因为你的行总是相同的长度),你不需要投cell.value,如果你的字符串期待这些单元格中的字符串数据。


更新: sheet.rows是返回一个发电机,至少在V2.3.5的属性,所以没有,除非你使用itertools.islice你不能切它。

但是,您可以将生成器返回到一个变量中,调用.next()两次来检索并存储前两行,然后遍历其余的部分。

row_gen_use_once = sheet.rows 
# should really try/except for StopIteration in the next() calls in case there are less than two rows, or else check the row count beforehand 
first_row = row_gen_use_once.next() 
second_row = row_gen_use_once.next() 

for row in row_gen_use_once: 
    pass # blah blah do stuff 
    # now you can access the second row here :) 

或者你可以使用enumerate,并从内环路保存第二行:

first_row = None 
second_row = None 

for idx, row in enumerate(sheet.rows): 
    if idx == 0: 
     first_row = row 
    elif idx == 1: 
     second_row = row 
    else: 
     pass 
     # blah blah do stuff 

这意味着在环路一些额外的检查,但他们不会因分支创建开销太大预测。

itertools.islice版本,这在我看来是最好的解决办法:

from itertools import islice 
first_row, second_row = islice(sheet.rows, 0, 2) 

for row in islice(sheet.rows, 2, sheet.max_row): 
    pass # do stuff 

除非你使用Python 3,在这种情况下,只是做:

first_row, second_row, *other_rows = sheet.rows 

for row in other_rows: 
    pass # do stuff 
+0

正如您所看到的,openpyxl已经提供了对行(和列)的优化访问,因此不需要编写自己的代码来执行此操作。它也处理类型转换,所以'str(cell.value)'是多余的。行大小均匀,所以调用'ws.max_column'(这是一个带有调用的属性)是非常昂贵的:'row [-1]'更好。应该提高一点。 –

+0

@CharlieClark好点。我最初使用'row [-1]',但在我的示例中切换到'ws.max_column',以更直接地与提问者的版本保持一致。提问者应该记住,这样的改变是很好的,但可能不会像使用'ws.rows'而不是使用自己的迭代那么多。 –

+0

我不知道,我怀疑调用'ws.max_column'实际上可能是限制因素,尽管嵌套循环使代码难以理解。 –

1

下面是我的一些即时的想法:

for i in xrange(3, sheet.max_row+1): 
    if str(sheet.cell(row=i, column=1).value) == country: 

     if weight > float(sheet.cell(row=2, column=sheet.max_column).value): 
      abort(404, "Maximum Weight Exceeded for Service Class") 
     if weight < float(sheet.cell(row=2, column=2).value): 
      return float(sheet.cell(row=i, column=2).value) 

     for x in xrange(2, sheet.max_column+1): 
      if weight <= float(sheet.cell(row=2, column=x).value): 
       return float(sheet.cell(row=i, column=x).value) 

这会将你的两个逻辑检查一起(的<=)和另外两个环外

另外,取决于它Ë你计算weight,这句话应该是别的地方在你的代码:

if weight > float(sheet.cell(row=2, column=sheet.max_column).value): 
     abort(404, "Maximum Weight Exceeded for Service Class") 

它不会使用ix,所以你不需要浪费时间在每次循环撞击它

时间检查它

你能澄清一下这个块是应该做的:

if weight < float(sheet.cell(row=2, column=2).value): 
    return float(sheet.cell(row=i, column=2).value) 

在你的循环,weight没有改变。这是一个静态检查,将利用当前值i从您的函数返回。考虑到你所显示的代码,这是没有意义的。

+0

感谢您的建议 - 您是对的,这两项检查可以移出列循环。 weight> float语句实际上可以完全移到循环之外。 – K997

+0

关于你的问题 - 它的基本意思是说,“如果重量小于最小量,那么只需选择最小量”。这也可以完全移到循环之外。 – K997

+0

我只是做了代码更改,而逻辑检出时,循环仍然运行缓慢。从行循环(第一个for循环)肯定会发生减速 - 而不是列循环。不知道可以采取什么措施加速下载电子表格? – K997

相关问题