这是我一直想知道的事情。我将提出Python的问题,但我也欢迎解决Java和C ++标准库的问题。
假设您有一个名为" my_list"的Python列表,并且您希望迭代其唯一元素。有两种自然方法:
#iterate over set
for x in set(my_list):
do_something(x)
或
#list to set to list
for x in list(set(my_list)):
do_something(x)
紧张的是,迭代列表比迭代迭代更快,但是将一个集转换为列表需要时间。我的猜测是,这个问题的答案取决于许多因素,例如:
所以我想我正在寻找形式的经验法则"如果列表有x个元素,每个元素重复不超过y次,你只需要迭代z次然后你应该遍历集合;否则你应该把它转换成一个列表。"
答案 0 :(得分:30)
我正在寻找经验法则......
以下是编写最佳Python的最佳经验法则:尽可能少使用中间步骤,避免实现不必要的数据结构。
应用于此问题:集合是可迭代的。不要将它们转换为另一个数据结构,只是为了迭代它们。信任Python以了解迭代集合的最快方法。如果将它们转换为列表更快,Python就会这样做。
不要通过为程序添加复杂性来尝试过早优化。如果您的程序耗时太长,请对其进行分析,然后优化瓶颈。如果您使用的是Python,那么您可能更关心开发时间而不是程序运行的时间。
在Python 2.7中:
import collections
import timeit
blackhole = collections.deque(maxlen=0).extend
s = set(xrange(10000))
我们看到更大的n
,更简单更好:
>>> timeit.timeit(lambda: blackhole(s))
108.87403416633606
>>> timeit.timeit(lambda: blackhole(list(s)))
189.0135440826416
对于较小的n
,同样的关系成立:
>>> s = set(xrange(10))
>>> timeit.timeit(lambda: blackhole(s))
0.2969839572906494
>>> timeit.timeit(lambda: blackhole(list(s)))
0.630713939666748
是的,列表比集合迭代更快(在你自己的Python解释器上尝试):
l = list(s)
timeit.repeat(lambda: blackhole(l))
但这并不意味着您应该将集合转换为仅用于迭代的列表。
好的,所以你已经分析了你的代码,发现你正在迭代一大堆(我认为这个集是静态的,否则我们正在做的是非常有问题的)。我希望你熟悉set方法,而不是复制那个功能。 (我也认为你应该考虑将frozensets与元组链接起来,因为使用一个列表(可变)来替换一个规范集(也是可变的)似乎很容易出错。)所以请注意,让我们做一个分析。
可能是通过对复杂性进行投资并从更多代码行中冒更大的错误风险,您可以获得良好的回报。这一分析将证明这一点的盈亏平衡点。我不知道你需要为更大的风险和开发时间付出多少性能,但这会告诉你在什么时候你可以开始为这些付出代价。:
import collections
import timeit
import pandas as pd
BLACKHOLE = collections.deque(maxlen=0).extend
SET = set(range(1000000))
def iterate(n, iterable):
for _ in range(n):
BLACKHOLE(iterable)
def list_iterate(n, iterable):
l = list(iterable)
for _ in range(n):
BLACKHOLE(l)
columns = ('ConvertList', 'n', 'seconds')
def main():
results = {c: [] for c in columns}
for n in range(21):
for fn in (iterate, list_iterate):
time = min(timeit.repeat((lambda: fn(n, SET)), number=10))
results['ConvertList'].append(fn.__name__)
results['n'].append(n)
results['seconds'].append(time)
df = pd.DataFrame(results)
df2 = df.pivot('n', 'ConvertList')
df2.plot()
import pylab
pylab.show()
看起来你的收支平衡点是5次完整的迭代。平均为5或更少,这样做是不可能的。只有5个或更多才开始补偿额外的开发时间,复杂性(增加维护成本)以及更多代码行的风险。
我认为你必须做很多事情,值得为你的项目增加复杂性和代码行。
这些结果是使用Anaconda的Python 2.7从Ubuntu 14.04终端创建的。使用不同的实现和版本可能会得到不同的结果。
我关心的是集合是可变的,列表是可变的。一个集合将阻止您在迭代它时修改它,但是从该集合创建的列表将不会:
>>> s = set('abc')
>>> for e in s:
... s.add(e + e.upper())
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: Set changed size during iteration
如果在迭代衍生列表时修改集合,则不会出现错误,告诉您这样做。
>>> for e in list(s):
... s.add(e + e.upper())
这也是我建议使用frozensets和tuples的原因。它将内置防范语义错误的数据更改。
>>> s = frozenset('abc')
>>> t_s = tuple(s)
>>> for e in t_s:
... s.add(e + e.upper())
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
最后,您必须相信自己才能使算法正确无误。我经常告诉我,当我向新用户提醒这些事情时,我给了他很好的建议。他们知道这是一个很好的建议,因为他们最初没有听,并发现它造成了不必要的复杂性,并发症和由此产生的问题。但是,如果你没有做对,那么你也只能责怪自己的逻辑正确性。最大限度地减少可能出错的事情是一种通常值得性能权衡的好处。 而且,如果在处理这个项目时性能(而不是正确性或开发速度)是主要关注点,那么你就不会使用Python。