Python: Eine Liste basierend auf einer Bedingung teilen?

272

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]
Parand
quelle
5
Ich
bin
5
split ist eine unglückliche Beschreibung dieser Operation, da sie in Bezug auf Python-Strings bereits eine bestimmte Bedeutung hat. Ich denke, Divide ist ein präziseres (oder zumindest im Kontext von Python-Iterables weniger überladenes) Wort, um diese Operation zu beschreiben. Ich bin hier gelandet und habe nach einem Listenäquivalent gesucht str.split(), um die Liste in eine geordnete Sammlung aufeinanderfolgender Unterlisten aufzuteilen . ZB split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])im Gegensatz zum Teilen der Elemente einer Liste nach Kategorien.
Eintopf
Diskussion des gleichen Themas auf Python-Liste.
Xiong Chiamiov
IMAGE_TYPES sollte eine Menge anstelle eines Tupels sein : IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) anstelle von n (o / 2), praktisch ohne Unterschied in der Lesbarkeit.
ChaimG

Antworten:

110
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?

Dieser Code ist perfekt lesbar und äußerst klar!

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

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:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

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

if f[1] == 0:
    continue
dbr
quelle
44
Gibt es nicht einen Weg, Listen zu verstehen, ohne die Liste zweimal durchlaufen zu müssen?
Balki
35
Das Problem ist, dass dies gegen das DRY-Prinzip verstößt. Es wäre schön, wenn es einen besseren Weg gäbe, dies zu tun.
Antimon
21
Sobald der Appetit auf funktionale Programmierung (Haskell) oder funktionalen Stil (LINQ) gestiegen ist, riechen wir Python für sein Alter [x for x in blah if ...]- wortreich, lambdaungeschickt und begrenzt ... Es fühlt sich an, als würde man heute das coolste Auto von 1995 fahren. Nicht das gleiche wie damals.
Tomasz Gandor
6
@TomaszGandor FTR, Haskell ist älter als Python (und hat tatsächlich sein Design beeinflusst). Ich denke, die Syntax für Listenverständnis und Lambdas wurde bewusst etwas wortreich gehalten, vielleicht um zu verhindern, dass sie übermäßig verwendet werden. Was in der Tat ein gewisses Risiko darstellt ... So sehr ich Haskell mag, kann ich sehen, warum viele Leute Python allgemein besser lesbar finden.
links um den
4
Die einfache for-Schleife ist der beste Weg, dies zu tun ... eine einzelne Schleife, sehr klar und lesbar
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
quelle
14
Das ist unglaublich genial! Ich brauchte eine Weile, um zu verstehen, was los war. Ich würde gerne wissen, ob andere glauben, dass dies als lesbarer Code angesehen werden kann oder nicht.
jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)ist besser lesbar.
Dansalmo
21
@dansalmo Vor allem, weil Sie es mit dem for-Zyklus zu einem Einzeiler machen können und wenn Sie etwas Komplizierteres anhängen möchten x, können Sie es nur zu einem machen append:for x in mylist: (good if isgood(x) else bad).append(x)
yo
2
@ MLister, in diesem Fall sollten Sie wahrscheinlich die Attributsuche einschließen(bad.append, good.append)
John La Rooy
11
Eine etwas kürzere Variante:(good if x in goodvals else bad).append(x)
Pi Delport
104

Hier ist der Lazy-Iterator-Ansatz:

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)

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:

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))

Normalerweise ist der Ansatz der nicht faulen Listenrückgabe jedoch besser:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

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:

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists

Verwendungszweck:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']
Ameisen Aasma
quelle
Sie haben wahrscheinlich Recht, dass dies gegen das YAGNI-Prinzip verstößt. Es basiert auf der Annahme, dass die Anzahl der verschiedenen Listen, in die Dinge unterteilt werden können, in Zukunft zunehmen wird.
Ameisen Aasma
17
Es mag viel Code sein, aber wenn [ x for x in my_list if ExpensiveOperation(x) ]die Ausführung lange dauert, möchten Sie es auf keinen Fall zweimal tun!
Dash-Tom-Bang
1
+1 für das Anbieten mehrerer Variationen, einschließlich iteratorbasierter und einer spezifischen "in X" -Lösung. Die OPs "in goodvals" mögen klein sein, aber das Ersetzen durch ein sehr großes Wörterbuch oder ein teures Prädikat könnte teuer sein. Außerdem wird die Notwendigkeit verringert, das Listenverständnis zweimal überall dort zu schreiben, wo es benötigt wird, wodurch die Wahrscheinlichkeit verringert wird, dass Tippfehler / Benutzerfehler auftreten. Schöne Lösung. Vielen Dank!
cod3monk3y
3
Beachten Sie, dass teealle Werte zwischen den zurückgegebenen Iteratoren gespeichert werden, sodass nicht wirklich Speicherplatz gespart wird, wenn Sie einen gesamten Generator und dann den anderen durchlaufen.
John La Rooy
25

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:

def SplitIntoTwoLists(l, f):
  a = []
  b = []
  for i in l:
    if f(i):
      a.append(i)
    else:
      b.append(i)
 return (a,b)

Auf diese Weise verarbeiten Sie nichts zweimal und wiederholen auch keinen Code.

winden
quelle
Genau. Ich suchte nach einem "eleganten" (dh hier kurzen und eingebauten / impliziten) Weg, dies zu tun, ohne die Liste zweimal zu scannen, aber dies scheint (ohne Profilerstellung) der richtige Weg zu sein. Natürlich wäre es sowieso nur für große Datenmengen von Bedeutung.
Matthew Flaschen
IMHO, wenn Sie einen Weg kennen, dies mit weniger CPU-Auslastung (und damit weniger Stromverbrauch) zu tun, gibt es keinen Grund, es nicht zu verwenden.
Winden
2
@winden ... Portierung aller meiner Python nach C .;)
Elliot Cameron
19

Meine Meinung dazu. Ich schlage eine faule Single-Pass- partitionFunktion vor, die die relative Reihenfolge in den Ausgabe-Teilsequenzen beibehält.

1. Anforderungen

Ich gehe davon aus, dass die Anforderungen sind:

  • Beibehaltung der relativen Reihenfolge der Elemente (daher keine Mengen und Wörterbücher)
  • Bewerten Sie die Bedingung nur einmal für jedes Element (daher nicht ( i) filteroder groupby)
  • Ermöglichen Sie einen verzögerten Verbrauch beider Sequenzen (wenn wir es uns leisten können, sie vorab zu berechnen, ist die naive Implementierung wahrscheinlich auch akzeptabel).

2. splitBibliothek

Meine partitionFunktion (unten vorgestellt) und andere ähnliche Funktionen haben es in eine kleine Bibliothek geschafft:

Es kann normalerweise über PyPI installiert werden:

pip install --user split

Verwenden Sie die partitionFunktion , um eine Liste basierend auf der Bedingung aufzuteilen :

>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]

3. partitionFunktion erklärt

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

from collections import deque

SplitSeq Klasse kümmert sich um die Hauswirtschaft:

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)

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:

    def getNext(self, getGood=True):
        if getGood:
            these, those, cond = self.goods, self.bads, self.cond
        else:
            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
        if these:
            return these.popleft()
        else:
            while 1: # exit on StopIteration
                n = self.seq.next()
                if cond(n):
                    return n
                else:
                    those.append(n)

Der Endbenutzer soll die partitionFunktion verwenden. Es nimmt eine Bedingungsfunktion und eine Sequenz (genau wie mapoder filter) 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.

def partition(condition, sequence):
    cond = condition if condition else bool  # evaluate as bool if condition == None
    ss = SplitSeq(cond, sequence)
    def goods():
        while 1:
            yield ss.getNext(getGood=True)
    def bads():
        while 1:
            yield ss.getNext(getGood=False)
    return goods(), bads()

Ich habe die Testfunktion als erstes Argument gewählt, um die teilweise Anwendung in der Zukunft zu erleichtern (ähnlich wie mapund filter habe die Testfunktion als erstes Argument).

Sastanin
quelle
15

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

def categorize(func, seq):
    """Return mapping from categories to lists
    of categorized items.
    """
    d = defaultdict(list)
    for item in seq:
        d[func(item)].append(item)
    return d
Alan Isaac
quelle
Ich wollte versuchen, die Aussagen aus Zen of Python herauszusuchen , die hier zutreffen, aber es sind zu viele für einen Kommentar. =) Super Code.
jpmc26
13

Zuerst los (Pre-OP-Edit): Verwenden Sie Sets:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]

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:

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])

und das erhöht die Leistung. Ansonsten sieht das, was du hast, für mich gut aus.

RichieHindle
quelle
4
Nicht die beste Lösung, wenn die Listen vor dem Teilen in einer bestimmten Reihenfolge waren und sie in dieser Reihenfolge bleiben müssen.
Daniyar
8
Würde das nicht Duplikate entfernen?
Mai
Das Erstellen einer Menge ist O (n log n). Das zweimalige Durchlaufen der Liste ist O (n). Die eingestellte Lösung mag eleganter sein (wenn sie überhaupt richtig ist), ist aber mit zunehmendem n mit Sicherheit langsamer.
Dash-Tom-Bang
1
@ dash-tom-bang Das Durchlaufen der Liste ist O (n * n). Dies liegt daran, dass möglicherweise jedes Element in der Liste mit jedem Element in der Liste verglichen werden muss goodvals.
ChaimG
@ChaimG guter Punkt, obwohl wir auch die Kosten der Kreuzungs- und Differenzoperationen berücksichtigen müssen (die ich nicht ohne weiteres kenne, aber ich bin mir ziemlich sicher, dass sie auch superlinear sind).
Dash-Tom-Bang
10

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.

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)

gibt:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

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

Brian
quelle
6
good.append(x) if x in goodvals else bad.append(x)

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:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)
John D.
quelle
5

Wenn Sie es im FP-Stil machen möchten:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]

Nicht die am besten lesbare Lösung, aber mindestens einmal durch meine Liste iteriert.

michau
quelle
1
Obwohl die Liste nur einmal durchlaufen wird, ist die Leistung aufgrund der angehängten Liste nicht so gut. Das Anhängen an eine Liste ist möglicherweise eine teure Operation (im Vergleich zu beispielsweise deque.append). Tatsächlich ist diese Lösung im Vergleich zu anderen Lösungen hier extrem langsam (21,4 Sekunden bei 100000 zufälligen Ganzzahlen und Testen ihres Werts).
rlat
5

Persönlich mag ich die Version, die Sie zitiert haben, vorausgesetzt, Sie haben bereits eine Liste mit goodvalsherumhängen. Wenn nicht, so etwas wie:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

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:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

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 intersectionund differencefür Sets natürlich auch gut.

BJ Homer
quelle
Natürlich filter(lambda x: is_good(x), mylist)kann auffilter(is_good, mylist)
robru
Das Hinzufügen des zusätzlichen Funktionsaufrufs verdoppelt (!) die Ausführungszeit im Vergleich zum Listenverständnis gegenüber dem, was ich bei der Profilerstellung gesehen habe. Meistens ist es schwer, ein Listenverständnis zu übertreffen.
Corley Brigman
4

Ich denke, eine Verallgemeinerung des Aufteilens eines Iterablen basierend auf N Bedingungen ist praktisch

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()

Zum Beispiel:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]

Wenn das Element mehrere Bedingungen erfüllen kann, entfernen Sie die Unterbrechung.

Gecko
quelle
3
def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

Überprüfen Sie dies

Hanfei Sun.
quelle
3

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

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

Und es geht los

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)

Mit der Funktion cmpthese ist die Antwort dbr das beste Ergebnis:

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --
FunkySayu
quelle
Schnellere Funktionen mit aktualisierten Benchmarks hier .
ChaimG
2

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:

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false

Anschließend können Sie die Funktion folgendermaßen verwenden:

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})

Wenn Sie nicht gut mit dem resultierenden sind dequeObjekt, können Sie diese leicht konvertieren list, set, was Sie wollen (zum Beispiel list(lower)). Die Konvertierung ist viel schneller, die Erstellung der Listen direkt.

Diese Methode behält die Reihenfolge der Elemente sowie aller Duplikate bei.

rlat
quelle
2

Zum Beispiel das Aufteilen der Liste durch gerade und ungerade

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

Oder allgemein:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

Vorteile:

  • Kürzester möglicher Weg
  • Das Prädikat gilt nur einmal für jedes Element

Nachteile

  • Erfordert Kenntnisse des funktionalen Programmierparadigmas
Pavel Ilchenko
quelle
2

Inspiriert von der großartigen (aber knappen!) Antwort von @ gnibbler können wir diesen Ansatz auf die Zuordnung zu mehreren Partitionen anwenden:

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results

Dann splitterkann dann wie folgt verwendet werden:

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]

Dies funktioniert für mehr als zwei Partitionen mit einer komplizierteren Zuordnung (und auch für Iteratoren):

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]

Oder verwenden Sie ein Wörterbuch, um Folgendes abzubilden:

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]
Josh Bode
quelle
... habe gerade bemerkt, dass dies im Grunde dasselbe ist, wie @ alan-isaac bereits geantwortet hat.
Josh Bode
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

append gibt None zurück, also funktioniert es.

Biga
quelle
1

Versuchen Sie es mit der Leistung itertools.

Das itertools- Modul standardisiert einen Kernsatz schneller, speichereffizienter Tools, die für sich oder in Kombination nützlich sind. Zusammen bilden sie eine „Iteratoralgebra“, die es ermöglicht, spezielle Tools in reinem Python kurz und effizient zu erstellen.

Siehe itertools.ifilter oder imap.

itertools.ifilter (Prädikat, iterierbar)

Erstellen Sie einen Iterator, der Elemente von iterable filtert und nur diejenigen zurückgibt, für die das Prädikat True ist

Gimel
quelle
ifilter / imap (und Generatoren im Allgemeinen) sind ziemlich langsam ... im Allgemeinen, in meiner Profilierung, wenn Sie wie eine Liste Verständnis nehmen [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.
Corley Brigman
1

Manchmal brauchen Sie die andere Hälfte der Liste nicht. Beispielsweise:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')
Shikhar Mall
quelle
1

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.

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)

Etwas kürzer:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)

Benchmark-Ergebnisse:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --

Der vollständige Benchmark-Code für Python 3.7 (geändert von FunkySayu):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))
ChaimG
quelle
0

Wenn Sie auf klug bestehen, könnten Sie Windens Lösung und nur ein bisschen falsche Klugheit nehmen:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
quelle
3
Das "d oder {}" ist ein bisschen gefährlich. Wenn ein leeres Diktat übergeben wird, wird es nicht an Ort und Stelle mutiert.
Brian
Stimmt, aber es wird zurückgegeben, also ... Eigentlich ist dies das perfekte Beispiel dafür, warum Sie Ihrem Code nicht klüger hinzufügen möchten. :-P
Anders Eurenius
0

Hier gibt es bereits einige Lösungen, aber eine andere Möglichkeit wäre:

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]

Iteriert die Liste nur einmal und sieht ein bisschen pythonischer und daher für mich lesbar aus.

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>
Shreyas
quelle
0

Ich würde einen 2-Pass-Ansatz verfolgen und die Bewertung des Prädikats vom Filtern der Liste trennen:

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

Das Schöne an der Leistung (zusätzlich zur prednur einmaligen Auswertung für jedes Mitglied von iterable) 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.

Jim Witschey
quelle
0

Lösung

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))

Prüfung

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])
doctorzeb8
quelle
0

Wenn es Ihnen nichts ausmacht, dort eine externe Bibliothek zu verwenden, weiß ich, dass zwei diese Operation nativ implementieren:

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
  • iteration_utilities.partition::

    >>> from iteration_utilities import partition
    >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
    >>> notimages
    [('file2.avi', 999, '.avi')]
    >>> images
    [('file1.jpg', 33, '.jpg')]
  • more_itertools.partition

    >>> from more_itertools import partition
    >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
    >>> list(notimages)  # returns a generator so you need to explicitly convert to list.
    [('file2.avi', 999, '.avi')]
    >>> list(images)
    [('file1.jpg', 33, '.jpg')]
MSeifert
quelle
0

Ich bin mir nicht sicher, ob dies ein guter Ansatz ist, aber er kann auch auf diese Weise durchgeführt werden

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))
kiran
quelle
0

Wenn die Liste aus Gruppen und intermittierenden Trennzeichen besteht, können Sie Folgendes verwenden:

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

Verwendungszweck:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
quelle
0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

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.

Chrisjan
quelle
0

Noch eine Antwort, kurz, aber "böse" (für Nebenwirkungen des Listenverständnisses).

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
Shay
quelle