Was ist sowohl ästhetisch als auch aus Sicht der Leistung der beste Weg, eine Liste von Elementen basierend auf einer Bedingung in mehrere Listen aufzuteilen? Das Äquivalent von:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
Gibt es eine elegantere Möglichkeit, dies zu tun?
Update: Hier ist der eigentliche Anwendungsfall, um besser zu erklären, was ich versuche:
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
str.split()
, um die Liste in eine geordnete Sammlung aufeinanderfolgender Unterlisten aufzuteilen . ZBsplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
im Gegensatz zum Teilen der Elemente einer Liste nach Kategorien.IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n (1) anstelle von n (o / 2), praktisch ohne Unterschied in der Lesbarkeit.Antworten:
Dieser Code ist perfekt lesbar und äußerst klar!
Auch das ist in Ordnung!
Es kann geringfügige Leistungsverbesserungen bei der Verwendung von Sets geben, aber es ist ein trivialer Unterschied, und ich finde das Listenverständnis viel einfacher zu lesen, und Sie müssen sich keine Sorgen machen, dass die Reihenfolge durcheinander gebracht und Duplikate entfernt werden.
In der Tat kann ich einen weiteren Schritt "rückwärts" gehen und einfach eine einfache for-Schleife verwenden:
Das Verstehen oder Verwenden einer Liste
set()
ist in Ordnung, bis Sie eine andere Prüfung oder ein anderes Stück Logik hinzufügen müssen - sagen Sie, Sie möchten alle 0-Byte-JPEGs entfernen, fügen Sie einfach etwas hinzu wie ..quelle
[x for x in blah if ...]
- wortreich,lambda
ungeschickt und begrenzt ... Es fühlt sich an, als würde man heute das coolste Auto von 1995 fahren. Nicht das gleiche wie damals.quelle
good.append(x) if x in goodvals else bad.append(x)
ist besser lesbar.x
, können Sie es nur zu einem machenappend
:for x in mylist: (good if isgood(x) else bad).append(x)
(bad.append, good.append)
(good if x in goodvals else bad).append(x)
Hier ist der Lazy-Iterator-Ansatz:
Es wertet die Bedingung einmal pro Element aus und gibt zwei Generatoren zurück, wobei zuerst Werte aus der Sequenz ausgegeben werden, in der die Bedingung wahr ist, und die andere, in der sie falsch ist.
Weil es faul ist, können Sie es auf jedem Iterator verwenden, auch auf einem unendlichen:
Normalerweise ist der Ansatz der nicht faulen Listenrückgabe jedoch besser:
Bearbeiten: Für Ihre spezifischere Verwendung des Aufteilens von Elementen in verschiedene Listen durch einen Schlüssel ist hier eine generische Funktion, die dies tut:
Verwendungszweck:
quelle
[ x for x in my_list if ExpensiveOperation(x) ]
die Ausführung lange dauert, möchten Sie es auf keinen Fall zweimal tun!tee
alle Werte zwischen den zurückgegebenen Iteratoren gespeichert werden, sodass nicht wirklich Speicherplatz gespart wird, wenn Sie einen gesamten Generator und dann den anderen durchlaufen.Das Problem bei allen vorgeschlagenen Lösungen besteht darin, dass die Filterfunktion zweimal gescannt und angewendet wird. Ich würde eine einfache kleine Funktion wie diese machen:
Auf diese Weise verarbeiten Sie nichts zweimal und wiederholen auch keinen Code.
quelle
Meine Meinung dazu. Ich schlage eine faule Single-Pass-
partition
Funktion vor, die die relative Reihenfolge in den Ausgabe-Teilsequenzen beibehält.1. Anforderungen
Ich gehe davon aus, dass die Anforderungen sind:
i
)filter
odergroupby
)2.
split
BibliothekMeine
partition
Funktion (unten vorgestellt) und andere ähnliche Funktionen haben es in eine kleine Bibliothek geschafft:Es kann normalerweise über PyPI installiert werden:
Verwenden Sie die
partition
Funktion , um eine Liste basierend auf der Bedingung aufzuteilen :3.
partition
Funktion erklärtIntern müssen wir zwei Teilsequenzen gleichzeitig erstellen. Wenn Sie also nur eine Ausgabesequenz verwenden, wird auch die andere berechnet. Und wir müssen den Status zwischen Benutzeranforderungen beibehalten (verarbeitete, aber noch nicht angeforderte Elemente speichern). Um den Status beizubehalten, verwende ich zwei Warteschlangen mit zwei Enden (
deques
):SplitSeq
Klasse kümmert sich um die Hauswirtschaft:Magie geschieht in ihrer
.getNext()
Methode. Es ist fast wie.next()
bei den Iteratoren, erlaubt jedoch die Angabe, welche Art von Element wir diesmal möchten. Hinter den Kulissen werden die abgelehnten Elemente nicht verworfen, sondern in eine der beiden Warteschlangen gestellt:Der Endbenutzer soll die
partition
Funktion verwenden. Es nimmt eine Bedingungsfunktion und eine Sequenz (genau wiemap
oderfilter
) an und gibt zwei Generatoren zurück. Der erste Generator erstellt eine Teilsequenz von Elementen, für die die Bedingung gilt, der zweite erstellt die komplementäre Teilsequenz. Iteratoren und Generatoren ermöglichen die verzögerte Aufteilung selbst langer oder unendlicher Sequenzen.Ich habe die Testfunktion als erstes Argument gewählt, um die teilweise Anwendung in der Zukunft zu erleichtern (ähnlich wie
map
undfilter
habe die Testfunktion als erstes Argument).quelle
Grundsätzlich mag ich Anders 'Ansatz, da er sehr allgemein ist. Hier ist eine Version, bei der der Kategorisierer an erster Stelle steht (um der Filtersyntax zu entsprechen) und ein Standarddikt verwendet (angenommen importiert).
quelle
Zuerst los (Pre-OP-Edit): Verwenden Sie Sets:
Das ist gut für die Lesbarkeit (IMHO) und die Leistung.
Zweiter Schritt (Post-OP-Edit):
Erstellen Sie Ihre Liste mit guten Erweiterungen als Set:
und das erhöht die Leistung. Ansonsten sieht das, was du hast, für mich gut aus.
quelle
goodvals
.itertools.groupby macht fast das, was Sie wollen, außer dass die Elemente sortiert werden müssen, um sicherzustellen, dass Sie einen einzigen zusammenhängenden Bereich erhalten. Sie müssen also zuerst nach Ihrem Schlüssel sortieren (andernfalls erhalten Sie für jeden Typ mehrere verschachtelte Gruppen). z.B.
gibt:
Ähnlich wie bei den anderen Lösungen kann die Schlüsselfunktion so definiert werden, dass sie in eine beliebige Anzahl von Gruppen unterteilt werden kann.
quelle
Diese elegante und prägnante Antwort von @dansalmo tauchte in den Kommentaren vergraben auf, daher poste ich sie hier nur als Antwort, damit sie die Bedeutung erhält, die sie verdient, insbesondere für neue Leser.
Vollständiges Beispiel:
quelle
Wenn Sie es im FP-Stil machen möchten:
Nicht die am besten lesbare Lösung, aber mindestens einmal durch meine Liste iteriert.
quelle
Persönlich mag ich die Version, die Sie zitiert haben, vorausgesetzt, Sie haben bereits eine Liste mit
goodvals
herumhängen. Wenn nicht, so etwas wie:Das ist natürlich sehr ähnlich wie bei der Verwendung eines Listenverständnisses, wie Sie es ursprünglich getan haben, aber mit einer Funktion anstelle einer Suche:
Im Allgemeinen finde ich die Ästhetik des Listenverständnisses sehr erfreulich. Wenn Sie die Reihenfolge nicht beibehalten müssen und keine Duplikate benötigen, funktioniert die Verwendung der Methoden
intersection
unddifference
für Sets natürlich auch gut.quelle
filter(lambda x: is_good(x), mylist)
kann auffilter(is_good, mylist)
Ich denke, eine Verallgemeinerung des Aufteilens eines Iterablen basierend auf N Bedingungen ist praktisch
Zum Beispiel:
Wenn das Element mehrere Bedingungen erfüllen kann, entfernen Sie die Unterbrechung.
quelle
Überprüfen Sie dies
quelle
Manchmal sieht es so aus, als wäre das Verständnis von Listen nicht das Beste!
Ich habe einen kleinen Test basierend auf der Antwort gemacht, die die Leute auf dieses Thema gegeben haben, getestet auf einer zufällig generierten Liste. Hier ist die Generierung der Liste (es gibt wahrscheinlich einen besseren Weg, aber es geht nicht darum):
Und es geht los
Mit der Funktion cmpthese ist die Antwort dbr das beste Ergebnis:
quelle
Noch eine Lösung für dieses Problem. Ich brauchte eine Lösung, die so schnell wie möglich ist. Dies bedeutet nur eine Iteration über die Liste und vorzugsweise O (1) zum Hinzufügen von Daten zu einer der resultierenden Listen. Dies ist der von Sastanin bereitgestellten Lösung sehr ähnlich , außer dass sie viel kürzer ist:
Anschließend können Sie die Funktion folgendermaßen verwenden:
Wenn Sie nicht gut mit dem resultierenden sind
deque
Objekt, können Sie diese leicht konvertierenlist
,set
, was Sie wollen (zum Beispiellist(lower)
). Die Konvertierung ist viel schneller, die Erstellung der Listen direkt.Diese Methode behält die Reihenfolge der Elemente sowie aller Duplikate bei.
quelle
Zum Beispiel das Aufteilen der Liste durch gerade und ungerade
Oder allgemein:
Vorteile:
Nachteile
quelle
Inspiriert von der großartigen (aber knappen!) Antwort von @ gnibbler können wir diesen Ansatz auf die Zuordnung zu mehreren Partitionen anwenden:
Dann
splitter
kann dann wie folgt verwendet werden:Dies funktioniert für mehr als zwei Partitionen mit einer komplizierteren Zuordnung (und auch für Iteratoren):
Oder verwenden Sie ein Wörterbuch, um Folgendes abzubilden:
quelle
append gibt None zurück, also funktioniert es.
quelle
Versuchen Sie es mit der Leistung
itertools
.Siehe itertools.ifilter oder imap.
quelle
[x for x in a if x > 50000]
auf einer einfachen Anordnung von 100000 ganzen Zahlen (über random.shuffle),filter(lambda x: x> 50000, a)
wird so lange dauern 2x,ifilter(lambda x: x> 50000, a); list(result)
nimmt ungefähr 2,3x so lang. Komisch aber wahr.Manchmal brauchen Sie die andere Hälfte der Liste nicht. Beispielsweise:
quelle
Dies ist der schnellste Weg.
Es verwendet
if else
(wie die Antwort von dbr), erstellt jedoch zuerst eine Menge. Ein Satz reduziert die Anzahl der Operationen von O (m * n) auf O (log m) + O (n), was zu einer Geschwindigkeitssteigerung von 45% + führt.Etwas kürzer:
Benchmark-Ergebnisse:
Der vollständige Benchmark-Code für Python 3.7 (geändert von FunkySayu):
quelle
Wenn Sie auf klug bestehen, könnten Sie Windens Lösung und nur ein bisschen falsche Klugheit nehmen:
quelle
Hier gibt es bereits einige Lösungen, aber eine andere Möglichkeit wäre:
Iteriert die Liste nur einmal und sieht ein bisschen pythonischer und daher für mich lesbar aus.
quelle
Ich würde einen 2-Pass-Ansatz verfolgen und die Bewertung des Prädikats vom Filtern der Liste trennen:
Das Schöne an der Leistung (zusätzlich zur
pred
nur einmaligen Auswertung für jedes Mitglied voniterable
) ist, dass viel Logik aus dem Interpreter in hochoptimierten Iterations- und Mapping-Code verschoben wird. Dies kann die Iteration über lange Iterables beschleunigen, wie in dieser Antwort beschrieben .In Bezug auf die Expressivität werden Ausdruckssprachen wie Verständnis und Mapping genutzt.
quelle
Lösung
Prüfung
quelle
Wenn es Ihnen nichts ausmacht, dort eine externe Bibliothek zu verwenden, weiß ich, dass zwei diese Operation nativ implementieren:
iteration_utilities.partition
::more_itertools.partition
quelle
Ich bin mir nicht sicher, ob dies ein guter Ansatz ist, aber er kann auch auf diese Weise durchgeführt werden
quelle
Wenn die Liste aus Gruppen und intermittierenden Trennzeichen besteht, können Sie Folgendes verwenden:
Verwendungszweck:
quelle
Schön, wenn der Zustand länger ist, wie in Ihrem Beispiel. Der Leser muss nicht herausfinden, in welchem negativen Zustand er sich befindet und ob alle anderen Fälle erfasst werden.
quelle
Noch eine Antwort, kurz, aber "böse" (für Nebenwirkungen des Listenverständnisses).
quelle