Il modo più veloce per verificare se esiste un valore in un elenco


818

Qual è il modo più veloce per sapere se esiste un valore in un elenco (un elenco con milioni di valori in esso) e qual è il suo indice?

So che tutti i valori nell'elenco sono unici come in questo esempio.

Il primo metodo che provo è (3,8 secondi nel mio codice reale):

a = [4,2,3,1,5,6]

if a.count(7) == 1:
    b=a.index(7)
    "Do something with variable b"

Il secondo metodo che provo è (2 volte più veloce: 1,9 secondi per il mio codice reale):

a = [4,2,3,1,5,6]

try:
    b=a.index(7)
except ValueError:
    "Do nothing"
else:
    "Do something with variable b"

Metodi proposti dall'utente Stack Overflow (2.74 sec per il mio codice reale):

a = [4,2,3,1,5,6]
if 7 in a:
    a.index(7)

Nel mio codice reale, il primo metodo richiede 3,81 secondi e il secondo metodo richiede 1,88 secondi. È un buon miglioramento, ma:

Sono un principiante con Python / scripting e c'è un modo più veloce per fare le stesse cose e risparmiare più tempo di elaborazione?

Spiegazione più specifica per la mia applicazione:

Nell'API di Blender posso accedere a un elenco di particelle:

particles = [1, 2, 3, 4, etc.]

Da lì, posso accedere alla posizione di una particella:

particles[x].location = [x,y,z]

E per ogni particella collaudo se esiste un vicino cercando ogni posizione delle particelle in questo modo:

if [x+1,y,z] in particles.location
    "Find the identity of this neighbour particle in x:the particle's index
    in the array"
    particles.index([x+1,y,z])

5
In Python la cosa tra parentesi quadre è chiamata lista, non matrice. Anziché utilizzare un elenco, utilizzare un set. Oppure mantieni la tua lista ordinata e usa il bisectmodulo
Steven Rumbalski,

Quindi hai davvero bisogno di destreggiarti tra gli indici? O l'ordine non ha importanza e vuoi semplicemente fare prove di nave membro, incroci, ecc.? In parole povere, dipende da cosa stai davvero cercando di fare. I set possono funzionare per te, quindi sono un'ottima risposta, ma non possiamo dirlo dal codice che hai mostrato.

2
Probabilmente devi specificare nella tua domanda che non è necessario il valore, ma il suo indice.
Roman Bodnarchuk,

Modifico la mia domanda e provo a spiegare più chiaramente cosa voglio fare ... lo spero ...
Jean-Francois Gallant,

1
@StevenRumbalski: poiché set non può contenere contenuti di duplicazione, mentre Jean vuole memorizzare la posizione delle particelle (x, y, z potrebbe essere la stessa), in questo caso non possiamo usare set
Hieu Vo

Risposte:


1574
7 in a

Il modo più chiaro e veloce per farlo.

Puoi anche prendere in considerazione l'utilizzo di a set, ma la costruzione di quel set dal tuo elenco potrebbe richiedere più tempo di quanto risparmierà un test di appartenenza più veloce. L'unico modo per essere certi è fare un buon benchmark. (questo dipende anche da quali operazioni sono necessarie)


5
Ma non hai l'indice e ottenerlo ti costerà ciò che hai salvato.
rodrigo,

6
come: Se 7 in a: b = a.index (7)?
Jean-Francois Gallant,

26
@StevenRumbalski: i set sono un'opzione solo se non è necessario che siano ordinati (e quindi hanno un indice). E gli insiemi sono chiaramente indicati nella risposta, ma dà anche una risposta semplice alla domanda quando l'OP l'ha posta. Non penso che questo valga -1.

Modifico la mia domanda e provo a spiegare più chiaramente cosa voglio fare ... lo spero ...
Jean-Francois Gallant

1
Ok, provo il tuo metodo nel mio codice reale e probabilmente ci vorrà un po 'più di tempo perché devo conoscere l'indice del valore. Con il mio secondo metodo, controllo se esiste e ottengo l'indice allo stesso tempo.
Jean-Francois Gallant,

213

Come affermato da altri, inpuò essere molto lento per elenchi di grandi dimensioni. Ecco alcuni confronti delle esibizioni per in, sete bisect. Si noti che il tempo (in secondi) è in scala logaritmica.

inserisci qui la descrizione dell'immagine

Codice per il test:

import random
import bisect
import matplotlib.pyplot as plt
import math
import time

def method_in(a,b,c):
    start_time = time.time()
    for i,x in enumerate(a):
        if x in b:
            c[i] = 1
    return(time.time()-start_time)   

def method_set_in(a,b,c):
    start_time = time.time()
    s = set(b)
    for i,x in enumerate(a):
        if x in s:
            c[i] = 1
    return(time.time()-start_time)

def method_bisect(a,b,c):
    start_time = time.time()
    b.sort()
    for i,x in enumerate(a):
        index = bisect.bisect_left(b,x)
        if index < len(a):
            if x == b[index]:
                c[i] = 1
    return(time.time()-start_time)

def profile():
    time_method_in = []
    time_method_set_in = []
    time_method_bisect = []

    Nls = [x for x in range(1000,20000,1000)]
    for N in Nls:
        a = [x for x in range(0,N)]
        random.shuffle(a)
        b = [x for x in range(0,N)]
        random.shuffle(b)
        c = [0 for x in range(0,N)]

        time_method_in.append(math.log(method_in(a,b,c)))
        time_method_set_in.append(math.log(method_set_in(a,b,c)))
        time_method_bisect.append(math.log(method_bisect(a,b,c)))

    plt.plot(Nls,time_method_in,marker='o',color='r',linestyle='-',label='in')
    plt.plot(Nls,time_method_set_in,marker='o',color='b',linestyle='-',label='set')
    plt.plot(Nls,time_method_bisect,marker='o',color='g',linestyle='-',label='bisect')
    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc = 'upper left')
    plt.show()

15
Adoro il codice eseguibile "taglia e incolla" come questo nelle risposte. Per risparmiare altri pochi secondi, avrai bisogno di 3 importazioni: import random / import bisect / import matplotlib.pyplot as plte poi chiama:profile()
kghastie

1
quale versione di Python è questa?
cowbert,

sempre ottimo per ottenere il codice, ma solo a testa alta ho dovuto importare tempo per l'esecuzione
whla

E non dimenticare l' range()oggetto umile . Durante l'utilizzo var in [integer list], vedere se un range()oggetto può modellare la stessa sequenza. Molto vicino nelle prestazioni a un set, ma più conciso.
Martijn Pieters

37

Puoi mettere i tuoi oggetti in a set. Le ricerche impostate sono molto efficienti.

Provare:

s = set(a)
if 7 in s:
  # do stuff

modifica In un commento dici che desideri ottenere l'indice dell'elemento. Sfortunatamente, i set non hanno idea della posizione dell'elemento. Un'alternativa è preordinare l'elenco e quindi utilizzare la ricerca binaria ogni volta che è necessario trovare un elemento.


E se dopo voglio conoscere l'indice di questo valore, è possibile e hai un modo rapido per farlo?
Jean-Francois Gallant,

@ Jean-FrancoisGallant: in questo caso i set non saranno di grande utilità. È possibile preordinare l'elenco e quindi utilizzare la ricerca binaria. Si prega di vedere la mia risposta aggiornata.
NPE,

Modifico la mia domanda e provo a spiegare più chiaramente cosa voglio fare ... lo spero ...
Jean-Francois Gallant,

30
def check_availability(element, collection: iter):
    return element in collection

uso

check_availability('a', [1,2,3,4,'a','b','c'])

Credo che questo sia il modo più veloce per sapere se un valore scelto è in un array.


71
return 'a' in a?
Shikiryu,

4
Devi inserire il codice in una definizione: def listValue (): a = [1,2,3,4, 'a', 'b', 'c'] return 'a' in ax = listValue () print ( x)
Tenzin,

12
È una risposta Python valida, semplicemente non è un codice leggibile.
Rick Henderson,

1
Attenzione! Ciò corrisponde mentre questo è molto probabilmente quello che non ti aspettavi:o='--skip'; o in ("--skip-ias"); # returns True !
Alex F

3
@Alex F l' inoperatore lavora allo stesso modo per testare l'appartenenza alla sottostringa. La parte confusa qui è probabilmente che ("hello")non è una tupla a valore singolo, mentre lo ("hello",)è - la virgola fa la differenza. o in ("--skip-ias",)è Falsecome previsto.
MoxieBall,

17
a = [4,2,3,1,5,6]

index = dict((y,x) for x,y in enumerate(a))
try:
   a_index = index[7]
except KeyError:
   print "Not found"
else:
   print "found"

Questa sarà una buona idea se a non cambia e quindi possiamo fare la parte dict () una volta e poi usarla ripetutamente. Se una modifica, fornisci maggiori dettagli su ciò che stai facendo.


Funziona ma non quando implementato nel mio codice: "TypeError: unhashable type: 'list'
Jean-Francois Gallant,

1
@ Jean-FrancoisGallant, probabilmente perché stai usando elenchi in cui dovresti davvero usare le tuple. Se desideri una consulenza completa su come velocizzare il tuo codice, dovresti pubblicarlo su codereview.stackexchange.com. Lì riceverai consigli di stile e prestazioni.
Winston Ewert,

1
Questa è una soluzione molto intelligente al problema. Invece di provare tranne il costrutto, farei: a_index = index.get (7) che per impostazione predefinita sarà None se la chiave non viene trovata.
murphsp1,

14

La domanda originale era:

Qual è il modo più veloce per sapere se esiste un valore in un elenco (un elenco con milioni di valori in esso) e qual è il suo indice?

Quindi ci sono due cose da trovare:

  1. è un elemento nell'elenco e
  2. qual è l'indice (se nell'elenco).

A tal fine, ho modificato il codice @xslittlegrass per calcolare gli indici in tutti i casi e ho aggiunto un metodo aggiuntivo.

risultati

inserisci qui la descrizione dell'immagine

I metodi sono:

  1. in - sostanzialmente se x in b: restituisce b.index (x)
  2. try - try / catch on b.index (x) (salta la necessità di verificare se x in b)
  3. set - sostanzialmente se x in set (b): return b.index (x)
  4. bisect - ordina b con il suo indice, ricerca binaria per x in ordinato (b). Nota la mod di @xslittlegrass che restituisce l'indice nella b ordinata, anziché nella b originale
  5. reverse - forma un dizionario di ricerca inversa d per b; quindi d [x] fornisce l'indice di x.

I risultati mostrano che il metodo 5 è il più veloce.

È interessante notare che i metodi try e set sono equivalenti nel tempo.


Codice test

import random
import bisect
import matplotlib.pyplot as plt
import math
import timeit
import itertools

def wrapper(func, *args, **kwargs):
    " Use to produced 0 argument function for call it"
    # Reference https://www.pythoncentral.io/time-a-python-function/
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

def method_in(a,b,c):
    for i,x in enumerate(a):
        if x in b:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_try(a,b,c):
    for i, x in enumerate(a):
        try:
            c[i] = b.index(x)
        except ValueError:
            c[i] = -1

def method_set_in(a,b,c):
    s = set(b)
    for i,x in enumerate(a):
        if x in s:
            c[i] = b.index(x)
        else:
            c[i] = -1
    return c

def method_bisect(a,b,c):
    " Finds indexes using bisection "

    # Create a sorted b with its index
    bsorted = sorted([(x, i) for i, x in enumerate(b)], key = lambda t: t[0])

    for i,x in enumerate(a):
        index = bisect.bisect_left(bsorted,(x, ))
        c[i] = -1
        if index < len(a):
            if x == bsorted[index][0]:
                c[i] = bsorted[index][1]  # index in the b array

    return c

def method_reverse_lookup(a, b, c):
    reverse_lookup = {x:i for i, x in enumerate(b)}
    for i, x in enumerate(a):
        c[i] = reverse_lookup.get(x, -1)
    return c

def profile():
    Nls = [x for x in range(1000,20000,1000)]
    number_iterations = 10
    methods = [method_in, method_try, method_set_in, method_bisect, method_reverse_lookup]
    time_methods = [[] for _ in range(len(methods))]

    for N in Nls:
        a = [x for x in range(0,N)]
        random.shuffle(a)
        b = [x for x in range(0,N)]
        random.shuffle(b)
        c = [0 for x in range(0,N)]

        for i, func in enumerate(methods):
            wrapped = wrapper(func, a, b, c)
            time_methods[i].append(math.log(timeit.timeit(wrapped, number=number_iterations)))

    markers = itertools.cycle(('o', '+', '.', '>', '2'))
    colors = itertools.cycle(('r', 'b', 'g', 'y', 'c'))
    labels = itertools.cycle(('in', 'try', 'set', 'bisect', 'reverse'))

    for i in range(len(time_methods)):
        plt.plot(Nls,time_methods[i],marker = next(markers),color=next(colors),linestyle='-',label=next(labels))

    plt.xlabel('list size', fontsize=18)
    plt.ylabel('log(time)', fontsize=18)
    plt.legend(loc = 'upper left')
    plt.show()

profile()

Errore di battitura nella descrizione ("reverse loop up" dovrebbe essere "reverse lookup", no?)
Cam U

@ CamU - sì, corretto. Grazie per averlo notato.
DarrylG,

7

Sembra che l'applicazione potrebbe trarre vantaggio dall'uso di una struttura di dati di Bloom Filter.

In breve, una ricerca del filtro bloom può dirti molto rapidamente se un valore NON È DEFINITAMENTE presente in un set. Altrimenti, è possibile effettuare una ricerca più lenta per ottenere l'indice di un valore POSSIBILMENTE POTREBBE ESSERE nell'elenco. Quindi, se la tua applicazione tende a ottenere il risultato "non trovato" molto più spesso del risultato "trovato", potresti aggiungere una velocità aggiungendo un filtro Bloom.

Per i dettagli, Wikipedia fornisce una buona panoramica del funzionamento dei filtri Bloom e una ricerca Web per "libreria di filtri bloom python" fornirà almeno un paio di implementazioni utili.


7

Tenere presente che l' inoperatore verifica non solo l'uguaglianza ( ==) ma anche l'identità ( is), la inlogica per lists è approssimativamente equivalente alla seguente (in realtà è scritta in C e non in Python, almeno in CPython):

for element in s:
    if element is target:
        # fast check for identity implies equality
        return True
    if element == target:
        # slower check for actual equality
        return True
return False

Nella maggior parte dei casi questo dettaglio è irrilevante, ma in alcune circostanze potrebbe lasciare sorpreso un principiante di Python, ad esempio, numpy.NANha la proprietà insolita di non essere uguale a se stesso :

>>> import numpy
>>> numpy.NAN == numpy.NAN
False
>>> numpy.NAN is numpy.NAN
True
>>> numpy.NAN in [numpy.NAN]
True

Per distinguere tra questi casi insoliti potresti usare any()come:

>>> lst = [numpy.NAN, 1 , 2]
>>> any(element == numpy.NAN for element in lst)
False
>>> any(element is numpy.NAN for element in lst)
True 

Nota che la inlogica di lists con any()sarebbe:

any(element is target or element == target for element in lst)

Tuttavia, dovrei sottolineare che questo è un caso limite e, nella stragrande maggioranza dei casi, l' inoperatore è altamente ottimizzato ed è esattamente ciò che si desidera ovviamente (con una listo con una set).


NAN == NAN che restituisce false non ha nulla di insolito al riguardo. È il comportamento definito nello standard IEEE 754.
TommyD,

2

Oppure usa __contains__:

sequence.__contains__(value)

demo:

>>> l=[1,2,3]
>>> l.__contains__(3)
True
>>> 

2

La soluzione di @Winston Ewert produce una grande accelerazione per elenchi molto grandi, ma questa risposta dello stackoverflow indica che il costrutto try: / tranne: / else: verrà rallentato se il ramo di eccezione viene spesso raggiunto. Un'alternativa è quella di sfruttare il .get()metodo per il dict:

a = [4,2,3,1,5,6]

index = dict((y, x) for x, y in enumerate(a))

b = index.get(7, None)
if b is not None:
    "Do something with variable b"

Il .get(key, default)metodo è solo per il caso in cui non puoi garantire che una chiave sia nel dict. Se la chiave è presente, restituisce il valore (come farebbe dict[key]), ma quando non lo è, .get()restituisce il valore predefinito (qui None). In questo caso devi assicurarti che il valore predefinito scelto non sia presente a.


1

Questo non è il codice, ma l'algoritmo per la ricerca molto veloce.

Se la tua lista e il valore che stai cercando sono tutti numeri, questo è piuttosto semplice. Se stringhe: guarda in basso:

  • - Lascia che "n" sia la lunghezza del tuo elenco
  • -Passo facoltativo: se è necessario l'indice dell'elemento: aggiungere una seconda colonna all'elenco con l'indice degli elementi corrente (da 0 a n-1) - vedere più avanti
  • Ordina il tuo elenco o una copia di esso (.sort ())
  • Passa attraverso:
    • Confronta il tuo numero con il n / 2 ° elemento dell'elenco
      • Se più grande, ripetere il ciclo tra gli indici n / 2-n
      • Se più piccolo, ripetere il ciclo tra gli indici 0-n / 2
      • Se lo stesso: l'hai trovato
  • Continua a restringere l'elenco fino a quando non l'hai trovato o hai solo 2 numeri (sotto e sopra quello che stai cercando)
  • Questo troverà qualsiasi elemento in al massimo 19 passaggi per un elenco di 1.000.000 (log (2) n per essere precisi)

Se hai bisogno anche della posizione originale del tuo numero, cercalo nella seconda colonna dell'indice.

Se l'elenco non è composto da numeri, il metodo funziona ancora e sarà più veloce, ma potrebbe essere necessario definire una funzione che può confrontare / ordinare stringhe.

Ovviamente, questo richiede l'investimento del metodo sort (), ma se continui a riutilizzare lo stesso elenco per il controllo, può valerne la pena.


26
Hai dimenticato di menzionare che l'algoritmo che hai spiegato è una semplice ricerca binaria.
diugalde,

0

Poiché la domanda non deve sempre essere intesa come il modo tecnico più veloce - suggerisco sempre il modo più semplice per capire / scrivere: una comprensione dell'elenco, una riga

[i for i in list_from_which_to_search if i in list_to_search_in]

Ho avuto un list_to_search_incon tutti gli articoli e volevo restituire gli indici degli articoli nel filelist_from_which_to_search .

Questo restituisce gli indici in una bella lista.

Esistono altri modi per verificare questo problema - tuttavia le comprensioni dell'elenco sono abbastanza rapide, aggiungendo al fatto di scriverlo abbastanza velocemente, per risolvere un problema.


-2

Per me sono stati 0,030 secondi (reali), 0,026 secondi (utente) e 0,004 secondi (sys).

try:
print("Started")
x = ["a", "b", "c", "d", "e", "f"]

i = 0

while i < len(x):
    i += 1
    if x[i] == "e":
        print("Found")
except IndexError:
    pass

-2

Codice per verificare se esistono due elementi nell'array il cui prodotto è uguale a k:

n = len(arr1)
for i in arr1:
    if k%i==0:
        print(i)
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.