在Python中,将两个列表中的元素相加的最快方法是什么?

3
我有一个有点奇怪的请求,希望您能以最高效的方式解决它。 我有两个列表list_1和list_2,它们的长度相同,并且都只包含大于或等于0的整数。我想创建一个新列表list_3,使得每个元素i都是来自list_1和list_2位置i处的元素之和。在Python中,可以使用以下代码实现: list_3 = [list_1[i] + list_2[i] for i in range(len(list_1))]
但是,这里有一个问题。对于所有满足0 <= i < len(list_1)的i,如果位置i处的元素(即list_1[i])为0,则list_1[i]和list_2[i]的和也应该为0。
那么,最有效的方法是什么?我需要对包含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,并且需要对所有键执行此操作,而不仅仅是选择几个键。根据,它目前相对于程序的其他部分来说是瓶颈。

这里是生成每个键盘键状态的代码(它使用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

1
请指定“在位置i处的项目”。 - Pavel
修改以更好地描述我所说的“位置i处的项目”。 - user3002473
1
为了理解性能要求,您是否介意详细说明需要计算什么以及为什么会成为瓶颈?也许您需要一个NumPy解决方案,而不是下面的列表推导式魔法。 - Pavel
这是否真的是你的瓶颈?我怀疑优化一个在60fps时间预算中占0.4%的操作是否会有任何区别。 - Lie Ryan
那么我是太过多虑了吗?请告诉我,因为我以前在不必要的地方容易进行微观优化。我只是有些担心,因为到目前为止,其他所有事情似乎都需要更少的时间。 - user3002473
6个回答

3
长按某个键会导致该键的计数器递增。除非你的用户有300个手指,否则他们可能只会同时按下最多十个键。您可以注册键按下和松开事件;当按下某个键时,在数组中保存帧计数器或time() / clock()的返回值;当松开某个键或需要查找当前键的值时,减去差值。这将将循环次数减少到约10次,而不是300次。请注意,根据系统的不同,time() / clock() 可能是一个syscall,可能会很慢,因此使用帧计数器可能更可取。
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

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

1

这将在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中的项为零,则总和不为零。 - user3002473
如果要处理问题中的奇怪情况,应该是 sum(t) if t[0] != 0 and t[1] != 0 else 0 - schillingt
你们说得对,我忽略了异常捕获。现在已经修复了。 - Óscar López

1

设置:

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

奥斯卡·洛佩兹的解决方案:
%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

这里的问题是,如果您选择使用列表,那么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数组。

0

你可以使用条件表达式

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要慢。


0

以下是一个使用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]

关于速度,任何明智的解决方案都会表现得差不多--尽管为了节省内存,如果你的代码允许,你可以考虑使用生成器表达式而不是列表。你应该选择最易读的方式。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接