假设如下:
>>> s = set([1, 2, 3])
我如何得到一个值(任何值)不做s.pop()?我希望将项目留在集合中,直到我确定可以删除它—只有在对另一个主机进行异步调用之后才能确定这一点。
又快又脏:
>>> elem = s.pop()
>>> s.add(elem)
但你知道更好的办法吗?理想情况是在常数时间内。
假设如下:
>>> s = set([1, 2, 3])
我如何得到一个值(任何值)不做s.pop()?我希望将项目留在集合中,直到我确定可以删除它—只有在对另一个主机进行异步调用之后才能确定这一点。
又快又脏:
>>> elem = s.pop()
>>> s.add(elem)
但你知道更好的办法吗?理想情况是在常数时间内。
当前回答
我想知道这些函数对于不同的集合会有怎样的表现,所以我做了一个基准测试:
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(免责声明:我是作者)包含了这个用例的方便函数:
>>> from iteration_utilities import first
>>> first({1,2,3,4})
1
我还将它包含在上面的基准测试中。它可以与其他两种“快速”解决方案竞争,但两者之间的差异并不大。
其他回答
最少的代码是:
>>> s = set([1, 2, 3])
>>> list(s)[0]
1
显然,这将创建一个包含集合中的每个成员的新列表,所以如果你的集合非常大,就不太好了。
你可以解包这些值来访问元素:
s = set([1, 2, 3])
v1, v2, v3 = s
print(v1,v2,v3)
#1 2 3
因为你想要一个随机元素,这也可以:
>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]
文档中似乎没有提到random.sample的性能。从一个非常快速的经验测试中,有一个巨大的列表和一个巨大的集合,对于列表来说似乎是常数时间,而对于集合来说则不是。而且,集合上的迭代不是随机的;顺序没有定义,但可以预测:
>>> list(set(range(10))) == range(10)
True
如果随机性很重要,并且你需要在常数时间内(大型集合)使用一堆元素,那么我会使用随机性。先采样并转换为列表:
>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time
博士tl;
对于muh_set中的first_item: break仍然是Python 3.x中的最佳方法。诅咒你,圭多。
你这样做
欢迎来到另一套Python 3。X时序,由wr推断。的优秀Python 2。x-specific响应。不像champion同样有用的Python 3。x特定的响应,下面的时间以及上面建议的时间异常值解决方案-包括:
list(s)[0], John新颖的基于序列的解决方案。 随机的。样品(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
面向整个家庭的Faceplants
不出所料,手动迭代的速度至少是第二快的解决方案的两倍。虽然与Bad Old Python 2相比,差距有所缩小。x天(其中手动迭代的速度至少是它的四倍),最详细的解决方案是最好的,这让我这个PEP 20狂热者感到失望。至少将一个集合转换为一个列表只是为了提取集合的第一个元素是可怕的。感谢圭多,愿他的光芒继续指引我们。
令人惊讶的是,基于rng的解决方案非常糟糕。列表转换是糟糕的,但随机确实是糟糕的。随机数之神也就这么多了。
我只是希望无定形的They已经为我们PEP了一个set.get_first()方法。如果你在读这篇文章,他们会说:“拜托。做点什么。”
我想知道这些函数对于不同的集合会有怎样的表现,所以我做了一个基准测试:
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(免责声明:我是作者)包含了这个用例的方便函数:
>>> from iteration_utilities import first
>>> first({1,2,3,4})
1
我还将它包含在上面的基准测试中。它可以与其他两种“快速”解决方案竞争,但两者之间的差异并不大。