在Python中快速相加两个列表的项

3 投票
6 回答
3111 浏览
提问于 2025-04-18 06:07

我有一个有点奇怪的请求,希望能高效地解决。现在我有两个列表,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),如果 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。这需要对所有按键进行处理,而不仅仅是几个特定的按键。根据 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

6 个回答

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

你可以使用一个叫做条件表达式

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])

根据你的计时代码,使用vectorize的方案更快:

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

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

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

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

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)]
3

按住一个键的时间越长,这个键的计数就会越高。

除非你的用户有300根手指,不然他们一次最多也就按十个键。你可以监听键按下和抬起的事件;当一个键被按下时,记录下当前的帧计数或者时间值,并把它保存到一个数组里;当键抬起或者你需要查看这个键的当前值时,就用抬起时的值减去按下时的值。这样可以把需要处理的循环次数从300减少到大约10次。需要注意的是,根据不同的系统,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

不过我很怀疑这会是你游戏的瓶颈所在。

撰写回答