Domanda

Supponiamo quanto segue:

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

Come ottengo un valore (qualsiasi valore) da s senza fare s.pop()?Voglio lasciare l'elemento nel set finché non sono sicuro di poterlo rimuovere, cosa di cui posso essere sicuro solo dopo una chiamata asincrona a un altro host.

Veloce e sporco:

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

Ma conosci un modo migliore?Idealmente in tempo costante.

È stato utile?

Soluzione

Due opzioni che non richiedono la copia dell'intero set:

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

O...

e = next(iter(s))

Ma in generale, i set non supportano l'indicizzazione o l'affettamento.

Altri suggerimenti

Il codice minimo sarebbe:

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

Ovviamente questo creerebbe un nuovo elenco che contiene ogni membro del set, quindi non eccezionale se il tuo set è molto grande.

Per fornire alcune cifre temporali alla base dei diversi approcci, considerare il codice seguente.get() è la mia aggiunta personalizzata a setobject.c di Python, essendo solo un pop() senza rimuovere l'elemento.

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

L'output è:

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

Ciò significa che il per/rompere la soluzione è la più veloce (a volte più veloce della soluzione get() personalizzata).

tl; dott

for first_item in muh_set: break rimane l'approccio ottimale in Python 3.x. Che tu sia maledetto, Guido.

fai questo

Benvenuti in un'altra serie di tempistiche di Python 3.x, estrapolate da wr.è eccellente Risposta specifica di Python 2.x.A differenza di Un campioneè altrettanto utile Risposta specifica di Python 3.x, gli orari di seguito Anche soluzioni temporali anomali suggerite sopra, tra cui:

Snippet di codice per una grande gioia

Accendi, sintonizzati, cronometra:

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

Tempi senza tempo rapidamente obsoleti

Ecco! Ordinati dagli snippet dal più veloce al più lento:

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

Piante per il viso per tutta la famiglia

Non sorprende che l'iterazione manuale rimane almeno due volte più veloce come la prossima soluzione più veloce.Sebbene il divario sia diminuito rispetto ai giorni di Bad Old Python 2.x (in cui l'iterazione manuale era almeno quattro volte più veloce), delude il PEP 20 fanatico in me che la soluzione più dettagliata sia la migliore.Almeno convertire un set in un elenco solo per estrarre il primo elemento del set è orribile come previsto. Grazie Guido, che la sua luce continui a guidarci.

Sorprendentemente, il La soluzione basata su RNG è assolutamente orribile. La conversione dell'elenco è negativa, ma random Veramente prende la torta con salsa orribile.Questo per quanto riguarda il Dio dei numeri casuali.

Vorrei solo che gli amorfi si PEP a set.get_first() metodo già per noi.Se stai leggendo questo, loro:"Per favore.Fare qualcosa."

Dato che vuoi un elemento casuale, funzionerà anche questo:

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

La documentazione non sembra menzionare le prestazioni di random.sample.Da un test empirico molto rapido con un elenco e un insieme enormi, sembra che il tempo sia costante per un elenco ma non per l'insieme.Inoltre, l'iterazione su un set non è casuale;l’ordine è indefinito ma prevedibile:

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

Se la casualità è importante e hai bisogno di un gruppo di elementi in tempo costante (set di grandi dimensioni), lo userei random.sample e converti prima in un elenco:

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

Mi chiedevo come si comporteranno le funzioni per set diversi, quindi ho fatto un benchmark:

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

Questo grafico mostra chiaramente che alcuni approcci (RandomSample, SetUnpacking E ListIndex) dipendono dalle dimensioni dell'apparecchio e dovrebbero essere evitati in generale (almeno se performance Potrebbe essere importante).Come già mostrato dalle altre risposte, il modo più veloce è ForLoop.

Tuttavia, finché viene utilizzato uno degli approcci a tempo costante, la differenza di prestazioni sarà trascurabile.


iteration_utilities (Disclaimer:Sono l'autore) contiene una funzione utile per questo caso d'uso: first:

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

L'ho incluso anche nel benchmark sopra.Può competere con le altre due soluzioni “veloci” ma la differenza non è molta in ogni caso.

Utilizzo una funzione di utilità che ho scritto.Il suo nome è in qualche modo fuorviante perché lascia intendere che potrebbe trattarsi di un oggetto casuale o qualcosa del genere.

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

Apparentemente il più compatto (6 simboli) però molto lento modo per ottenere un elemento impostato (reso possibile da PEP 3132):

e,*_=s

Con Python 3.5+ puoi anche usare questa espressione a 7 simboli (grazie a PEP 448):

[*s][0]

Entrambe le opzioni sono circa 1000 volte più lente sulla mia macchina rispetto al metodo for-loop.

Seguendo @wr.post, ottengo risultati simili (per 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()

Produzione:

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

Tuttavia, quando si modifica il set sottostante (ad es.chiama a remove()) le cose vanno male per gli esempi iterabili (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()

Risultati 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

Che ne dite di s.copy().pop()?Non l'ho cronometrato, ma dovrebbe funzionare ed è semplice.Funziona meglio per i set piccoli, poiché copia l'intero set.

Un'altra opzione è utilizzare un dizionario con valori che non ti interessano.Per esempio.,


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

Puoi trattare le chiavi come un set tranne che sono solo un array:


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

Un effetto collaterale di questa scelta è che il codice sarà retrocompatibile con versioni precedenti e pre-set versioni di Python.Forse non è la risposta migliore, ma è un'altra opzione.

Modificare:Puoi anche fare qualcosa del genere per nascondere il fatto che hai usato un dict invece di un array o 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()
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top