假设如下:
>>> s = set([1, 2, 3])
Run Code Online (Sandbox Code Playgroud)
如何获得的值(任意值)出来s而不做s.pop()?我想把这个项留在集合中,直到我确定我可以删除它 - 我只能在异步调用另一个主机后才能确定.
又快又脏:
>>> elem = s.pop()
>>> s.add(elem)
Run Code Online (Sandbox Code Playgroud)
但是你知道更好的方法吗?理想情况下在恒定的时间.
Bla*_*rad 481
两个不需要复制整个集合的选项:
for e in s:
break
# e is now an element from s
Run Code Online (Sandbox Code Playgroud)
要么...
e = next(iter(s))
Run Code Online (Sandbox Code Playgroud)
但通常,集合不支持索引或切片.
Joh*_*ohn 91
最少的代码是:
>>> s = set([1, 2, 3])
>>> list(s)[0]
1
Run Code Online (Sandbox Code Playgroud)
显然,这将创建一个包含该集合中每个成员的新列表,因此如果您的集合非常大,那么就不会很好.
Cec*_*rry 40
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()
Run Code Online (Sandbox Code Playgroud)
看哪!按最快到最慢的片段排序:
$ ./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
Run Code Online (Sandbox Code Playgroud)
不出所料,手动迭代至少是下一个最快解决方案的两倍.虽然差距已经从Bad Old Python 2.x天(其中手动迭代至少快四倍)减少,但令我失望的是PEP 20狂热者,最详细的解决方案是最好的.至少将一个集合转换为一个列表来提取集合的第一个元素就像预期的那样可怕.感谢Guido,愿他的光继续引导我们.
令人惊讶的是,基于RNG的解决方案绝对是可怕的.列表转换很糟糕,但random 真的需要糟糕的蛋糕.对于随机数上帝来说太多了.
我只是希望无定形他们会set.get_first()为我们提供一种方法.如果你正在读这篇文章,他们会说:"请.做点什么吧."
wr.*_*wr. 38
要提供不同方法背后的一些时序数据,请考虑以下代码. 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()
Run Code Online (Sandbox Code Playgroud)
输出是:
$ ./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
Run Code Online (Sandbox Code Playgroud)
这意味着for/break解决方案是最快的(有时比自定义get()解决方案更快).
dF.*_*dF. 28
既然你想要一个随机元素,这也可以:
>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]
Run Code Online (Sandbox Code Playgroud)
文档似乎没有提到性能random.sample.从一个非常快速的经验测试中获得一个巨大的列表和一个巨大的集合,它似乎是一个列表的常量时间,但不是集合.此外,对集合的迭代不是随机的; 订单未定义但可预测:
>>> list(set(range(10))) == range(10)
True
Run Code Online (Sandbox Code Playgroud)
如果随机性很重要,并且你需要在恒定时间(大集合)中使用一堆元素,我首先使用random.sample并转换为列表:
>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time
Run Code Online (Sandbox Code Playgroud)
MSe*_*ert 27
我想知道函数将如何针对不同的集合执行,所以我做了一个基准测试:
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()
Run Code Online (Sandbox Code Playgroud)
该图清楚地表明,某些方法(RandomSample,SetUnpacking和ListIndex)取决于集合的大小,在一般情况下应该避免(至少如果性能可能很重要).正如其他答案所示,最快的方法是ForLoop.
然而,只要使用其中一个恒定时间方法,性能差异就可以忽略不计.
iteration_utilities(免责声明:我是作者)包含这个用例的便利功能first:
>>> from iteration_utilities import first
>>> first({1,2,3,4})
1
Run Code Online (Sandbox Code Playgroud)
我还把它包含在上面的基准测试中.它可以与其他两个"快速"解决方案竞争,但差别不大.
dza*_*ang 25
Python 3 中的另一种方式:
next(iter(s))
Run Code Online (Sandbox Code Playgroud)
或者
s.__iter__().__next__()
Run Code Online (Sandbox Code Playgroud)
我使用了我写的实用函数.它的名字有点误导,因为它暗示它可能是一个随机项目或类似的东西.
def anyitem(iterable):
try:
return iter(iterable).next()
except StopIteration:
return None
Run Code Online (Sandbox Code Playgroud)
关注@wr。帖子,我得到了类似的结果(对于Python3.5)
from timeit import *
stats = ["for i in range(1000): next(iter(s))",
"for i in range(1000): \n\tfor x in s: \n\t\tbreak",
"for i in range(1000): s.add(s.pop())"]
for stat in stats:
t = Timer(stat, setup="s=set(range(100000))")
try:
print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
except:
t.print_exc()
Run Code Online (Sandbox Code Playgroud)
输出:
Time for for i in range(1000): next(iter(s)): 0.205888
Time for for i in range(1000):
for x in s:
break: 0.083397
Time for for i in range(1000): s.add(s.pop()): 0.226570
Run Code Online (Sandbox Code Playgroud)
然而,当更改底层集合(例如调用remove())时,可迭代示例(for、iter)的情况会变得很糟糕:
from timeit import *
stats = ["while s:\n\ta = next(iter(s))\n\ts.remove(a)",
"while s:\n\tfor x in s: break\n\ts.remove(x)",
"while s:\n\tx=s.pop()\n\ts.add(x)\n\ts.remove(x)"]
for stat in stats:
t = Timer(stat, setup="s=set(range(100000))")
try:
print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
except:
t.print_exc()
Run Code Online (Sandbox Code Playgroud)
结果是:
Time for while s:
a = next(iter(s))
s.remove(a): 2.938494
Time for while s:
for x in s: break
s.remove(x): 2.728367
Time for while s:
x=s.pop()
s.add(x)
s.remove(x): 0.030272
Run Code Online (Sandbox Code Playgroud)
看似最紧凑(6个符号)虽然获取设定元素的速度很慢(PEP 3132可以实现):
e,*_=s
Run Code Online (Sandbox Code Playgroud)
使用Python 3.5+,您还可以使用此7符号表达式(感谢PEP 448):
[*s][0]
Run Code Online (Sandbox Code Playgroud)
这两个选项在我的机器上比for-loop方法慢大约1000倍.
| 归档时间: |
|
| 查看次数: |
371220 次 |
| 最近记录: |