Python znajduje elementy na jednej liście, których nie ma na drugiej [zduplikowane]

136

Muszę porównać dwie listy, aby utworzyć nową listę konkretnych elementów znajdujących się na jednej liście, ale nie na drugiej. Na przykład:

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

Chcę przejrzeć listę_1 i dołączyć do main_list wszystkie elementy z listy_2, których nie ma na liście_1.

Wynik powinien być:

main_list=["f", "m"]

Jak mogę to zrobić w Pythonie?

CosimoCD
źródło
2
Czy szukasz elementów, list_2które nigdzie nie występują w, list_1lub elementów list_2, które nie występują w tym samym indeksie w list_1?
Patrick Haugh,

Odpowiedzi:

98

TL; DR:
ROZWIĄZANIE (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`

ROZWIĄZANIE (2) Chcesz posortowaną listę

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)




Objaśnienia:
(1) Można użyć NumPy użytkownika setdiff1d( array1, array2, assume_unique= False).

assume_uniquepyta użytkownika, JEŻELI tablice SĄ JUŻ UNIKALNE.
Jeśli False, to unikalne elementy są określane jako pierwsze.
Jeśli Truefunkcja przyjmie, że elementy są już unikalne ORAZ funkcja pominie określanie unikalnych elementów.

Daje to unikatowe wartości w array1które nie są w array2. assume_uniquejest Falsedomyślnie.

Jeśli obawiasz się unikalnych elementów (na podstawie odpowiedzi Chinny84 ), po prostu użyj (gdzie assume_unique=False=> wartość domyślna):

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) Dla tych, którzy chcą posortować odpowiedzi, stworzyłem niestandardową funkcję:

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

Aby uzyskać odpowiedź, uruchom:

main_list = setdiff_sorted(list_2,list_1)

UWAGI BOCZNE:
(a) Rozwiązanie 2 (funkcja niestandardowa setdiff_sorted) zwraca listę (w porównaniu z tablicą w rozwiązaniu 1).

(b) Jeśli nie jesteś pewien, czy elementy są unikalne, po prostu użyj domyślnego ustawienia NumPy setdiff1dw obu rozwiązaniach A i B. Co może być przykładem komplikacji? Patrz uwaga (c).

(c) Sytuacja będzie wyglądać inaczej, jeśli żadna z tych dwóch list nie jest unikalna.
Powiedzmy list_2nie jest unikalna: list2 = ["a", "f", "c", "m", "m"]. Keep list1as is: list_1 = ["a", "b", "c", "d", "e"]
Ustawienie domyślnej wartości assume_uniqueuzysku ["f", "m"](w obu rozwiązaniach). JEDNAK, jeśli ustawisz assume_unique=True, oba rozwiązania dają ["f", "m", "m"]. Czemu? Dzieje się tak, ponieważ użytkownik ZAKŁADAŁ, że elementy są unikalne). Dlatego LEPIEJ ZACHOWAĆassume_uniquedo wartości domyślnej. Zwróć uwagę, że obie odpowiedzi są posortowane.

jcoderepo
źródło
Jeśli Twoje listy są już uporządkowane, zwróci to również listę uporządkowaną. Natywne rozwiązanie polegające na konwersji na zestawy, a następnie uzyskaniu różnicy (rozwiązania pokazane poniżej) zwraca nieuporządkowaną listę, co może utrudnić wizualne sprawdzenie wyników.
Doubledown
1
Cześć @Doubledown! Twój problem został rozwiązany w zredagowanym poście. Mam nadzieję że to pomoże!
jcoderepo
183

Możesz użyć zestawów:

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

Wynik:

>>> 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']

Zgodnie z komentarzem @JonClements, oto bardziej uporządkowana wersja:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']
nrlakin
źródło
2
Jest to dobre, jeśli zależy nam tylko na uniqueelementach, ale co, jeśli mamy m'sna przykład wiele, to nie podniesie tego.
Chinny84
To prawda. Założyłem, że plakat szukał wyjątkowych elementów. Przypuszczam, że zależy to od tego, co ma na myśli mówiąc „konkretny”.
nrlakin
Rzeczywiście ps Nie przegłosowałem twojej odpowiedzi, szczególnie w przypadku niejasnego pierwotnego pytania.
Chinny84,
13
Mógłbyś napisać to jako list(set(list_2).difference(list_1))uniknięcie jawnej setkonwersji ...
Jon Clements
Bez obaw! Dzięki @leaf za pomoc w formatowaniu.
nrlakin
61

Nie jestem pewien, dlaczego powyższe wyjaśnienia są tak skomplikowane, gdy masz dostępne metody natywne:

main_list = list(set(list_2)-set(list_1))
A.Kot
źródło
6
Przyczyną może być zachowanie porządku
Keith
57

Użyj takiego rozumienia listy :

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

Wynik:

>>> 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']

Edytować:

Jak wspomniano w komentarzach poniżej, przy dużych listach powyższe nie jest idealnym rozwiązaniem. W takim przypadku lepszą opcją byłaby konwersja list_1na setpierwszą:

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]
ettanany
źródło
3
Uwaga: w przypadku większych list_1, chciałbyś wstępnie przekonwertować na set/ frozenset, np. set_1 = frozenset(list_1)Następnie main_list = [item for item in list_2 if item not in set_1]skrócić czas sprawdzania z O(n)na element do (w przybliżeniu) O(1).
ShadowRanger,
@ettanany Uwaga, jeśli wypróbujesz rozwiązanie, które opublikował ettanany. Wypróbowałem rozwiązanie ettanany w obecnej postaci i jest rzeczywiście bardzo wolne w przypadku większej listy. Czy możesz zaktualizować odpowiedź, aby uwzględnić sugestię shadowrangera?
Doubledown
Czy byłoby możliwe uzyskanie indeksu zamiast ciągu?
JareBear
@JareBear Możesz użyć enumerate()do tego:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany
@ ettanany's dziękuję bardzo !! Wdrożę to jak najszybciej, zrobiłem to. Ale twój kod wygląda o wiele czystszy.
JareBear
5

Jeśli chcesz rozwiązanie jednej liniowej (ignorując importu), który wymaga tylko O(max(n, m))pracę dla wejść długości ni m, a nie O(n * m)pracy, można to zrobić z tym itertoolsmodule :

from itertools import filterfalse

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

Wykorzystuje to funkcje funkcjonalne, które podczas konstrukcji przyjmują funkcję zwrotną, umożliwiając jednokrotne utworzenie wywołania zwrotnego i ponowne użycie go dla każdego elementu bez konieczności przechowywania go gdzieś (ponieważ filterfalseprzechowuje go wewnętrznie); Listy składane i wyrażenia generatora mogą to zrobić, ale jest to brzydkie. †

To daje takie same wyniki w jednym wierszu, jak:

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

z prędkością:

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

Oczywiście, jeśli porównania mają być pozycyjne, to:

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

powinien produkować:

main_list = [2, 3, 4]

(ponieważ wartość w list_2ma dopasowanie w tym samym indeksie w list_1), zdecydowanie powinieneś wybrać odpowiedź Patryka , która nie zawiera tymczasowych lists lub sets (nawet jeśli sets jest z grubsza O(1), mają wyższy współczynnik „stały” na sprawdzenie niż proste sprawdzenia równości ) i wymaga O(min(n, m))pracy, mniej niż jakakolwiek inna odpowiedź, a jeśli twój problem jest wrażliwy na położenie, jest jedynym poprawnym rozwiązaniem, gdy pasujące elementy pojawiają się przy niedopasowanych przesunięciach.

†: Sposobem na zrobienie tego samego z rozumieniem listy jako jednowierszowym byłoby nadużywanie zagnieżdżonych pętli w celu tworzenia i buforowania wartości w „najbardziej zewnętrznej” pętli, np .:

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

co również daje niewielką korzyść w zakresie wydajności w Pythonie 3 (ponieważ teraz set_1jest lokalnie objęty zakresem kodu zrozumienia, zamiast wyszukiwać w zagnieżdżonym zakresie dla każdego sprawdzenia; w Pythonie 2 to nie ma znaczenia, ponieważ Python 2 nie używa domknięć dla listy składane; działają w tym samym zakresie, w którym są używane).

ShadowRanger
źródło
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)

wynik:

['f', 'm']
Inconnu
źródło
Podobnie jak w przypadku równoważnego rozwiązania opartego na zrozumieniu list , będzie to powolne, jeśli list_1jest duże i list_2ma niebanalny rozmiar, ponieważ obejmuje len(list_2) O(n)skanowanie list_1, wykonanie go O(n * m)(gdzie ni msą długościami list_2i list_1odpowiednio). Jeśli przekonwertujesz list_1na set/ z frozensetgóry, sprawdzanie zawartości można wykonać w O(1), co daje całkowitą pracę O(n)na długości list_2(technicznie O(max(n, m)), ponieważ pracujesz O(m)nad wykonaniem set).
ShadowRanger,
1

Chciałbym zestawić ziplisty razem, aby porównać je element po elemencie.

main_list = [b for a, b in zip(list1, list2) if a!= b]
Patrick Haugh
źródło
Jeśli PO chce porównać element po elemencie (nie jest jasne, przykład może przebiegać w obie strony), jest to znacznie bardziej wydajne niż inne odpowiedzi, ponieważ jest to jedno tanie przejście przez oba elementy listz jednym listkonstruowanym nowym , bez dodatkowych tymczasowych , żadnych kosztownych kontroli bezpieczeństwa itp.
ShadowRanger,
1
@ShadowRanger to działałoby tylko w przypadku różnicy elementów, co jest kluczowym punktem
ford prefect
@fordprefect: Tak. Moja własna odpowiedź obejmuje różnice niezależne od pozycji.
ShadowRanger,
1

Użyłem dwóch metod i okazało się, że jedna z nich jest bardziej użyteczna niż druga. Oto moja odpowiedź:

Moje dane wejściowe:

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

Metoda 1: np.setdiff1dPodoba mi się to podejście w porównaniu z innymi, ponieważ zachowuje pozycję

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

Metoda 2: Chociaż daje taką samą odpowiedź jak w metodzie 1, ale zakłóca porządek

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

Metoda 1 np.setdiff1ddoskonale spełnia moje wymagania. Ta odpowiedź dla informacji.

Msquare
źródło
0

Jeśli należy wziąć pod uwagę liczbę wystąpień, prawdopodobnie trzeba użyć czegoś takiego 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']

Zgodnie z obietnicą może to również obsługiwać różną liczbę wystąpień jako „różnicę”:

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']
MSeifert
źródło
-1

Z ser1 usuń elementy obecne w ser2.

Wejście

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

Rozwiązanie

ser1 [~ ser1.isin (ser2)]

adnan
źródło
Witamy w Stack Overflow. To pytanie ma osiem innych odpowiedzi, z których jedna została zaakceptowana przez oryginalny plakat. Opisz, w jaki sposób Twoja odpowiedź poprawia to, co już zostało przedstawione.
chb