【问题标题】:fastest way to count occurrences of partial list计算部分列表出现次数的最快方法
【发布时间】:2020-04-08 17:24:33
【问题描述】:

从开始位置到停止位置计算元素出现次数的最快方法是什么。

 list = [a,b,c,c,d,c....] can be very long
 count(list,c, from = 2, till = 4) = 2.

我们可以做到

 counter = 0
 for i in range(startpos, endpos):
        if symbol == list[i]:
            counter+= 1

或者我们可以这样做

list[startpos:endpos].count(symbol)

但是这看起来仍然比第一个选项慢,并且会复制列表的很大一部分

由于字符串具有这样的计数函数,我们可以将列表连接到字符串,然后使用内置计数函数,但是由于列表很大,因此转换为字符串似乎不是更快的方法。

有没有更快更pythonic的方法来实现这一点?

【问题讨论】:

    标签: python python-3.x list performance


    【解决方案1】:

    如果您想要一个纯 Python 解决方案,将您的第一个选项转换为 sum 函数中的生成器表达式可能是对于非常大的列表最有效的解决方案:

    sum(1 for i in range(startpos, endpos) if list[i] == symbol)
    

    其他选项,例如从列表的开头进行迭代,这会浪费大量时间迭代超出所需范围,或者切片,其中涉及创建副本,当列表非常大时,效率几乎不会那么高。

    如果您不介意使用numpy,但是,您可以创建numpy 数组而不是列表,这样您就可以在不复制项目的情况下对其进行切片(numpy 在切片时创建数组视图),然后使用sum 方法计算切片中等于所需值的项目数:

    import numpy as np
    
    ... # create your very large numpy array as lst
    
    print(np.sum(lst[startpos:endpos] == symbol))
    

    【讨论】:

      【解决方案2】:

      您可以使用生成器和范围来检查枚举索引是否在您的范围内:

      a = 2
      pos = range(5,15)
      d = [1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,]
      total = sum(elem == a for idx,elem in enumerate(d) if idx in pos)
      
      print(total)
      

      这不会复制列表,但会迭代完整的原始列表。检查idx 是否在range 中很快。

      展开循环会稍作休息 - 如果牢度非常关键,您应该计时:

      maxpos = max(pos)
      minpos = min(pos)
      for idx,elem in enumerate(d):
          if idx < minpos:
              continue
          elif idx > pos:
              break
      
          # check elem and counts something up
              continue
      

      如果您只测试少量列表,这可能会也可能不会更快 - 如果您使用列表中的最后一个元素,它不会做太多,但如果您的列表很大并且您的目标区域是“前” -休息可能会为您节省一些周期

      【讨论】:

      • lst[start:end].count(elem)enumerate 版本更好更快(几乎是 10 倍,但我猜这取决于范围)但您的替代方案是做同样的事情,没有额外的空格为list[start:end].count.+1。 timeit results
      • “更好更快”取决于您提供的数据。如果您在 5.2 个数据点的子列表中有 50 亿个数据点 - 复制这 50 亿个数据点并在之后进行计数可能比迭代它们更昂贵 - 这就是我提到 timeit-ing 的原因。为了便于阅读和良好的 c++ 实现,copy.count() 变体可能是我会使用 99/100 次的。
      • 问题的前提是一个非常长的列表,因此如果所需范围接近列表末尾,则从列表的开头迭代可能会相当低效,因为循环被浪费在迭代之外范围。
      • 完全同意。当数据真的很大时。我会使用您提到的第二种方法,或者首先使用 NumPy 数组来处理如此大的数据。
      • @blh 你使用生成器总和的方式更好:)
      【解决方案3】:

      试试这个:

      from collections import Counter
      print(Counter(my_list[start:end]))
      

      如果您愿意,可以将 Counter 对象转换为 dict:

      occurrences = dict(Counter(my_list[start:end]))
      

      【讨论】:

        【解决方案4】:

        只是为了补充 blhsing 的答案,生成器表达式可能稍微不那么冗长:

        sum(list[i] == symbol for i in range(startpos, endpos))
        
        

        【讨论】:

          猜你喜欢
          • 2012-09-09
          • 2014-06-12
          • 1970-01-01
          • 2014-06-19
          • 2020-02-22
          • 2014-06-17
          • 1970-01-01
          • 2011-01-23
          相关资源
          最近更新 更多