如何从一个集合中检索一个元素而不删除它?

假设如下:

>>>s = set([1, 2, 3]) 

如何在不执行s.pop()的情况下获得一个值(任何值)? 我想把这个项目留在这个集合中,直到我确信我可以删除它 – 我只能在asynchronous调用另一个主机之后才能确定它。

快速和肮脏:

 >>>elem = s.pop() >>>s.add(elem) 

但是你知道更好的方法吗? 理想的是在不变的时间。

两个选项不需要复制整个集合:

 for e in s: break # e is now an element from s 

要么…

 e = next(iter(s)) 

但一般来说,集合不支持索引或切片。

最less的代码是:

 >>> s = set([1, 2, 3]) >>> list(s)[0] 1 

显然,这将创build一个新的列表,其中包含每个成员的集合,所以不是很好,如果你的设置是非常大的。

为了提供不同方法背后的时序数据,请考虑以下代码。 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解决scheme是最快的(有时比自定义get()解决scheme更快)。

既然你想要一个随机元素,这也将工作:

 >>> import random >>> s = set([1,2,3]) >>> random.sample(s, 1) [2] 

这个文档似乎没有提到random.sample性能。 从一个巨大的清单和一个庞大的集合的真正快速的经验testing,似乎是一个恒定的时间,而不是集合。 另外,对集合的迭代不是随机的; 订单不确定但可预测:

 >>> list(set(range(10))) == range(10) True 

如果随机性很重要,并且你需要一些常量(大集合)的元素,我会使用random.sample并首先转换成列表:

 >>> lst = list(s) # once, O(len(s))? ... >>> e = random.sample(lst, 1)[0] # constant time 

TL;博士

for first_item in muh_set: break仍然是Python 3.x中的最佳方法。 诅咒你,Guido。

你这样做

欢迎来到另外一套Python 3.x时序,从wr推断出来。 非常出色的Python 2.x特有的响应 。 与AChampion同样有用的Python 3.x特有的响应不同 ,下面的时间也是上面build议的时间exception解决scheme – 包括:

  • list(s)[0] , 约翰的小说序列为基础的解决scheme 。
  • random.sample(s, 1) , dF。 不拘一格的RNG解决scheme 。

代码片断为伟大的喜悦

打开,调入,时间:

 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() 

迅速过时的永恒时间

看哪! 由最快到最慢的片段sorting:

 $ ./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 

整个家庭的面孔植物

不出所料, 手动迭代至less是下一个最快解决scheme的两倍 。 虽然差距已经从糟糕的老Python 2.x天(其中手动迭代至less是四倍的速度)下降了,但是令我失望的是PEP 20狂热分子,最详细的解决scheme是最好的。 至less将一个集合转换成一个列表来提取集合的第一个元素,就像预期的那样糟糕透顶。 感谢Guido,愿他的光明继续引导我们。

令人惊讶的是, 基于RNG的解决scheme是非常可怕的。 列表转换不好,但random 真的把可怕的酱饼。 对于随机数字神来说太多了。

我只是希望不定形他们会为我们提供一个set.get_first()方法。 如果你正在读这个,他们:“请,做点什么。”

我使用了我写的效用函数。 它的名字有点误导,因为它暗示它可能是一个随机项目或类似的东西。

 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 

看起来最紧凑的 (6个符号),但通过非常缓慢的方式来获得一个设置元素(使PEP 3132成为可能):

 e,*_=s 

使用Python 3.5+,你也可以使用这个7符号expression式(感谢PEP 448 ):

 [*s][0] 

我的机器上的两个选项比循环方法慢大约1000倍。

另一个select是使用一个你不关心的值的字典。 例如,

 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] 

这个select的一个副作用是你的代码将向后兼容较老的,预先set的Python版本。 这可能不是最好的答案,但这是另一种select。

编辑:你甚至可以做这样的事情来隐藏你使用字典而不是数组或集合的事实:

 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()