2010-10-16 4 views
8
# I have 3 lists: 
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
# I want to create another that is L1 minus L2's memebers and L3's memebers, so: 
L4 = (L1 - L2) - L3 # Of course this isn't going to work 

Ich frage mich, was ist der "richtige" Weg, dies zu tun. Ich kann es auf viele verschiedene Arten machen, aber Python's Style Guide sagt, dass es nur 1 korrekte Art geben sollte, jede Sache zu machen. Ich habe nie gewusst, was das war.Python - Entfernen von Elementen aus Listen

+3

Es gibt keinen einzigen richtigen Weg, dies zu tun, bis Sie entscheiden, ob Sie sich um Dubletten und Bestellungen kümmern oder sich nicht darum kümmern. Wahrscheinlich eine Art Listenverständnis oder Satzarbeit, je nachdem, was dir wichtig ist. – istruble

+1

Ist es auch OK anzunehmen, dass alle Elemente in den Listen die ganze Zeit über hashbar sind? Wenn nicht oder manchmal nicht, wäre das sehr wichtig. – martineau

+1

Warum verwenden Sie nicht zuerst Sätze? Dann würde deine "Arithmetik" funktionieren. – poke

Antwort

10

Hier sind einige Versuche:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ] # parens for clarity 

tmpset = set(L2 + L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Nun, da ich einen Moment zu denken gehabt haben, Ich merke, dass die L2 + L3 Sache eine temporäre Liste erstellt, die sofort weggeworfen wird. So eine noch bessere Möglichkeit ist:

tmpset = set(L2) 
tmpset.update(L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

Update: Ich sehe einige extravagante Ansprüche um über die Leistung geworfen, und ich möchte behaupten, dass meine Lösung schon so schnell wie möglich war. Das Erstellen von Zwischenergebnissen, unabhängig davon, ob es sich um Zwischenlisten oder Zwischeniteratoren handelt, die dann wiederholt aufgerufen werden müssen, ist immer langsamer, als L2 und L3 für den Satz direkt zu durchlaufen, wie ich es hier getan habe.

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]' 
10000 loops, best of 3: 39.7 usec per loop 

Alle anderen Alternativen (die ich mir vorstellen kann) werden notwendigerweise langsamer als das sein. Doing die Schleifen selbst, zum Beispiel, anstatt den set() Konstruktor ließ sie tun, fügt Kosten:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]' 
10000 loops, best of 3: 46.4 usec per loop 

Iteratoren verwenden, werden alle der staatlichen Spar und Rückrufe sie beinhalten, wird offensichtlich sein, noch teurer:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \ 
    'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop 

Deshalb glaube ich, dass die Antwort gab ich gestern Abend noch weit weg (für Werte von „weit weg“ von mehr als 5μsec um, natürlich) die beste, es sei denn, die Fragesteller werden Duplikate in L1 haben und will Sie werden jeweils einmal entfernt, wenn das Duplikat in einer der anderen Listen erscheint .

+0

Es könnte möglich sein, etwas mehr Leistung zu erzielen, indem ein eingefrorenes Set aus einer Kette von zwei Listen-Iteratoren erstellt wird. – intuited

+0

Nein, eingefrorene Sätze haben genau die gleiche Geschwindigkeit wie normale, erfordern aber normalerweise mehr Aufwand beim Erstellen, da Sie ein Zwischenergebnis oder eine Schleife selbst erstellen müssen, wenn Sie sie wie hier aus mehreren Eingabe-Iterablen erstellen. –

0

Angenommen, Ihre einzelnen Listen werden keine Duplikate enthalten .... Verwenden Set und Difference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
print(list(set(L1) - set(L2) - set(L3))) 
+2

Dies würde die Reihenfolge verlieren. –

+1

Ja, der Hauptunterschied zwischen einer Liste und einem Satz ... – mepcotterell

+1

Wenn Bestellung/Duplikate kein Problem sind, ist dies die sauberste Option, IMO –

0

Solche Operationen in Listen können die Leistung Ihres Programms sehr bald beeinträchtigen. Was passiert, ist mit jedem entfernen, List Operationen machen einen frischen malloc & Elemente bewegen. Dies kann teuer sein, wenn Sie eine sehr große Liste haben oder nicht. Also würde ich vorschlagen, dass -

Ich nehme an, Ihre Liste hat einzigartige Elemente. Andernfalls müssen Sie in Ihrem Diktat eine Liste mit doppelten Werten pflegen. Auf jeden Fall für die Daten hier Ihre zur Verfügung gestellt, ist-es

VERFAHREN 1

d = dict() 
for x in L1: d[x] = True 

# Check if L2 data is in 'd' 
for x in L2: 
    if x in d: 
     d[x] = False 

for x in L3: 
    if x in d: 
     d[x] = False 

# Finally retrieve all keys with value as True. 
final_list = [x for x in d if d[x]] 

METHODE 2 Wenn alles, was wie zu viel Code aussieht. Dann könnten Sie versuchen, set zu verwenden. Auf diese Weise verliert Ihre Liste alle doppelten Elemente.

final_set = set.difference(set(L1),set(L2),set(L3)) 
final_list = list(final_set) 
+0

Das Listenverständnis entfernt keine Operationen, die teuer sind. – aaronasterling

+0

#aaron ja ich weiß. Ich habe mich auf die Lösung von Santiago bezogen. –

+1

Hey, du verwendest grundsätzlich ein Wörterbuch als Set. Sie haben einen ganz anderen Datentyp dafür: http://docs.python.org/library/stdtypes.html#types-set – intuited

0

Diese weniger Pythonesque als die Liste Verständnis Antwort sein kann, aber einen einfacheren Blick zu ihm:

l1 = [ ... ] 
l2 = [ ... ] 

diff = list(l1) # this copies the list 
for element in l2: 
    diff.remove(element) 

Der hier Vorteil ist, dass wir, um der Liste zu erhalten , und wenn Es gibt doppelte Elemente, wir entfernen nur eines für jedes Mal, wenn es in l2 erscheint.

+1

Das ist unglaublich teuer und ist im Gegenteil mehr kompliziert zu betrachten als ein einfaches Verständnis. – aaronasterling

+0

Ein Geschmacksproblem scheint es. Ich mag Listen Comprehensions sehr, ich tendiere tatsächlich dazu, sie zu übernutzen, aber ich denke nicht "n für n in L wenn n nicht in ..." ist nett in den Augen. Auf die eine oder andere Weise ist es, ich gebe es zu, rechenintensiv. – slezica

6

update ::: Beitrag enthält einen Verweis auf falsche Behauptungen von schlechter Leistung von Sätzen im Vergleich zu frozensets. Ich behaupte, dass es immer noch sinnvoll ist, in dieser Instanz ein eingefrorenes Set zu verwenden, obwohl es nicht nötig ist, das Set selbst zu hacken, nur weil es semantisch korrekter ist. Obwohl ich in der Praxis nicht die zusätzlichen 6 Zeichen eingeben würde. Ich fühle mich nicht motiviert, den Post zu bearbeiten und zu bearbeiten, also sei nur darauf hingewiesen, dass die "Anschuldigungen" Links zu einigen falsch ausgeführten Tests verlinken. Die blutigen Details sind in den Kommentaren zusammengefasst. ::: Update

Der zweite Teil des Codes posted von Brandon Craig Rhodes ist recht gut, aber da er nicht auf meinen Vorschlag reagierte (nicht gut, als ich begann, diese zu schreiben, jedenfalls) eine frozenset über die Verwendung von Ich werde weitermachen und es selbst posten.

Die gesamte Grundlage des vorliegenden Projekts besteht darin, zu überprüfen, ob sich jeder einer Reihe von Werten (L1) in einer anderen Gruppe von Werten befindet; Dieser Satz von Werten ist der Inhalt von L2 und L3. Die Verwendung des Wortes "set" in diesem Satz ist bezeichnend: Obwohl L2 und L3list s sind, interessieren wir uns nicht wirklich für ihre listenähnlichen Eigenschaften, wie die Reihenfolge, in der ihre Werte sind oder wie viele von ihnen enthalten. Wir kümmern uns nur um die setzen (dort ist es wieder) der Werte, die sie zusammen enthalten.

Wenn diese Gruppe von Werten als Liste gespeichert wird, müssen Sie nacheinander die Listenelemente durchgehen und jedes einzelne überprüfen. Es ist relativ zeitaufwendig und es ist eine schlechte Semantik: Es ist wieder eine "Menge" von Werten, keine Liste. Also hat Python diese ordentlichen Settypen, die eine Reihe einzigartiger Werte enthalten, und kann Ihnen schnell sagen, ob ein Wert darin enthalten ist oder nicht. Dies funktioniert auf die gleiche Art und Weise, wie Pythons dict Typen funktionieren, wenn Sie nach einem Schlüssel suchen.

Der Unterschied zwischen Sätzen und frozensets ist, dass Sets sind wandelbar, was bedeutet, dass sie nach der Erstellung geändert werden können. Die Dokumentation für beide Typen lautet here.

Da die Menge, die wir erstellen müssen, die Union der in L2 und L3 gespeicherten Werte nicht geändert wird, sobald sie erstellt wird, ist es semantisch angemessen, einen unveränderlichen Datentyp zu verwenden. Dies hat auch einige Leistungsvorteile. Nun, es macht Sinn, dass es einen Vorteil hätte; Sonst, warum hätte Python frozenset als eingebaut?

Update ...

Brandon diese Frage beantwortet hat: der eigentliche Vorteil von gefrorenen Sets ist, dass ihre Unveränderlichkeit es ihnen ermöglicht, hashable zu sein, so dass sie Dictionary-Schlüssel oder Mitglieder anderer Sätze sein .

Ich habe einige informelle Timing-Tests durchgeführt, die die Geschwindigkeit für die Erstellung und Suche auf relativ großen (3000 Elemente) eingefrorenen und veränderbaren Mengen verglichen; Es gab keinen großen Unterschied. Dies widerspricht der obigen Verbindung, unterstützt aber das, was Brandon über ihre Identität sagt, aber für den Aspekt der Veränderbarkeit.

... Update

Jetzt, da frozensets unveränderlich sind, sie keine Update-Methode haben. Brandon verwendete die set.update-Methode, um zu vermeiden, dass eine temporäre Liste auf dem Weg zur Erstellung erstellt und dann verworfen wird. Ich werde einen anderen Ansatz wählen.

items = (item for lst in (L2, L3) for item in lst) 

Diese generator expression macht items einen Iterator über, nacheinander, dessen Inhalt L2 und L3. Nicht nur das, aber es macht es, ohne eine ganze Liste voller Zwischenobjekte zu erstellen. Die Verwendung geschachtelter for Ausdrücke in Generatoren ist ein wenig verwirrend, aber ich schaffe es, sie zu sortieren, indem ich mich daran erinnere, dass sie in der gleichen Reihenfolge verschachteln, wie sie es tun würden, wenn Sie tatsächliche For-Schleifen schreiben würden, z.B.

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

Das generator function zum Generator Ausdruck äquivalent ist, die wir zu items zugeordnet. Nun, außer dass es eine parametrisierte Funktionsdefinition anstelle einer direkten Zuweisung zu einer Variablen ist.

Wie auch immer, genug Abschweifung. Die große Sache mit Generatoren ist, dass sie eigentlich gar nichts machen. Nun, zumindest nicht sofort: Sie richten nur Arbeit ein, die später erledigt werden muss, wenn der Generatorausdruck iteriert ist. Dies wird formal als faul bezeichnet. Wir werden das tun (nun, ich bin es sowieso), indem ich items an die frozenset Funktion übergebe, die darüber iteriert und ein frostiges kaltes gefrorenes Set zurückgibt.

unwanted = frozenset(items) 

Sie tatsächlich konnte die letzten beiden Zeilen, kombiniert durch den Generator Ausdruck direkt im Aufruf zu frozenset setzen:

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

Dieser ordentlichen syntaktischen Trick funktioniert, solange die durch den Generator Ausdruck erstellt iterator ist der einzige Parameter für die Funktion, die Sie anrufen. Andernfalls müssen Sie es in seine übliche separate Klammer schreiben, genau so, als ob Sie ein Tupel als Argument für die Funktion übergeben würden.

Jetzt können wir eine neue Liste auf die gleiche Weise wie Brandon, mit einem list comprehension erstellen. Diese verwenden die gleiche Syntax wie Generator Ausdrücke und tun im Grunde die gleiche Sache, mit der Ausnahme, dass sie begierig statt faul sind (wieder, das sind tatsächliche Fachbegriffe), so dass sie direkt zur Arbeit Iterieren über die Elemente und erstellen eine Liste von ihnen.

L4 = [item for item in L1 if item not in unwanted] 

Dies ist gleichbedeutend mit einem Generator Ausdruck list zum Übergeben, z.B.

L4 = list(item for item in L1 if item not in unwanted) 

aber mehr idiomatische.

So wird dies erstellen die Liste L4, die Elemente von L1 enthält, die nicht entweder in L2 waren oder L3, die Aufrechterhaltung der Ordnung, dass sie ursprünglich in und die Zahl derer, die dort waren.


Wenn Sie nur wissen wollen die Werte in L1 sind aber nicht in L2 oder L3, es ist viel einfacher: Sie schaffen nur diesen Satz:

L1_unique_values = set(L1) - unwanted 

Sie eine Liste machen aus davon, as does st0le, aber das könnte nicht wirklich das sein, was Sie wollen. Wenn Sie wirklich wollen, tun die gesetzt von Werten, die nur in L1 zu finden sind, können Sie einen sehr guten Grund haben zu halten, dass gesetzt als set oder in der Tat ein frozenset:

L1_unique_values = frozenset(L1) - unwanted 

... Annnnd, jetzt etwas ganz anderes:

from itertools import ifilterfalse, chain 
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1)) 
+0

+1 Sehr informativ. Die neueste Ergänzung (mit itertools) ist sehr nett. Ich würde sagen, Sie haben Ihren Ph.D in Filtern Listen basierend auf der Aufnahme in eine Reihe von Listen verdient. – aaronasterling

+0

@aaron: Es hat Jahre des Studiums gedauert, aber es hat sich gelohnt. – intuited

+0

Fehle ich etwas oder ist dein Generator Ausdruck nur 'itertools.chain'? Wenn ja, benutze das einfach (du kannst die Erklärung von Generatoren und Generatorenausdrücken beibehalten, aber die Leute müssen etwas darüber lernen). – delnan

0

Ich denke, die Antwort von intuited ist viel zu lang für solch ein einfaches Problem, und Python hat bereits eine eingebaute Funktion, um zwei Listen als Generator zu verketten.

Das Verfahren ist wie folgt:

  1. Verwenden itertools.chain zur Kette L2 und L3 ohne eine speicherintensive Kopie erstellen
  2. Erstellen eines Satzes aus, dass (in diesem Fall ein frozenset, weil wir don nach der Erstellung nicht ändern)
  3. Verwenden Sie das Listenverständnis, um Elemente herauszufiltern, die sich in L1 und auch in L2 oder L3 befinden. Da set/frozenset lookup (x in someset) O (1) ist, wird dies sehr schnell sein.

Und nun der Code:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 

from itertools import chain 
tmp = frozenset(chain(L2, L3)) 
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6] 

Dies sollte eine der schnellsten, einfachsten und speicherintensive Lösung.

+0

Es ist nicht am schnellsten; Überprüfen Sie die Tests in meinem Beitrag. Wenn Sie einen Iterator zwischen die Menge und die bereits iterierbaren Listen setzen, verlangsamt sich dies nur. –

+0

@Brandon Craig Rhodes: Ok, sagen wir mal "eine der schnellsten Lösungen". Vielen Dank, dass Sie Ihre Benchmark-Ergebnisse veröffentlicht haben. – AndiDog

+0

In der Tat - Ihre Lösungen sind definitiv eine der schnellsten und sicherlich eine der Klasse von O (* n * log * m *) Lösungen, die dieses Problem verdient. Ich wollte nur sicherstellen, dass Programmierer erkennen, dass Iteratoren nicht Pixie-Staub sind, die irgendwie schneller sind als Schleifen über einen Container selbst; Jedes Element, das von einem Iterator zurückgegeben wird, erfordert, dass sein Bereich erneut aktiviert wird und sein Code erneut gestartet wird, sodass seine Vorteile nicht umsonst sind. –