您可以使用itertools.compress
,它产生与选择器中的 true 相对应的元素。
但是,这将需要复制bits
并反转副本以选择零元素,最终结果为:
from operator import not_
true_values = list(compress(sequence, bits))
false_values = list(compress(sequence, map(not_, bits)))
我相信使用一个简单的for
循环会更容易和更快,因为它只进行一次迭代:
true_values = []
false_values = []
for bit, val in zip(bits, values):
if bit:
true_values.append(val)
else:
false_values.append(val)
出于好奇,这里有一些具有各种解决方案的微型基准测试:
In [12]: import random
In [13]: value = 'a' * 17000
In [14]: selectors = [random.randint(0, 1) for _ in range(17000)]
In [15]: %%timeit
...: true_values = [v for v,b in zip(value, selectors) if b == 1]
...: false_values = [v for v,b in zip(value, selectors) if b == 0]
...:
100 loops, best of 3: 2.56 ms per loop
In [16]: %%timeit
...: true_values = []
...: false_values = []
...: for bit,val in zip(selectors, value):
...: if bit:
...: true_values.append(val)
...: else:
...: false_values.append(val)
...:
1000 loops, best of 3: 1.87 ms per loop
In [17]: %%timeit
...: res = {}
...: for val, bit in zip(value, selectors):
...: res.setdefault(bit, []).append(val)
...: true_values, false_values = res.get(1, []), res.get(0, [])
...:
100 loops, best of 3: 3.73 ms per loop
In [18]: from collections import defaultdict
In [19]: %%timeit
...: res = defaultdict(list)
...: for val, bit in zip(value, selectors):
...: res[bit].append(val)
...: true_values, false_values = res.get(1, []), res.get(0, [])
...:
100 loops, best of 3: 2.05 ms per loop
In [26]: %%timeit # after conversion to numpy arrays
...: true_values = values[selectors == 0]
...: false_values = values[selectors == 1]
...:
1000 loops, best of 3: 344 us per loop
In [31]: %%timeit
...: res = [[], []]
...: for val, bit in zip(value, selectors):
...: res[bit].append(val)
...: true_values, false_values = res
...:
100 loops, best of 3: 2.09 ms per loop
In [34]: from operator import not_
In [35]: %%timeit
...: true_values = list(compress(value, selectors))
...: false_values = list(compress(value, map(not_, selectors)))
...:
1000 loops, best of 3: 1.44 ms per loop
假设你可以用 numpy 数组替换 python 列表,显然numpy
比其他的要快得多。
这似乎itertools.compress
是最快的非 3rd 方解决方案,位于1.44 ms
. 第二快的是for
带有if-else
, at的 naive 1.87
,其他解决方案花费的时间略多于2 ms
.
增加元素的数量我看到的唯一变化是 Jon Clement 的defaultdict(list)
解决方案和 newtower 的[[], []]
解决方案变得比朴素的+略快(比如在 处更快)。仍然比其他人快 30%,并且仍然比.for
if-else
2%
500000
compress
numpy
compress
这种差异对你来说重要吗?如果不是(并且配置文件以检查它是否是瓶颈!)我只是考虑使用更具可读性的解决方案,这几乎是主观的,取决于你。
关于我获得的时间的最后一句话:
即使两者compress
和您的双重列表理解都对列表进行了两次迭代,一个是最快的非第 3 方解决方案,另一个是最慢的。在这里,您可以看到“python 级循环”或“显式循环”与“C 级循环”或“隐式循环”之间的区别。
itertools.compress
被实现,C
这允许它在没有太多解释器开销的情况下进行迭代。正如你所看到的,这有很大的不同。
您可以在解决方案中看到更多这一点numpy
,它还执行两次迭代而不是一次。在这种情况下,不仅循环是“在 C 级别”,而且它还完全避免调用 python API 来迭代数组,因为numpy
它有自己的 C 数据类型。
这几乎是CPython 中的一条规则:为了提高性能,尝试使用内置函数或 C 扩展中定义的函数将显式循环替换为隐式循环。
Guido van Rossum 很清楚这一点,请尝试阅读他的 An Optimization Anecdote。
你可以在这个SO 问题中找到另一个这样的例子(免责声明:接受的答案是我的。我利用二分搜索和字符串相等(-> C 级内置)来获得比纯 python 线性更快的解决方案搜索)。