假设如下:
>>> 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
显然,这将创建一个包含集合中每个成员的新列表,因此如果您的集合非常大,则不是很好。
我想知道这些功能将如何针对不同的集合执行,所以我做了一个基准测试:
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
我还将它包含在上面的基准测试中。它可以与其他两种“快速”解决方案竞争,但两者的区别并不大。
for first_item in muh_set: break
仍然是 Python 3.x 中的最佳方法。诅咒你,圭多。
欢迎来到另一组 Python 3.x 时序,从wr 推断。的优秀Python 2.x 特定响应。与AChampion同样有用的Python 3.x-specific response不同,下面的时间也是上面建议的时间异常值解决方案——包括:
list(s)[0]
, John的新颖的基于序列的解决方案。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
Python 3 中的另一种方式:
next(iter(s))
或者
s.__iter__().__next__()
我使用我编写的实用程序函数。它的名字有点误导,因为它暗示它可能是一个随机项目或类似的东西。
def anyitem(iterable):
try:
return iter(iterable).next()
except StopIteration:
return None
关注@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()
输出:
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
但是,当更改底层集合(例如调用)时,可迭代示例( , )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()
结果是:
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
我通常为小集合做的是创建一种像这样的解析器/转换器方法
def convertSetToList(setName):
return list(setName)
然后我可以使用新列表并按索引号访问
userFields = convertSetToList(user)
name = request.json[userFields[0]]
作为一个列表,您将拥有可能需要使用的所有其他方法
您可以解压缩值以访问元素:
s = set([1, 2, 3])
v1, v2, v3 = s
print(v1,v2,v3)
#1 2 3
如果你只想要第一个元素试试这个: b = (a-set()).pop()
怎么样s.copy().pop()
?我没有计时,但它应该可以工作而且很简单。然而,它最适合小集合,因为它复制整个集合。
另一种选择是使用包含您不关心的值的字典。例如,
poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
...
您可以将键视为一个集合,但它们只是一个数组:
keys = poor_man_set.keys()
print "Some key = %s" % keys[0]
这种选择的副作用是您的代码将向后兼容较旧set
的 Python 预版本。这可能不是最好的答案,但它是另一种选择。
编辑:你甚至可以做这样的事情来隐藏你使用字典而不是数组或集合的事实:
poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
poor_man_set = poor_man_set.keys()