How to retrieve an element from a set without removing it?

前端 未结 14 2568
孤城傲影
孤城傲影 2020-12-07 07:17

Suppose the following:

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

How do I get a value (any value) out of s without doing s.pop()

相关标签:
14条回答
  • 2020-12-07 07:50

    Another option is to use a dictionary with values you don't care about. E.g.,

    
    poor_man_set = {}
    poor_man_set[1] = None
    poor_man_set[2] = None
    poor_man_set[3] = None
    ...
    

    You can treat the keys as a set except that they're just an array:

    
    keys = poor_man_set.keys()
    print "Some key = %s" % keys[0]
    

    A side effect of this choice is that your code will be backwards compatible with older, pre-set versions of Python. It's maybe not the best answer but it's another option.

    Edit: You can even do something like this to hide the fact that you used a dict instead of an array or set:

    
    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()
    
    0 讨论(0)
  • 2020-12-07 07:52

    Two options that don't require copying the whole set:

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

    Or...

    e = next(iter(s))
    

    But in general, sets don't support indexing or slicing.

    0 讨论(0)
  • 2020-12-07 07:55

    Least code would be:

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

    Obviously this would create a new list which contains each member of the set, so not great if your set is very large.

    0 讨论(0)
  • 2020-12-07 08:00

    Yet another way in Python 3:

    s.__iter__().__next__()
    
    0 讨论(0)
  • 2020-12-07 08:03

    Following @wr. post, I get similar results (for 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()
    

    Output:

    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
    

    However, when changing the underlying set (e.g. call to remove()) things go badly for the iterable examples (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()
    

    Results in:

    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
    
    0 讨论(0)
  • 2020-12-07 08:04

    tl;dr

    for first_item in muh_set: break remains the optimal approach in Python 3.x. Curse you, Guido.

    y u do this

    Welcome to yet another set of Python 3.x timings, extrapolated from wr.'s excellent Python 2.x-specific response. Unlike AChampion's equally helpful Python 3.x-specific response, the timings below also time outlier solutions suggested above – including:

    • list(s)[0], John's novel sequence-based solution.
    • random.sample(s, 1), dF.'s eclectic RNG-based solution.

    Code Snippets for Great Joy

    Turn on, tune in, time it:

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

    Quickly Obsoleted Timeless Timings

    Behold! Ordered by fastest to slowest snippets:

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

    Faceplants for the Whole Family

    Unsurprisingly, manual iteration remains at least twice as fast as the next fastest solution. Although the gap has decreased from the Bad Old Python 2.x days (in which manual iteration was at least four times as fast), it disappoints the PEP 20 zealot in me that the most verbose solution is the best. At least converting a set into a list just to extract the first element of the set is as horrible as expected. Thank Guido, may his light continue to guide us.

    Surprisingly, the RNG-based solution is absolutely horrible. List conversion is bad, but random really takes the awful-sauce cake. So much for the Random Number God.

    I just wish the amorphous They would PEP up a set.get_first() method for us already. If you're reading this, They: "Please. Do something."

    0 讨论(0)
提交回复
热议问题