Python trova elementi in un elenco che non sono nell'altro [duplicato]


137

Ho bisogno di confrontare due elenchi per creare un nuovo elenco di elementi specifici trovati in un elenco ma non nell'altro. Per esempio:

main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 

Voglio scorrere l'elenco_1 e aggiungere a main_list tutti gli elementi di list_2 che non si trovano in list_1.

Il risultato dovrebbe essere:

main_list=["f", "m"]

Come posso farlo con Python?


2
Stai cercando elementi in list_2che non compaiono da nessuna parte in list_1o elementi in list_2che non sono presenti nello stesso indice in list_1?
Patrick Haugh

Risposte:


98

TL; DR:
SOLUZIONE (1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

SOLUZIONE (2) Si desidera un elenco ordinato

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




SPIEGAZIONI:
(1) È possibile utilizzare NumPy di setdiff1d( array1, array2, assume_unique= False).

assume_uniquechiede all'utente SE gli array SONO GIÀ UNICI.
Se False, quindi gli elementi univoci vengono determinati per primi.
Se True, la funzione presumerà che gli elementi siano già univoci E la funzione salterà la determinazione degli elementi univoci.

Ciò restituisce i valori univoci array1che non sono presenti array2. assume_uniqueè l' Falseimpostazione predefinita.

Se sei interessato agli elementi unici (in base alla risposta di Chinny84 ), usa semplicemente (dove assume_unique=False=> il valore predefinito):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) Per coloro che desiderano ordinare le risposte, ho creato una funzione personalizzata:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

Per ottenere la risposta, esegui:

main_list = setdiff_sorted(list_2,list_1)

NOTE A LATO:
(a) La soluzione 2 (funzione personalizzata setdiff_sorted) restituisce un elenco (rispetto a un array nella soluzione 1).

(b) Se non sei sicuro che gli elementi siano univoci, usa semplicemente l'impostazione predefinita di NumPy setdiff1din entrambe le soluzioni A e B. Quale può essere un esempio di complicazione? Vedi nota (c).

(c) Le cose saranno diverse se uno dei due elenchi non è unico.
Dire list_2non è unico nel suo genere: list2 = ["a", "f", "c", "m", "m"]. Keep list1as is: list_1 = ["a", "b", "c", "d", "e"]
impostazione del valore di default delle assume_uniquerese ["f", "m"](in entrambe le soluzioni). TUTTAVIA, se si imposta assume_unique=True, entrambe le soluzioni danno ["f", "m", "m"]. Perché? Questo perché l'utente HA ASSUNTO che gli elementi siano unici). Quindi, È MEGLIO MANTENEREassume_uniqueal valore predefinito. Nota che entrambe le risposte sono ordinate.


Se i tuoi elenchi sono già ordinati, anche questo restituirà un elenco ordinato. La soluzione nativa di convertire in set e quindi ottenere la differenza (soluzioni mostrate di seguito) restituisce un elenco non ordinato che potrebbe rendere più difficile esaminare visivamente i risultati.
Raddoppio

1
Ciao, @Doubledown! La tua preoccupazione è stata affrontata nel post modificato. Spero che questo ti aiuti!
jcoderepo

183

Puoi usare i set:

main_list = list(set(list_2) - set(list_1))

Produzione:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

Secondo il commento di @JonClements, ecco una versione più ordinata:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']

2
Questo è positivo se ci preoccupiamo solo degli uniqueelementi, ma se avessimo più elementi, m'sad esempio, questo non lo prenderebbe.
Chinny84

È vero. Ho pensato che il poster stesse cercando elementi unici. Suppongo che dipenda da cosa intende per "specifico".
nrlakin

In effetti ps non ho votato giù la tua risposta, soprattutto per una domanda originale poco chiara.
Chinny84

13
Potresti scrivere questo per list(set(list_2).difference(list_1))evitare la setconversione esplicita ...
Jon Clements

Nessun problema! Grazie @leaf per l'assistenza alla formattazione.
nrlakin

61

Non sono sicuro del motivo per cui le spiegazioni di cui sopra sono così complicate quando hai a disposizione metodi nativi:

main_list = list(set(list_2)-set(list_1))

6
Preservare l'ordine potrebbe essere la ragione
Keith

57

Usa una comprensione dell'elenco come questa:

main_list = [item for item in list_2 if item not in list_1]

Produzione:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

Modificare:

Come accennato nei commenti qui sotto, con elenchi di grandi dimensioni, quanto sopra non è la soluzione ideale. In questo caso, un'opzione migliore sarebbe la conversione list_1in un setprimo:

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]

3
Nota: per i più grandi list_1, che ci si vuole preconvert a un set/ frozenset, ad esempio set_1 = frozenset(list_1), quindi main_list = [item for item in list_2 if item not in set_1], riducendo il tempo di spunta da O(n)ogni elemento (o meno) O(1).
ShadowRanger

@ettanany Per favore, fai attenzione se provi la soluzione pubblicata da ettanany. Ho provato la soluzione di ettanany così com'è ed è davvero super lenta per un elenco più ampio. Puoi aggiornare la risposta per incorporare il suggerimento di Shadowranger?
Raddoppio

Sarebbe possibile ottenere l'indice invece della stringa?
JareBear

@JareBear Puoi usare enumerate()per questo:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany

@ ettanany ti ringrazio molto !! Lo implementerò al più presto, l'avevo fatto. Ma il tuo codice sembra molto più pulito.
JareBear

5

Se vuoi una soluzione di una riga (ignorando le importazioni) che richiede solo O(max(n, m))lavoro per input di lunghezza ne m, non O(n * m)funziona, puoi farlo con il itertoolsmodulo :

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

Questo sfrutta le funzioni funzionali che assumono una funzione di callback in fase di costruzione, permettendole di creare il callback una volta e riutilizzarlo per ogni elemento senza bisogno di memorizzarlo da qualche parte (perché lo filterfalsememorizza internamente); le comprensioni delle liste e le espressioni del generatore possono farlo, ma è brutto. †

Ottiene gli stessi risultati in una singola riga di:

main_list = [x for x in list_2 if x not in list_1]

con la velocità di:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

Ovviamente, se i confronti sono da considerarsi posizionali, quindi:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

dovrebbe produrre:

main_list = [2, 3, 4]

(poiché il valore in list_2ha una corrispondenza allo stesso indice in list_1), dovresti assolutamente seguire la risposta di Patrick , che non implica lists o sets temporanei (anche se sets è approssimativamente O(1), hanno un fattore "costante" più alto per controllo rispetto ai controlli di uguaglianza semplici ) e implica un O(min(n, m))lavoro, meno di qualsiasi altra risposta, e se il tuo problema è sensibile alla posizione, è l'unica soluzione corretta quando gli elementi corrispondenti appaiono con offset non corrispondenti.

†: Il modo per fare la stessa cosa con una comprensione di lista come una riga singola sarebbe abusare del ciclo annidato per creare e memorizzare nella cache i valori nel ciclo "più esterno", ad esempio:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

che offre anche un minor vantaggio in termini di prestazioni su Python 3 (perché ora set_1è localizzato localmente nel codice di comprensione, piuttosto che cercare dall'ambito annidato per ogni controllo; su Python 2 non importa, perché Python 2 non usa le chiusure per elencare le comprensioni; operano nello stesso ambito in cui sono utilizzate).


4
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

produzione:

['f', 'm']

Come la soluzione basata sulla comprensione della lista equivalente , questa sarà lenta se list_1è grande e list_2di dimensioni non banali, perché coinvolge len(list_2) O(n)scansioni di list_1, rendendolo O(n * m)(dove ne msono le lunghezze di list_2e list_1rispettivamente). Se si converte list_1in a set/ frozensetup front, i controlli di contiene possono essere eseguiti in O(1), facendo il lavoro totale O(n)sulla lunghezza di list_2(tecnicamente O(max(n, m)), dal momento che O(m)lavori per fare il set).
ShadowRanger

1

Vorrei ziple liste insieme per confrontarle elemento per elemento.

main_list = [b for a, b in zip(list1, list2) if a!= b]

Se l'OP vuole confrontare elemento per elemento (non è chiaro, l'esempio potrebbe andare in entrambi i modi), questo è molto più efficiente delle altre risposte, poiché è un singolo passaggio economico su entrambi i lists con un singolo nuovo listin costruzione, senza provvisori aggiuntivi , nessun costoso controllo di contenimento, ecc.
ShadowRanger

1
@ShadowRanger questo funzionerebbe solo per la differenza in termini di elementi che è un punto chiave
prefetto ford

@fordprefect: Sì. La mia risposta copre le differenze indipendenti dalla posizione.
ShadowRanger

1

Ho usato due metodi e ho trovato un metodo utile rispetto all'altro. Ecco la mia risposta:

I miei dati di input:

crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

Metodo1: np.setdiff1dmi piace questo approccio rispetto ad altri perché preserva la posizione

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

Metodo2: sebbene dia la stessa risposta del Metodo1 ma disturba l'ordine

test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

Il metodo1 np.setdiff1dsoddisfa perfettamente le mie esigenze. Questa risposta per informazione.


0

Se il numero di occorrenze deve essere preso in considerazione, probabilmente è necessario utilizzare qualcosa come collections.Counter:

list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['f', 'm']

Come promesso, questo può anche gestire un numero diverso di occorrenze come "differenza":

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['a', 'f', 'm']

-1

Da ser1 rimuovere gli elementi presenti in ser2.

Ingresso

ser1 = pd.Series ([1, 2, 3, 4, 5]) ser2 = pd.Series ([4, 5, 6, 7, 8])

Soluzione

SER1 [~ ser1.isin (SER2)]


Benvenuto in Stack Overflow. Questa domanda ha altre otto risposte, una delle quali è stata accettata dal poster originale. Descrivi come la tua risposta migliora rispetto a ciò che è già stato presentato.
chb
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.