假设如下:
>>> s = set([1, 2, 3])
如何获得的值(任意值)出来s
而不做s.pop()
?我想把这个项留在集合中,直到我确定我可以删除它 - 我只能在异步调用另一个主机后才能确定.
又快又脏:
>>> elem = s.pop() >>> s.add(elem)
但是你知道更好的方法吗?理想情况下在恒定的时间.
两个不需要复制整个集合的选项:
for e in s: break # e is now an element from s
要么...
e = next(iter(s))
但通常,集合不支持索引或切片.
最少的代码是:
>>> s = set([1, 2, 3]) >>> list(s)[0] 1
显然,这将创建一个包含该集合中每个成员的新列表,因此如果您的集合非常大,那么就不会很好.
for first_item in muh_set: break
仍然是Python 3.x中的最佳方法.诅咒你,圭多.
欢迎来到另一组Python 3.x时序,从wr推断.特别是Python 2.x特有的响应.与AChampion同样有用的Python 3.x特定响应不同,下面的时间安排也是上面提出的时间异常解决方案 - 包括:
list(s)[0]
,约翰的新颖的基于序列的解决方案.
random.sample(s, 1)
,dF.不拘一格的基于RNG的解决方案.
打开,收听,计时:
from timeit import Timer stats = [ "for i in range(1000): \n\tfor x in s: \n\t\tbreak", "for i in range(1000): next(iter(s))", "for i in range(1000): s.add(s.pop())", "for i in range(1000): list(s)[0]", "for i in range(1000): random.sample(s, 1)", ] for stat in stats: t = Timer(stat, setup="import random\ns=set(range(100))") try: print("Time for %s:\t %f"%(stat, t.timeit(number=1000))) except: t.print_exc()
看哪!按最快到最慢的片段排序:
$ ./test_get.py Time for for i in range(1000): for x in s: break: 0.249871 Time for for i in range(1000): next(iter(s)): 0.526266 Time for for i in range(1000): s.add(s.pop()): 0.658832 Time for for i in range(1000): list(s)[0]: 4.117106 Time for for i in range(1000): random.sample(s, 1): 21.851104
不出所料,手动迭代至少是下一个最快解决方案的两倍.虽然差距已经从Bad Old Python 2.x天(其中手动迭代至少快四倍)减少,但令我失望的是PEP 20狂热者,最详细的解决方案是最好的.至少将一个集合转换为一个列表来提取集合的第一个元素就像预期的那样可怕.感谢Guido,愿他的光继续引导我们.
令人惊讶的是,基于RNG的解决方案绝对是可怕的.列表转换很糟糕,但random
真的需要糟糕的蛋糕.对于随机数上帝来说太多了.
我只是希望无定形他们会set.get_first()
为我们提供一种方法.如果你正在读这篇文章,他们会说:"请.做点什么吧."
要提供不同方法背后的一些时序数据,请考虑以下代码. get()是我对Python的setobject.c的自定义添加,只是一个pop()而不删除元素.
from timeit import * stats = ["for i in xrange(1000): iter(s).next() ", "for i in xrange(1000): \n\tfor x in s: \n\t\tbreak", "for i in xrange(1000): s.add(s.pop()) ", "for i in xrange(1000): s.get() "] for stat in stats: t = Timer(stat, setup="s=set(range(100))") try: print "Time for %s:\t %f"%(stat, t.timeit(number=1000)) except: t.print_exc()
输出是:
$ ./test_get.py Time for for i in xrange(1000): iter(s).next() : 0.433080 Time for for i in xrange(1000): for x in s: break: 0.148695 Time for for i in xrange(1000): s.add(s.pop()) : 0.317418 Time for for i in xrange(1000): s.get() : 0.146673
这意味着for/break解决方案是最快的(有时比自定义get()解决方案更快).
既然你想要一个随机元素,这也可以:
>>> import random >>> s = set([1,2,3]) >>> random.sample(s, 1) [2]
文档似乎没有提到性能random.sample
.从一个非常快速的经验测试中获得一个巨大的列表和一个巨大的集合,它似乎是一个列表的常量时间,但不是集合.此外,对集合的迭代不是随机的; 订单未定义但可预测:
>>> list(set(range(10))) == range(10) True
如果随机性很重要,并且你需要在恒定时间(大集合)中使用一堆元素,我首先使用random.sample
并转换为列表:
>>> lst = list(s) # once, O(len(s))? ... >>> e = random.sample(lst, 1)[0] # constant time
我想知道函数将如何针对不同的集合执行,所以我做了一个基准测试:
from random import sample def ForLoop(s): for e in s: break return e def IterNext(s): return next(iter(s)) def ListIndex(s): return list(s)[0] def PopAdd(s): e = s.pop() s.add(e) return e def RandomSample(s): return sample(s, 1) def SetUnpacking(s): e, *_ = s return e from simple_benchmark import benchmark b = benchmark([ForLoop, IterNext, ListIndex, PopAdd, RandomSample, SetUnpacking], {2**i: set(range(2**i)) for i in range(1, 20)}, argument_name='set size', function_aliases={first: 'First'}) b.plot()
该图清楚地表明,某些方法(RandomSample
,SetUnpacking
和ListIndex
)取决于集合的大小,在一般情况下应该避免(至少如果性能可能很重要).正如其他答案所示,最快的方法是ForLoop
.
然而,只要使用其中一个恒定时间方法,性能差异就可以忽略不计.
iteration_utilities
(免责声明:我是作者)包含这个用例的便利功能first
:
>>> from iteration_utilities import first >>> first({1,2,3,4}) 1
我还把它包含在上面的基准测试中.它可以与其他两个"快速"解决方案竞争,但差别不大.
我使用了我写的实用函数.它的名字有点误导,因为它暗示它可能是一个随机项目或类似的东西.
def anyitem(iterable): try: return iter(iterable).next() except StopIteration: return None
看似最紧凑(6个符号)虽然获取设定元素的速度很慢(PEP 3132可以实现):
e,*_=s
使用Python 3.5+,您还可以使用此7符号表达式(感谢PEP 448):
[*s][0]
这两个选项在我的机器上比for-loop方法慢大约1000倍.