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.