【问题标题】:Fastest way to sum two lists of items with each other in python在python中将两个项目列表相加的最快方法
【发布时间】:2014-05-10 15:07:58
【问题描述】:

我有一个奇怪的请求,我希望以最高效率解决;我有两个列表list_1list_2,它们的长度相同,并且都只包含大于或等于0 的整数。我想创建一个新列表list_3,这样每个元素i 都是来自list_1list_2 的位置i 的元素之和。在 python 中,这就足够了:

list_3 = [list_1[i] + list_2[i] for i in range(len(list_1))]

但是,有一个问题。对于每个i 使得0 <= i < len(list_1),如果位置i 的项目(即list_1[i])为0,那么list_1[i]list_2[i] 的总和应该也为零 .

最有效的方法是什么?我必须在包含 323 个元素的列表上执行此操作,并且它需要用于游戏,因此它应该能够轻松地每秒运行 60 次,同时允许大量额外的时间来计算游戏中的其他事物。我想知道是否有任何花哨的 numpy 方法可以做到这一点,但我对 numpy 的了解还不够,无法确定。

编辑:

就简单地将两个元素相加而言,一些常见的表达式是:

list_3 = [list_1[i] + list_2[i] for i in range(len(list_1))]
list_3 = [sum(t) for t in zip(list_1, list_2)]
list_3 = numpy.add(list_1, list_2)

编辑 2:

我知道条件列表推导,但我想知道是否有比这更快的方法。

编辑 3:

以下是给出的一些方法的时间安排:

>>> import timeit
>>> setup='''
import random
list_1 = [random.randint(0, 323) for i in range(323)]
list_2 = [random.randint(0, 323) for i in range(323)]
'''
>>> timeit.timeit('list_3 = [list_1[i] + list_2[i] if list_2[i] else 0 for i in range(len(list_1))]', setup=setup, number=1)
6.005677381485953e-05
>>> timeit.timeit('list_3 = [x + y if y else 0 for x, y in zip(list_1, list_2)]', setup=setup, number=1)
3.604091037417601e-05

更快吗?

编辑 4:

以下是我需要此功能的解释:我正在开发一款视频游戏,该游戏需要一个不时检查键盘上某些键状态的系统。系统需要工作的方式是按键被按下的时间越长,该按键的计数器就增加得越高。释放该键后,计数器将设置回 0。这需要对所有键进行,而不仅仅是少数几个键。根据cProfile 的说法,与程序的其他部分相比,它目前是一个瓶颈。

这是生成键盘中每个键状态的代码(它使用pygame 来获取键状态):

class KeyState:
    """
    An object representing the state of the keyboard input at a given frame.

    The KeyState is a global replacement for pygame's event system (or
    pygame.keys.get_pressed()). It provides a simple interface for updating
    and retreiving the states of keys in real time.

    To retreive and store the current key information, simply call
    the update() method. To retreive the given information about a
    key, use the get_state(key) method where key is any pygame key
    (i.e. pygame.K_RSHIFT, etc.).
    """

    def __init__(self):
       self.current_frame = pygame.key.get_pressed()

    def update(self):
        """
        Retreive the current key state data.
        """
        new_frame = pygame.key.get_pressed()
        self.current_frame = [state + new_frame[i] if new_frame[i] else 0 for i, state in enumerate(self.current_frame)]

    def get_state(self, key, strict=True):
        """
        Retreive the current state of a given key.

        >= 1 - Pressed
        0    - Unpressed
        """
        try: 
            return self.current_frame[key]
        except KeyError:
            if strict:
                raise

【问题讨论】:

  • 请指定“位置 i 的项目”
  • 已编辑以更好地描述“位置 i 的项目”的含义。
  • 谢谢!为了了解性能要求,您是否愿意详细说明您需要计算什么以及为什么它会成为这样的瓶颈?也许您需要一个 numpy 解决方案,而不是他们在下面展示的列表理解魔法。
  • 如果这严重是您的瓶颈?我怀疑优化一项会占用 60fps 时间预算的 0.4% 的操作会产生什么不同。
  • 所以我太偏执了?请告诉我是不是因为我以前在不必要的地方容易进行微优化。我只是担心,因为到目前为止,其他所有事情似乎单独花费的时间要少得多。

标签: python list numpy


【解决方案1】:

一个键被按下的时间越长,该键的计数器就越高

除非您的用户有 300 根手指,否则他们可能一次最多只能按下十个键。您可以注册 keydown 和 keyup 事件;当按键按下时,将帧计数器或 time()/clock() 的返回值保存在数组中;当一个键启动或当您需要找到该键的当前值时,减去差异。这会将循环数减少到大约 10 个而不是 300 个。请注意,根据系统的不同,time()/clock() 可能是一个系统调用,它可能很慢,因此使用帧计数器可能更好。

counter = 0
keys = {}
while True:
    for event in pygame.event.get() :
        if event.type == pygame.KEYDOWN :
            keys[event.key] = counter
        elif event.type == pygame.KEYUP :
            diff[event.key] = keys.pop(event.key) - counter
    counter += 1

但我非常怀疑这是你游戏的瓶颈。

【讨论】:

    【解决方案2】:

    这将在 Python 3.x 中很好地工作

    list3 = [x+y if x and y else 0 for x, y in zip(list1, list2)]
    

    或者,如果您使用的是 Python 2.x:

    import itertools as it
    list3 = [x+y if x and y else 0 for x, y in it.izip(list1, list2)]
    

    【讨论】:

    • 如果list2 中的项目为零,则总和不为0。
    • 应该是sum(t) if t[0] != 0 and t[1] != 0 else 0,以便处理问题中的奇怪问题。
    • 你们是对的,我忽略了问题。现在已经修复了
    【解决方案3】:

    设置:

    import numpy as np
    import random
    list_1 = [random.randint(0, 323) for i in range(323)]
    list_2 = [random.randint(0, 323) for i in range(323)]
    array_1 = np.random.randint(0,323,323)
    array_2 = np.random.randint(0,323,323)
    

    原定时间:

    %timeit list_3 = [list_1[i] + list_2[i] if list_2[i] else 0 for i in range(len(list_1))]
    10000 loops, best of 3: 62.8 µs per loop
    
    %timeit list_3 = [list_1[i] + list_2[i] if list_2[i] else 0 for i in range(len(list_1))]
    10000 loops, best of 3: 62.3 µs per loop
    

    Oscar Lopez 的解决方案:

    %timeit list3 = [x+y if x and y else 0 for x, y in zip(list_1, list_2)]
    10000 loops, best of 3: 60.7 µs per loop
    
    import itertools as it
    
    %timeit list3 = [x+y if x and y else 0 for x, y in it.izip(list_1, list_2)]
    10000 loops, best of 3: 50.5 µs per loop
    

    Dawg 的np.vectorize 解决方案:

    vector_func=np.vectorize(lambda e1, e2: e1+e2 if e1 and e2 else 0)
    
    %timeit vector_func(array_1,array_2)
    10000 loops, best of 3: 121 µs per loop
    

    numpy 解决方案:

    %timeit out = array_1 + array_2; out[(array_1==0) & (array_2==0)] = 0
    100000 loops, best of 3: 11.1 µs per loop
    

    这里的问题是,如果你选择使用 list,numpy 的解决方案实际上会更慢。

    %%timeit
    array_1 = np.array(list_1)
    array_2 = np.array(list_2)
    out = array_1 + array_2
    out[(array_1==0) & (array_2==0)] = 0
    10000 loops, best of 3: 84.8 µs per loop
    

    numpy 解决方案将是最快的,但您需要在一开始就使用 numpy 数组。

    【讨论】:

      【解决方案4】:
      list3 = [x + y for x, y in zip(list1, list2)]
      

      【讨论】:

        【解决方案5】:

        下面是一个示例,它使用zip 说明了常规求和和带有条件的求和:

        >>> list_1 = [1,2,0,4,9]
        >>> list_2 = [6,7,3,1,0]
        >>> list_3 = [sum(v) for v in zip(list_1, list_2)] # regular sum
        >>> list_3
        [7, 9, 3, 5, 9]
        >>> list_3 = [sum(v) if 0 not in v else 0 for v in zip(list_1, list_2)] # neither list_1[i] nor list_2[i] should be 0
        >>> list_3
        [7, 9, 0, 5, 0]
        >>> list_3 = [sum(v) if v[0] != 0 else 0 for v in zip(list_1, list_2)] # list_1[i] should not be 0
        >>> list_3
        [7, 9, 0, 5, 9]
        

        至于速度,任何合理的解决方案都将执行大致相同的 - 尽管为了节省内存,如果您的其余代码允许,您可以考虑使用生成器表达式而不是列表。您应该选择最清晰的内容。

        【讨论】:

          【解决方案6】:

          您可以使用conditional expression

          li1=[1,2,3,4,0,5,6]
          li2=[1,2,3,0,5,6,7]
          
          print [ li1[i] + li2[i]  if li1[i] and li2[i] else 0 for i in range(len(li1))]
          # [2, 4, 6, 0, 0, 11, 13]
          

          对于 Numpy(您的问题已被标记),您可以使用 vectorize

          >>> a1=np.array([1,2,3,4,0,5,6])
          >>> a2=np.array([1,2,3,0,5,6,7])
          >>> vector_func=np.vectorize(lambda e1, e2: e1+e2 if e1 and e2 else 0)
          >>> vector_func(a1,a2)
          array([ 2,  4,  6,  0,  0, 11, 13])
          

          或者,如果您更喜欢函数而不是 lambda:

          >>> def vector(e1, e2):
          ...    if e1 and e2: return e1+e2
          ...    return 0
          ...
          >>> vector_func=np.vectorize(vector)
          >>> vector_func(a1,a2)
          array([ 2,  4,  6,  0,  0, 11, 13])
          

          使用您的时序代码,矢量化解决方案更快:

          import timeit
          import numpy as np
          import random
          
          a1=np.array([random.randint(0, 323) for i in range(323)])
          a2=np.array([random.randint(0, 323) for i in range(323)])
          vector_func=np.vectorize(lambda e1, e2: e1+e2 if e1 and e2 else 0)
          
          n=10000
          
          setup='''
          from __main__ import a1, a2, vector_func
          '''
          print timeit.timeit('a3 = [a1[i] + a2[i] if a2[i] else 0 for i in range(len(a1))]', setup=setup, number=n)
          print timeit.timeit('a3 = [x + y if y else 0 for x, y in zip(a1, a2)]', setup=setup, number=n)
          print timeit.timeit('a3=vector_func(a1,a2)', setup=setup, number=n)
          

          打印:

          2.25640797615
          1.97595286369
          0.993543148041
          

          如果你只有整数并且没有其他理由使用 numpy,那么 Numpy 比纯 Python 慢。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-08-25
            • 2020-10-24
            • 2021-10-18
            • 2011-01-05
            • 2021-07-17
            • 1970-01-01
            • 2010-11-23
            相关资源
            最近更新 更多