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

时间:2008-09-12 19:58:33

标签: python set

假设以下内容:

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

如果不s,我如何从s.pop()中获取值(任何值)?我想把这个项留在集合中,直到我确定我可以删除它 - 我只能在异步调用另一个主机后确定这一点。

又快又脏:

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

但你知道更好的方法吗?理想情况下,在不变的时间。

15 个答案:

答案 0 :(得分:438)

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

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

或者...

e = next(iter(s))

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

答案 1 :(得分:82)

最少的代码是:

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

显然这会创建一个包含该集合中每个成员的新列表,所以如果你的集合非常大,那就太好了。

答案 2 :(得分:36)

要提供不同方法背后的一些时序数据,请考虑以下代码。 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()解决方案更快。)

答案 3 :(得分:34)

TL;博士

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

你这样做

欢迎使用另一组Python 3.x时序,从wr.的优秀Python 2.x-specific response推断出来。与AChampion同样有帮助的Python 3.x-specific response不同,下面的时间也是以上建议的时间异常解决方案 - 包括:

欢乐代码片段

打开,收听,计时:

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 真的带来了糟糕的蛋糕。对于Random Number God来说非常重要。

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

答案 4 :(得分:26)

由于您需要随机元素,因此也可以使用:

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

答案 5 :(得分:18)

我想知道这些函数将如何针对不同的集合执行,因此我做了一个基准测试:

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

enter image description here

这个图清楚地表明一些方法(RandomSampleSetUnpackingListIndex)取决于集合的大小,在一般情况下应该避免(至少如果性能< em>可能很重要)。正如其他答案所示,最快的方式是ForLoop

然而,只要使用其中一个恒定时间方法,性能差异就可以忽略不计。

iteration_utilities(免责声明:我是作者)包含此用例的便捷功能:first

>>> from iteration_utilities import first
>>> first({1,2,3,4})
1

我也把它包括在上面的基准中。它可以与其他两个&#34; fast&#34;解决方案,但差异不是很大。

答案 6 :(得分:5)

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

def anyitem(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

答案 7 :(得分:4)

看似最紧凑(6个符号)虽然非常慢获取set元素的方式(由PEP 3132生成):

e,*_=s

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

[*s][0]

我的机器上的两个选项比for-loop方法慢大约1000倍。

答案 8 :(得分:2)

关注@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())时,可迭代示例(foriter)的内容就会很糟糕:

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

答案 9 :(得分:1)

我通常对小型馆藏所做的是创建这样的解析器/转换器方法

def convertSetToList(setName):
return list(setName)

然后我可以使用新列表并按索引号访问

userFields = convertSetToList(user)
name = request.json[userFields[0]]

作为列表,您将拥有可能需要使用的所有其他方法

答案 10 :(得分:1)

您可以解压缩值以访问元素:

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

v1, v2, v3 = s

print(v1,v2,v3)
#1 2 3

答案 11 :(得分:1)

Python 3中的另一种方式:

s.__iter__().__next__()

答案 12 :(得分:0)

如果你只想要第一个元素,试试这个: b = (a-set()).pop()

答案 13 :(得分:-1)

s.copy().pop()怎么样?我没有计时,但它应该有效,而且很简单。然而,它适用于小型集合,因为它复制了整个集合。

答案 14 :(得分:-2)

另一种选择是使用具有您不关心的值的字典。如,


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。这可能不是最佳答案,但它是另一种选择。

编辑:您甚至可以执行以下操作来隐藏您使用dict而不是数组或集合的事实:


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