Controlla se qualcosa è (non) in un elenco in Python


314

Ho un elenco di tuple in Python e ho un condizionale in cui voglio prendere il ramo SOLO se la tupla non è nell'elenco (se è nell'elenco, quindi non voglio prendere il ramo if)

if curr_x -1 > 0 and (curr_x-1 , curr_y) not in myList: 

    # Do Something

Questo non funziona davvero per me però. Cosa ho fatto di sbagliato?


1
Si noti che 3 -1 > 0 and (4-1 , 5) not in []Truepertanto l'errore non è una priorità dell'operatore.
Dan D.

6
Cosa intendi con "non lavorare davvero per me"? Cosa ti aspetti che accada? Cosa succede realmente? Quali contenuti dell'elenco esatto innescano il problema?
Karl Knechtel,

Perché non provare myList.count((curr_x, curr_y)), se (curr_x, curr_y)non lo è myList, il risultato sarà0
LittleLittleQ


2
In che modo questa domanda "il mio codice non funziona davvero per me" ha ottenuto 297 voti positivi? Ti preghiamo di darci un esempio riproducibile minimo .
Gerrit,

Risposte:


503

Il bug è probabilmente da qualche altra parte nel tuo codice, perché dovrebbe funzionare bene:

>>> 3 not in [2, 3, 4]
False
>>> 3 not in [4, 5, 6]
True

O con le tuple:

>>> (2, 3) not in [(2, 3), (5, 6), (9, 1)]
False
>>> (2, 3) not in [(2, 7), (7, 3), "hi"]
True

11
@Zack: se non lo sapevi, potresti semplicemente farloif not ELEMENT in COLLECTION:
ninjagecko

@ninjagecko: a seconda del tipo di contenitore che potrebbe essere meno efficiente o addirittura errato. Vedi ad esempio i filtri di fioritura .
orlp,

14
@nightcracker Non ha senso poiché A not in Bsi riduce a fare ciò not B.__contains__(A)che è lo stesso di ciò che not A in Bè ridotto a quale è not B.__contains__(A).
Dan D.

1
Oh wow, avrei potuto giurare che Python avesse qualcosa del genere __notcontains__. Mi dispiace, allora quello che ho detto è solo una cazzata.
orlp,

2
@ std''OrgnlDave L'unico modo che potrebbe accadere è se notavesse una precedenza superiore a quella inche non accade. Considera il risultato di ast.dump(ast.parse("not A in B").body[0])cui risultati "Expr(value=UnaryOp(op=Not(), operand=Compare(left=Name(id='A', ctx=Load()), ops=[In()], comparators=[Name(id='B', ctx=Load())])))"Se notraggruppati strettamente in A, ci si aspetterebbe che il risultato sia quello "Expr(value=Compare(left=UnaryOp(op=Not(), operand=Name(id='A', ctx=Load())), ops=[In()], comparators=[Name(id='B', ctx=Load())]))"che è l'analisi "(not A) in B".
Dan D.

20

Come posso verificare se qualcosa è (non) in un elenco in Python?

La soluzione più economica e più leggibile sta utilizzando l' inoperatore (o nel tuo caso specifico not in). Come menzionato nella documentazione,

Gli operatori ine not intest per l'adesione. x in svaluta Truese xè membro di s, e Falsealtrimenti. x not in srestituisce la negazione di x in s.

Inoltre,

L'operatore not inè definito per avere il valore reale inverso di in.

y not in xè logicamente uguale a not y in x.

Ecco alcuni esempi:

'a' in [1, 2, 3]
# False

'c' in ['a', 'b', 'c']
# True

'a' not in [1, 2, 3]
# True

'c' not in ['a', 'b', 'c']
# False

Questo funziona anche con le tuple, poiché le tuple sono irritabili (come conseguenza del fatto che sono anche immutabili):

(1, 2) in [(3, 4), (1, 2)]
#  True

Se l'oggetto su RHS definisce un __contains__()metodo, inlo chiamerà internamente, come indicato nell'ultimo paragrafo della sezione Confronti dei documenti.

... ine not insono supportati da tipi iterabili o implementano il __contains__()metodo. Ad esempio, potresti (ma non dovresti) fare questo:

[3, 2, 1].__contains__(1)
# True

incortocircuiti, quindi se il tuo elemento è all'inizio dell'elenco, invaluta più velocemente:

lst = list(range(10001))
%timeit 1 in lst
%timeit 10000 in lst  # Expected to take longer time.

68.9 ns ± 0.613 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
178 µs ± 5.01 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Se vuoi fare di più che controllare se un elemento è in un elenco, ci sono opzioni:

  • list.indexpuò essere utilizzato per recuperare l'indice di un elemento. Se quell'elemento non esiste, ValueErrorviene sollevato a.
  • list.count può essere utilizzato se si desidera contare le occorrenze.

Il problema XY: hai considerato sets?

Ponetevi queste domande:

  • devi verificare se un elemento è in un elenco più di una volta?
  • Questo controllo viene eseguito all'interno di un ciclo o una funzione chiamata ripetutamente?
  • Gli elementi che stai memorizzando nel tuo elenco sono cancellabili? IOW, puoi chiamarli hash?

Se hai risposto "sì" a queste domande, dovresti usare setinvece un . Un intest di appartenenza su lists è O (n) complessità temporale. Ciò significa che python deve eseguire una scansione lineare dell'elenco, visitando ogni elemento e confrontandolo con l'elemento di ricerca. Se lo fai ripetutamente o se gli elenchi sono grandi, questa operazione comporterà un sovraccarico.

setgli oggetti, d'altra parte, eseguono l'hashing dei loro valori per un controllo costante dell'appartenenza a tempo. Il controllo viene inoltre eseguito utilizzando in:

1 in {1, 2, 3} 
# True

'a' not in {'a', 'b', 'c'}
# False

(1, 2) in {('a', 'c'), (1, 2)}
# True

Se sei abbastanza sfortunato che l'elemento che stai cercando / non stai cercando sia alla fine della tua lista, Python avrà scannerizzato la lista fino alla fine. Ciò è evidente dai tempi seguenti:

l = list(range(100001))
s = set(l)

%timeit 100000 in l
%timeit 100000 in s

2.58 ms ± 58.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
101 ns ± 9.53 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Come promemoria, questa è un'opzione adatta a condizione che gli elementi che stai memorizzando e guardando siano hash. IOW, dovrebbero essere tipi immutabili o oggetti che implementano __hash__.


2
I set non sono sempre un'opzione (ad esempio, quando si dispone di un elenco di elementi mutabili). Per raccolte di grandi dimensioni: la creazione del set per una ricerca è comunque O (n) volta e può raddoppiare l'utilizzo della memoria. Se non hai già una ricerca in giro, non è sempre la scelta migliore per fare / mantenere uno.
mercoledì
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.