680 Stimmen

Wie kann man ein Element aus einer Menge abrufen, ohne es zu entfernen?

Nehmen Sie Folgendes an:

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

Wie erhalte ich einen Wert (irgendeinen Wert) aus der s ohne zu tun s.pop() ? Ich möchte das Element in der Menge belassen, bis ich sicher bin, dass ich es entfernen kann - etwas, das ich nur nach einem asynchronen Aufruf an einen anderen Host sicher sein kann.

Schnell und schmutzig:

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

Aber kennen Sie einen besseren Weg? Idealerweise in konstanter Zeit.

39 Stimmen

Weiß jemand, warum Python diese Funktion nicht bereits implementiert hat?

7 Stimmen

Was ist der Anwendungsfall? Set hat diese Fähigkeit aus einem bestimmten Grund nicht. Sie sollen durch sie iterieren und Set-bezogene Operationen durchführen wie union usw., ohne Elemente daraus zu entnehmen. Zum Beispiel next(iter({3,2,1})) gibt immer zurück 1 Wenn Sie also dachten, dass dies ein zufälliges Element zurückgeben würde - das stimmt nicht. Vielleicht verwenden Sie also einfach die falsche Datenstruktur? Was ist der Anwendungsfall?

1 Stimmen

Verwandt: stackoverflow.com/questions/20625579/ (Ich weiß, es ist nicht dieselbe Frage, aber es gibt lohnende Alternativen und Einsichten).

839voto

Blair Conrad Punkte 217777

Zwei Optionen, bei denen nicht der gesamte Satz kopiert werden muss:

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

Oder...

e = next(iter(s))

Aber im Allgemeinen unterstützen Sets keine Indizierung oder Slicing.

212voto

John Punkte 14038

Der kleinste Code wäre:

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

Natürlich würde dies eine neue Liste erstellen, die jedes Mitglied der Menge enthält, also nicht gut, wenn Ihre Menge sehr groß ist.

207voto

MSeifert Punkte 131411

Ich habe mich gefragt, wie die Funktionen für verschiedene Sets funktionieren, also habe ich einen Benchmark durchgeführt:

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

Diese Darstellung zeigt deutlich, dass einige Ansätze ( RandomSample , SetUnpacking y ListIndex ) hängen von der Größe der Menge ab und sollten im allgemeinen Fall vermieden werden (zumindest wenn die Leistung könnte wichtig sein). Wie bereits aus den anderen Antworten hervorgeht, ist der schnellste Weg ForLoop .

Solange jedoch einer der Ansätze mit konstanter Zeit verwendet wird, ist der Leistungsunterschied vernachlässigbar.


iteration_utilities (Disclaimer: Ich bin der Autor) enthält eine Komfortfunktion für diesen Anwendungsfall: first :

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

Ich habe sie auch in den Benchmark oben aufgenommen. Er kann mit den beiden anderen "schnellen" Lösungen mithalten, aber der Unterschied ist in beiden Fällen nicht groß.

73voto

Cecil Curry Punkte 8830

Tl;dr

for first_item in muh_set: break bleibt der optimale Ansatz in Python 3.x. Verflucht seist du, Guido.

Warum tun Sie das?

Willkommen zu einem weiteren Satz von Python 3.x Timings, extrapoliert von wr. Der ausgezeichnete Python 2.x-spezifische Antwort . Anders als AChampion ist ebenso hilfreich Python 3.x-spezifische Antwort die nachstehenden Zeitangaben también die oben vorgeschlagenen Lösungen für Zeitausreißer - einschließlich:

Code-Schnipsel für große Freude

Einschalten, abstimmen, Zeit einstellen:

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

Schnell verdrängte zeitlose Zeitabläufe

Seht her! Geordnet nach schnellsten bis langsamsten 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 für die ganze Familie

Das ist nicht überraschend, die manuelle Iteration bleibt mindestens doppelt so schnell als die nächst schnellere Lösung. Obwohl sich der Abstand zu den Tagen von Bad Old Python 2.x (in denen die manuelle Iteration mindestens viermal so schnell war) verringert hat, enttäuscht er die PEP 20 Eiferer in mir, dass die ausführlichste Lösung die beste ist. Zumindest die Umwandlung einer Menge in eine Liste, nur um das erste Element der Menge zu extrahieren, ist so schrecklich wie erwartet. Danke Guido, möge sein Licht uns weiterhin leiten.

Überraschenderweise ist die Die RNG-basierte Lösung ist absolut furchtbar. Listenkonvertierung ist schlecht, aber random vraiment nimmt den Kuchen der schrecklichen Soße. So viel zum Gott der Zufallszahlen .

Ich wünschte nur, dass die amorphen Sie würden PEP up a set.get_first() Methode für uns bereits. Wenn Sie das lesen, sie: "Bitte. Tun Sie etwas."

40voto

wr. Punkte 2831

Um die verschiedenen Ansätze mit Zahlen zu untermauern, betrachten Sie den folgenden Code. Die Funktion get() ist meine eigene Ergänzung zu Pythons setobject.c, die nur ein pop() ist, ohne das Element zu entfernen.

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

Die Ausgabe ist:

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

Dies bedeutet, dass die für/zu brechen Lösung ist die schnellste (manchmal schneller als die benutzerdefinierte get()-Lösung).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X