Rimuovere tutti gli elementi presenti in un elenco da un altro


367

Diciamo che ho due liste l1e l2. Voglio esibirmi l1 - l2, che restituisce tutti gli elementi di l1non in l2.

Mi viene in mente un approccio a ciclo ingenuo per farlo, ma sarà davvero inefficiente. Qual è un modo pitonico ed efficace per farlo?

Ad esempio, se ho l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2dovrebbe tornare[1,6]


12
Solo un consiglio: PEP8 afferma che la "L" minuscola non dovrebbe essere usata perché sembra troppo simile a un 1.
spelchekr

2
Sono d'accordo. Ho letto l'intera domanda e le risposte chiedendomi perché le persone continuassero a usare undici e dodici. È stato solo quando ho letto il commento di @spelchekr che aveva senso.
robline,


@JimG. Dataframe ed elenco non sono la stessa cosa.
riduzione dell'attività

Risposte:


492

Python ha una funzione linguistica chiamata Comprensione delle liste che si adatta perfettamente a rendere questo genere di cose estremamente semplice. La seguente dichiarazione fa esattamente quello che vuoi e memorizza il risultato in l3:

l3 = [x for x in l1 if x not in l2]

l3conterrà [1, 6].


8
Molto pitonico; Mi piace! Quanto è efficiente?
fandom

2
Credo abbastanza efficace e ha il vantaggio di essere estremamente leggibile e chiaro su ciò che stai cercando di realizzare. Mi sono imbattuto in un post sul blog che potresti trovare interessante in relazione all'efficienza: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Ciambella

6
@fandom: la stessa comprensione dell'elenco è abbastanza efficiente (sebbene una comprensione del generatore possa essere più efficiente non duplicando gli elementi in memoria), ma l' inoperatore non è così efficiente in un elenco. inin un elenco è O (n), mentre inin un set è O (1). Tuttavia, fino a quando non si arriva a migliaia di elementi o più, è improbabile che si noti la differenza.
Daniel Pryden,

1
l3 = [x for x in l1 if x not in set(l2)]? Sono sicuro che set(l2)sarebbe chiamato più di una volta.
Danosaure,

5
Potresti anche solo impostare l2s = set(l2)e poi dire l3 = [x for x in l1 if x not in l2s]. Leggermente più facile
spelchekr,

149

Un modo è usare i set:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])

58
Questo rimuoverà anche i duplicati da l1, che potrebbe essere un effetto collaterale indesiderato.
kindall

37
..e perde l'ordine degli elementi (se l'ordine è importante).
Danosaure,

3
Voglio solo aggiungere che ho cronometrato questo contro la risposta accettata ed è stato più performante di un fattore di circa 3: timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Quindi, se le prestazioni sono un fattore significativo, questa risposta potrebbe essere più appropriata (e anche se non ti interessano i duplicati o l'ordine)
wfgeo,

38

In alternativa, puoi anche usare filterl'espressione lambda per ottenere il risultato desiderato. Per esempio:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Confronto delle prestazioni

Qui sto confrontando le prestazioni di tutte le risposte menzionate qui. Come previsto, l' set operazione basata su Arkku è la più veloce.

PS: set non mantiene l'ordine e rimuove gli elementi duplicati dall'elenco. Quindi, non usare la differenza impostata se ne hai bisogno.


32

Espandendo la risposta di Donut e le altre risposte qui, puoi ottenere risultati ancora migliori utilizzando una comprensione del generatore anziché una comprensione dell'elenco e utilizzando una setstruttura di dati (poiché l' inoperatore è O (n) in un elenco ma O (1) su un set).

Quindi, ecco una funzione che funzionerebbe per te:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

Il risultato sarà un iterabile che recupererà pigramente l'elenco filtrato. Se hai bisogno di un vero oggetto elenco (ad esempio se devi fare un len()risultato), puoi facilmente creare un elenco in questo modo:

filtered_list = list(filter_list(full_list, excludes))

29

Usa il tipo di set Python. Sarebbe il più Pythonic. :)

Inoltre, poiché è nativo, dovrebbe essere anche il metodo più ottimizzato.

Vedere:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (per i vecchi python)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2

5
Quando si utilizzano i set, è necessario notare che l'output di è ordinato, ovvero {1,3,2} diventa {1,2,3} e {"A", "C", "B"} diventa {"A", "B", "C"} e potresti non volerlo avere.
Pablo Reyes

2
questo metodo non funzionerà se l'elenco l1include elementi ripetuti.
jdhao,

10

usa Imposta comprensioni {x per x in l2} o imposta (l2) per ottenere l'impostazione, quindi usa Elenco comprensioni per ottenere l'elenco

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

codice test benchmark:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

risultato del test benchmark:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    

1
l2set = set( l2 )invece dil2set = { x for x in l2 }
cz

1
Bella soultion! Ma bisogna tenere presente che funziona solo con oggetti cancellabili.
Eerik Sven Puudist,

7

Soluzione alternativa:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])

2
C'è qualche vantaggio nell'utilizzare questo metodo? Sembra che sia più complesso e più difficile da leggere senza molti benefici.
skrrgwasme,

Potrebbe sembrare complesso. Ridurre è molto flessibile e può essere utilizzato per molti scopi. È noto come piega. ridurre è in realtà pieghevole. Supponiamo che tu voglia aggiungere cose più complesse in esso, quindi sarà possibile in questa funzione, ma la comprensione dell'elenco che è la risposta migliore selezionata ti darà solo un output dello stesso tipo, cioè elenco e probabilmente della stessa lunghezza mentre con le pieghe potresti cambia anche il tipo di output. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Questa soluzione è n * m o meno complessa. Altri potrebbero o meno essere meglio però.
Akshay Hazari,

1
ridurre (funzione, elenco, accumulatore iniziale (che può essere di qualsiasi tipo))
Akshay Hazari
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.