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

Dar*_*mas 381 python set

假设如下:

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

但通常,集合不支持索引或切片.

  • 我不认为iter()正在对元素进行排序 - 当我创建一个set和pop()直到它为空时,我得到一致(在我的例子中排序)排序,它与iterator相同 - pop( )不承诺随机顺序,只是随意,如"我保证一无所有". (6认同)
  • +1 `iter(s).next()` 不粗糙但很棒。从任何可迭代对象中获取任意元素是完全通用的。如果您想小心集合是否为空,您的选择。 (5认同)
  • 下一个(iter(s))也行,我倾向于认为它读得更好.此外,当s为空时,您可以使用标记来处理该情况.例如next(iter(s),set()). (5认同)
  • 这回答了我的问题.唉,我想我仍然会使用pop(),因为迭代似乎对元素进行排序.我希望它们随机排列...... (4认同)
  • `next(iter(your_list或[]),None)`处理无集和空集 (2认同)

Joh*_*ohn 91

最少的代码是:

>>> s = set([1, 2, 3])
>>> list(s)[0]
1
Run Code Online (Sandbox Code Playgroud)

显然,这将创建一个包含该集合中每个成员的新列表,因此如果您的集合非常大,那么就不会很好.

  • `next(iter(s))`仅超过`list(s)[0]`**三个字符**,并且在时间和空间复杂性方面都显着优越.因此,虽然"最少代码"的主张是微不足道的,但这也是最糟糕的方法.即使手动移除然后将移除的元素重新添加到原始集合也优于"构造一个全新的容器以仅提取第一个元素",这显然是疯狂的.令我更担心的是,38个Stackoverflowers实际上支持了这一点.我只知道我会在生产代码中看到这一点. (76认同)
  • @augurar:因为它以相对简单的方式完成工作.有时候,快速的脚本就是最重要的. (15认同)
  • 同样,如果您只是针对“最少的代码”(愚蠢的),那么`min(s)`会使用更少的字符,而与此同时却又如此可怕和低效。 (6认同)
  • @Vicrobot是的,但是它是通过复制整个集合并将O(1)操作转换为O(n)操作来实现的。这是一个可怕的解决方案,任何人都不要使用。 (4认同)
  • 如果 set 保证是一个元素,则可以将其解压缩为一个元组。`元素,= s` (4认同)
  • @augurar我认为人们对这个答案投了赞成票,因为`set`不是主要用于索引和切片的。而这个用户只是改变了编码器,以使用适当的数据类型来执行此类工作,即“列表”。 (2认同)
  • 高尔夫代码获胜者+1,我有一个“糟糕而低效”的实际反例:对于大小为1的组,“ min(s)”比“ next(iter(s))”稍快,我来了这个答案专门针对特殊情况,从大小为1的集合中提取唯一元素。 (2认同)
  • @augurar 这个解决方案还允许我检索任何索引(例如`list(s)[2]`)——在我的“小测试世界”中,这非常方便! (2认同)

Cec*_*rry 40

TL;博士

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

你这样做

欢迎来到另一组Python 3.x时序,从wr推断.特别是Python 2.x特有的响应.与AChampion同样有用的Python 3.x特定响应不同,下面的时间安排也是上面提出的时间异常解决方案 - 包括:

伟大的喜悦代码片段

打开,收听,计时:

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()为我们提供一种方法.如果你正在读这篇文章,他们会说:"请.做点什么吧."

  • 由于集合没有排序,因此“set.get_first()”可能会产生误导。但我想要一个“set.get_any()”,它返回集合中的任何元素,即使该元素始终相同。 (6认同)
  • 我认为抱怨 `next(iter(s))` 比 `CPython` 中的 `for x in s: break` 慢两倍有点奇怪。我的意思是那是`CPython`。它比 C 或 Haskell 做同样的事情慢大约 50-100 倍(或类似的东西)(在大多数情况下,尤其是在迭代中,没有尾调用消除和任何优化。)。失去一些微秒并没有真正的区别。你不觉得吗?还有 PyPy (2认同)

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()解决方案更快).

  • @Ryan:不是为`for s in s`隐式创建的迭代器对象吗?["被用于`expression_list`的结果而创建的迭代器."](https://docs.python.org/2/reference/compound_stmts.html#the-for-statement) (3认同)
  • @musiphil是的;最初我错过了0.14的“突破”,这确实违反直觉。有空的时候,我想对此进行深入研究。 (2认同)

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)

  • 如果你只想要一个元素,random.choice更明智. (14认同)
  • @Gregg:你不能使用`choice()`,因为Python [将尝试索引你的集合](https://hg.python.org/cpython/file/754c630c98a3/Lib/random.py#l250)和这不起作用. (8认同)
  • 虽然很聪明,但这实际上是最慢的解决方案,但仍然是一个数量级的建议.**是的,它是_慢的.甚至将集合转换为列表只是为了提取该列表的第一个元素更快.对于我们中间的非信徒(_... hi!_),请参阅这些[神话般的时间](/sf/answers/2803813491/). (2认同)

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,SetUnpackingListIndex)取决于集合的大小,在一般情况下应该避免(至少如果性能可能很重要).正如其他答案所示,最快的方法是ForLoop.

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


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

>>> from iteration_utilities import first
>>> first({1,2,3,4})
1
Run Code Online (Sandbox Code Playgroud)

我还把它包含在上面的基准测试中.它可以与其他两个"快速"解决方案竞争,但差别不大.

  • 这是一个很好的答案。感谢您投入时间进行实证研究。 (3认同)

dza*_*ang 25

Python 3 中的另一种方式:

next(iter(s))
Run Code Online (Sandbox Code Playgroud)

或者

s.__iter__().__next__()
Run Code Online (Sandbox Code Playgroud)

  • `next(iter(s))` 会做同样的事情,但会更短,更Pythonic。 (2认同)

Nic*_*ick 6

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

def anyitem(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None
Run Code Online (Sandbox Code Playgroud)

  • 您也可以使用next(iter(iterable),None)来节省墨水:) (2认同)

ACh*_*ion 6

关注@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())时,可迭代示例(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()
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)


sko*_*kin 5

看似最紧凑(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倍.

  • for 循环方法(或更准确地说是迭代器方法)的时间复杂度为 O(1),而这些方法的时间复杂度为 O(N)。不过它们*简洁*。:) (6认同)