Entfernen Sie alle Elemente, die in einer Liste vorkommen, aus einer anderen

365

Nehmen wir an, ich habe zwei Listen l1und l2. Ich möchte durchführen l1 - l2, was alle Elemente von l1nicht in zurückgibt l2.

Ich kann mir einen naiven Loop-Ansatz vorstellen, aber das wird wirklich ineffizient sein. Was ist eine pythonische und effiziente Methode, um dies zu tun?

Als Beispiel, wenn ich habe l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2sollte zurückkehren[1,6]

Fangemeinde
quelle
12
Nur ein Tipp: PEP8 besagt, dass Kleinbuchstaben "L" nicht verwendet werden sollten, da es zu sehr wie ein 1.
spelchekr
2
Genau. Ich las diese ganze Frage und die Antworten und fragte mich, warum die Leute immer wieder elf und zwölf benutzten. Erst als ich @spelchekrs Kommentar las, machte es Sinn.
Robline
1
Mögliches Duplikat des Löschens
Jim G.
@ JimG. Datenrahmen und Liste sind nicht dasselbe.
Reduzierung der Aktivität

Antworten:

491

Python verfügt über eine Sprachfunktion namens List Comprehensions , die sich perfekt dafür eignet, solche Dinge extrem einfach zu machen. Die folgende Anweisung macht genau das, was Sie wollen, und speichert das Ergebnis in l3:

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

l3wird enthalten [1, 6].

Krapfen
quelle
8
Sehr pythonisch; Ich mag das! Wie effizient ist es?
Fandom
2
Ich glaube sehr effizient, und es hat den Vorteil, dass es äußerst lesbar und klar ist, was Sie erreichen möchten. Ich bin auf einen Blog-Beitrag gestoßen, den
Donut
6
@fandom: Das Listenverständnis selbst ist sehr effizient (obwohl ein Generatorverständnis möglicherweise effizienter ist, wenn keine Elemente im Speicher dupliziert werden), aber der inOperator ist in einer Liste nicht so effizient. inauf einer Liste ist O (n), während inauf einer Menge O (1) ist. Bis Sie jedoch zu Tausenden von Elementen oder mehr gelangen, werden Sie den Unterschied wahrscheinlich nicht bemerken.
Daniel Pryden
1
l3 = [x for x in l1 if x not in set(l2)]? Ich bin sicher, wenn set(l2)ich mehr als einmal angerufen würde.
Danosaure
5
Man könnte auch einfach einstellen l2s = set(l2)und dann sagen l3 = [x for x in l1 if x not in l2s]. Etwas einfacher.
Spelchekr
149

Eine Möglichkeit besteht darin, Sets zu verwenden:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
Arkku
quelle
58
Dadurch werden auch Duplikate entfernt l1, was eine unerwünschte Nebenwirkung sein kann.
Kindall
37
..und Elementreihenfolge verlieren (wenn Reihenfolge wichtig ist).
Danosaure
3
Ich möchte nur hinzufügen, dass ich dies im Vergleich zur akzeptierten Antwort zeitlich festgelegt habe und es um den Faktor 3 performanter war : 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. Wenn also die Leistung ein wesentlicher Faktor ist, ist diese Antwort möglicherweise angemessener (und auch, wenn Sie sich nicht für Duplikate oder Bestellungen
interessieren
37

Als Alternative können Sie auch verwenden , filtermit dem Lambda - Ausdruck um das gewünschte Ergebnis zu erhalten. Zum Beispiel:

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

Leistungsvergleich

Hier vergleiche ich die Leistung aller hier genannten Antworten. Wie erwartet ist der set Betrieb von Arkku am schnellsten.

  • Arkkus eingestellter Unterschied - Zuerst (0,124 usec pro Schleife)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
    
  • Daniel Prydens Listenverständnis mit setNachschlagen - Zweiter (0,302 usec pro Schleife)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
    
  • Donuts Listenverständnis auf einfacher Liste - Dritter (0,552 usec pro Schleife)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
    
  • Moinuddin Quadri verwendetfilter - Viertens (0,972 usec pro Schleife)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
    
  • Akshay Hazari verwendet eine Kombination von reduce+filter - Fünftel (3,97 usec pro Schleife)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop
    

PS: set behält die Reihenfolge nicht bei und entfernt die doppelten Elemente aus der Liste. Verwenden Sie daher keine eingestellte Differenz, wenn Sie eine dieser benötigen.

Moinuddin Quadri
quelle
32

Wenn Sie die Antwort von Donut und die anderen Antworten hier erweitern, können Sie noch bessere Ergebnisse erzielen, indem Sie ein Generatorverständnis anstelle eines Listenverständnisses und eine setDatenstruktur verwenden (da der inOperator O (n) in einer Liste ist, aber O (1). am Set).

Hier ist eine Funktion, die für Sie funktionieren würde:

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

Das Ergebnis ist eine Iterierbarkeit, die die gefilterte Liste träge abruft. Wenn Sie ein echtes Listenobjekt benötigen (z. B. wenn Sie len()das Ergebnis bearbeiten müssen ), können Sie ganz einfach eine Liste wie folgt erstellen:

filtered_list = list(filter_list(full_list, excludes))
Daniel Pryden
quelle
29

Verwenden Sie den Python-Set-Typ. Das wäre das Pythonischste. :) :)

Da es nativ ist, sollte es auch die am besten optimierte Methode sein.

Sehen:

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

http://docs.python.org/library/sets.htm (für ältere 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
nonot1
quelle
5
Bei der Verwendung von Mengen sollte beachtet werden, dass die Ausgabe von geordnet ist, dh {1,3,2} wird {1,2,3} und {"A", "C", "B"} wird {"A", "B", "C"} und das möchten Sie vielleicht nicht.
Pablo Reyes
2
Diese Methode funktioniert nicht, wenn die Liste l1wiederholte Elemente enthält.
Jdhao
10

Verwenden Sie Set Comprehensions {x für x in l2} oder set (l2), um set zu erhalten, und verwenden Sie List Comprehensions , um die Liste abzurufen

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

Benchmark-Testcode:

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))

Benchmark-Testergebnis:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    
lbsweek
quelle
1
l2set = set( l2 )stattl2set = { x for x in l2 }
cz
1
Schöne Seele! Es muss jedoch beachtet werden, dass es nur mit hashbaren Objekten funktioniert.
Eerik Sven Puudist
7

Alternative Lösung:

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])
Akshay Hazari
quelle
2
Gibt es einen Vorteil bei der Verwendung dieser Methode? Es sieht so aus, als wäre es komplexer und schwerer zu lesen, ohne viel Nutzen zu haben.
skrrgwasme
Das mag komplex erscheinen. Reduzieren ist sehr flexibel und kann für viele Zwecke verwendet werden. Es ist als Falte bekannt. reduzieren ist eigentlich foldl. Angenommen, Sie möchten komplexere Inhalte hinzufügen, als dies in dieser Funktion möglich ist. Wenn Sie jedoch das Listenverständnis verwenden, das die beste Antwort darstellt, erhalten Sie nur eine Ausgabe des gleichen Typs, dh der Liste und wahrscheinlich der gleichen Länge, während Sie Falten verwenden können Ändern Sie auch den Ausgabetyp. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Diese Lösung ist n * m oder weniger komplex. Andere können besser sein oder auch nicht.
Akshay Hazari
1
reduzieren (Funktion, Liste, anfänglicher Akkumulator (der von jedem Typ sein kann))
Akshay Hazari