Python - Entfernen von Elementen aus Listen

8
%Vor%

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 ist.

    
orokusaki 16.10.2010, 04:16
quelle

6 Antworten

10

Hier sind einige Versuche:

%Vor%

Nachdem ich nun einen Moment zum Nachdenken hatte, merke ich, dass das L2 + L3 Ding eine temporäre Liste erstellt, die sofort weggeworfen wird. Also ein noch besserer Weg ist:

%Vor%

Update: Ich sehe extravagante Behauptungen über die Leistung, und ich möchte behaupten, dass meine Lösung so schnell wie möglich war. Das Erstellen von Zwischenergebnissen, egal ob es sich um Zwischenlisten oder Zwischeniteratoren handelt, die dann wiederholt aufgerufen werden müssen, wird immer langsamer sein, als einfach L2 und L3 für den Satz direkt zu durchlaufen, wie ich es hier getan habe.

%Vor%

Alle anderen Alternativen (die ich mir vorstellen kann) werden langsamer sein. Wenn wir zum Beispiel die Schleifen selbst machen, anstatt den set() -Konstruktor machen zu lassen, entstehen zusätzliche Kosten:

%Vor%

Wenn Sie Iteratoren verwenden, werden alle diese Funktionen und Rückrufe, die sie beinhalten, natürlich noch teurer:

%Vor%

Ich glaube also, dass die Antwort, die ich letzte Nacht gegeben habe, immer noch weit entfernt ist (für Werte von "weit und weg" größer als etwa 5μs, offensichtlich) die besten, es sei denn, der Fragesteller wird Duplikate in L1 haben und will sie haben wird jedes Mal für jedes Mal entfernt, wenn das Duplikat in einer der anderen Listen erscheint.

    
Brandon Rhodes 16.10.2010, 04:22
quelle
6

update ::: post 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

Das zweite Stück Code geschrieben von Brandon Craig Rhodes ist ganz gut, aber da er nicht auf meinen Vorschlag reagiert hat, ein eingefrorenes Set zu verwenden (na ja, nicht, als ich anfing, dies zu schreiben), werde ich weitermachen und es selbst posten.

Die gesamte zugrunde liegende Aufgabe besteht darin, zu überprüfen, ob jeder einer Reihe von Werten ( L1 ) in einer anderen Menge von Werten enthalten ist; Diese Menge von Werten ist der Inhalt von L2 und L3 . Die Verwendung des Wortes "set" in diesem Satz ist bezeichnend: Obwohl L2 und L3 list s sind, interessieren wir uns nicht für ihre listenähnlichen Eigenschaften, wie die Reihenfolge, in der sich ihre Werte befinden oder wie viele von ihnen enthalten sie jeweils. Wir kümmern uns nur um das Set (dort ist es wieder) von Werten, die sie zusammen enthalten.

Wenn diese Menge von Werten als eine Liste gespeichert wird, müssen Sie nacheinander die Listenelemente durchgehen und jedes einzelne prü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. Das funktioniert ähnlich wie die dict -Typen von python, wenn Sie nach einem Schlüssel suchen.

Der Unterschied zwischen sets und frozensets besteht darin, dass Sets veränderbar sind, was bedeutet, dass sie nach der Erstellung geändert werden können. Dokumentation zu beiden Typen ist hier .

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

update ...

Brandon hat diese Frage beantwortet: Der wahre Vorteil von eingefrorenen Sets ist, dass ihre Unveränderlichkeit es ihnen ermöglicht, zu sein hashbar , damit sie Wörterbuchschlüssel oder Mitglieder anderer Sets sein können.

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

... update

Nun, da frozensets unveränderbar sind, haben sie keine Update-Methode. 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.

%Vor%

Dieser Generatorausdruck macht items zu einem Iterator, über den Inhalt von L2 und L3 . Nicht nur das, aber es macht es, ohne eine ganze Liste voller Zwischenobjekte zu erstellen. Die Verwendung von geschachtelten for -Ausdrücken in Generatoren ist ein wenig verwirrend, aber ich sorge dafür, dass es sortiert bleibt, indem ich mich daran erinnere, dass sie in der gleichen Reihenfolge verschachtelt werden wie beim Schreiben von aktuellen Schleifen, z. B.

%Vor%

Diese Generatorfunktion entspricht dem Generatorausdruck, den wir items zugewiesen haben. Nun, außer dass es eine parametrisierte Funktionsdefinition anstelle einer direkten Zuweisung zu einer Variablen ist.

Wie auch immer, genug Exkurs. 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 ausgeführt werden soll, 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 Funktion frozenset übergebe, die darüber iteriert und ein frostiges, kaltes, gefrorenes Set zurückgibt.

%Vor%

Sie könnten die letzten beiden Zeilen kombinieren, indem Sie den Generatorausdruck direkt in den Aufruf von frozenset :

setzen %Vor%

Dieser nette syntaktische Trick funktioniert, solange der Iterator , der vom Generator-Ausdruck erzeugt wird, der einzige Parameter ist zu der Funktion, die Sie anrufen. Andernfalls müssen Sie es in seiner üblichen separaten Klammer schreiben, so wie Sie ein Tupel als Argument für die Funktion übergeben haben.

Nun können wir eine neue Liste auf die gleiche Weise wie Brandon erstellen, mit einem Listenverständnis . Diese verwenden die gleiche Syntax wie Generator-Ausdrücke und machen im Prinzip dasselbe, außer dass sie eifrig statt faul sind (dies sind wiederum technische Begriffe), also sie Machen Sie sich bereit, über die Elemente zu iterieren und daraus eine Liste zu erstellen.

%Vor%

Dies entspricht der Übergabe eines Generatorausdrucks an list , z. B.

%Vor%

aber mehr idiomatisch.

Damit wird die Liste L4 erstellt, die die Elemente von L1 enthält, die weder in L2 noch in L3 waren, wobei die Reihenfolge, in der sie sich ursprünglich befanden, und die Anzahl derer, die es gab, beibehalten wurde .

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

%Vor%

Sie können eine Liste daraus erstellen genauso wie st0le , aber das ist vielleicht nicht das was du willst. Wenn Sie wirklich die Menge von Werten wollen, die nur in L1 gefunden werden, haben Sie möglicherweise einen guten Grund, diese Menge als set oder zu behalten in der Tat ein frozenset :

%Vor%

... Annnnd , jetzt für etwas ganz anderes:

%Vor%     
intuited 16.10.2010 05:43
quelle
0

Angenommen, Ihre individuellen Listen enthalten keine Duplikate .... Verwenden Sie Set und Difference

%Vor%     
st0le 16.10.2010 04:21
quelle
0

Dies ist vielleicht weniger pythonesque als die Liste-Verständnis-Antwort, hat aber ein einfacheres Aussehen:

%Vor%

Der Vorteil hier ist, dass wir die Reihenfolge der Liste beibehalten, und wenn doppelte Elemente vorhanden sind, entfernen wir nur eins für jedes Mal, wenn es in l2 erscheint.

    
slezica 16.10.2010 04:35
quelle
0

Wenn Sie solche Vorgänge in Listen ausführen, kann dies die Leistung Ihres Programms sehr bald beeinträchtigen. Was passiert, ist mit jedem entfernen, List Operationen machen einen frischen malloc & amp; Elemente verschieben. Dies kann teuer sein, wenn Sie eine sehr große Liste haben oder nicht. Also würde ich das vorschlagen -

Ich gehe davon aus, dass Ihre Liste einzigartige Elemente enthält. Andernfalls müssen Sie in Ihrem Diktat eine Liste mit doppelten Werten pflegen. Wie auch immer für die Daten, die du zur Verfügung gestellt hast, hier ist es -

METHODE 1

%Vor%

METHODE 2 Wenn das alles zu viel Code aussieht. Dann könnten Sie set verwenden. Aber auf diese Weise verliert Ihre Liste alle doppelten Elemente.

%Vor%     
Srikar Appalaraju 16.10.2010 04:35
quelle
0

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

Die Vorgehensweise ist wie folgt:

  1. Verwenden Sie itertools.chain , um L2 und L3 zu verketten, ohne eine speicherintensive Kopie zu erstellen
  2. Erstellen Sie daraus eine Menge (in diesem Fall wird ein eingefrorenes Set ausgeführt, weil wir es nach der Erstellung nicht ändern)
  3. Verwenden Sie 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 jetzt der Code:

%Vor%

Dies sollte eine der schnellsten, einfachsten und am wenigsten speicherintensiven Lösung sein.

    
AndiDog 16.10.2010 07:26
quelle

Tags und Links