397 Stimmen

Wie kann man eine Liste anhand einer Bedingung aufteilen?

Was ist der beste Weg, sowohl ästhetisch als auch von der Leistung her, um eine Liste von Elementen in mehrere Listen auf der Grundlage einer Bedingung 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?

Hier ist der eigentliche Anwendungsfall, um besser zu erklären, was ich zu tun 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]

329voto

John La Rooy Punkte 278961
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)

156voto

dbr Punkte 158949
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 sehr 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]

Nochmals, dies ist Gut!

Die Verwendung von Sets mag zwar zu leichten Leistungsverbesserungen führen, aber das ist ein trivialer Unterschied, und ich finde, dass die Liste viel leichter zu lesen ist, und man muss sich keine Sorgen machen, dass die Reihenfolge durcheinander gerät, dass Duplikate entfernt werden und so weiter.

Vielleicht gehe ich sogar noch einen Schritt "zurück" und verwende eine einfache for-Schleife:

images, anims = [], []

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

Das Listen-Verständnis oder die Verwendung von set() ist in Ordnung, bis Sie eine andere Prüfung oder ein anderes Bit der Logik hinzufügen müssen - sagen Sie, Sie wollen alle 0-Byte-Jpegs entfernen, fügen Sie einfach etwas wie.

if f[1] == 0:
    continue

125voto

Ants Aasma Punkte 50286

Hier ist der Ansatz des "Lazy Iterators":

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)

Sie wertet die Bedingung einmal pro Element aus und gibt zwei Generatoren zurück, wobei der erste Werte aus der Sequenz liefert, bei denen die Bedingung wahr ist, und der andere, bei denen sie falsch ist.

Weil es faul ist, kann man es auf jeden Iterator anwenden, sogar auf einen 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)))

In der Regel ist es jedoch besser, wenn die Liste nicht zu schnell zurückgegeben wird:

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 Usecase der Aufteilung Elemente in verschiedene Listen durch einige Schlüssel, heres eine generische Funktion, die das 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

Verwendung:

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

29voto

winden Punkte 2497

Das Problem bei allen vorgeschlagenen Lösungen ist, dass die Filterfunktion zweimal gescannt und angewendet wird. Ich würde eine einfache kleine Funktion wie diese machen:

def split_into_two_lists(lst, f):
    a = []
    b = []
    for elem in lst:
        if f(elem):
            a.append(elem)
        else:
            b.append(elem)
    return a, b

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

18voto

sastanin Punkte 38556

Meine Meinung dazu. Ich schlage eine faule, einseitige Vorgehensweise vor, partition Funktion, die die relative Reihenfolge in den ausgegebenen Teilsequenzen beibehält.

1. Anforderungen

Ich gehe davon aus, dass dies die Voraussetzungen sind:

  • die relative Reihenfolge der Elemente beibehalten (daher keine Mengen und Wörterbücher)
  • Bedingung nur einmal für jedes Element auswerten (daher keine Verwendung von ( i ) filter o groupby )
  • die faule Nutzung einer der beiden Sequenzen ermöglichen (wenn wir es uns leisten können vorzurechnen, dann ist die naive Implementierung wahrscheinlich auch akzeptabel)

2. split Bibliothek

Meine partition Funktion (siehe unten) und andere ähnliche Funktionen haben es in eine kleine Bibliothek geschafft:

Es ist normal über PyPI installierbar:

pip install --user split

Um eine Liste aufgrund einer Bedingung aufzuteilen, verwenden Sie partition Funktion:

>>> 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. partition Funktion erklärt

Intern müssen wir zwei Teilsequenzen auf einmal erstellen, also verbrauchen wir nur eine Ausgabesequenz verbraucht wird, muss die andere auch berechnet zu berechnen. Außerdem müssen wir den Zustand zwischen den Benutzeranfragen beibehalten (Speicherung von verarbeiteten aber noch nicht angeforderte Elemente). Um den Zustand zu erhalten, verwende ich zwei doppelendige Warteschlangen ( deques ):

from collections import deque

SplitSeq Klasse kümmert sich um die Haushaltsführung:

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

Magie geschieht in ihrem .getNext() Methode. Es ist fast wie .next() der Iteratoren, ermöglicht aber die Angabe der Art des gewünschten Elements dieses Mal. Hinter den Kulissen werden die abgelehnten Elemente nicht verworfen, sondern legt sie stattdessen in einer der beiden Warteschlangen ab:

    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 Endnutzer soll Folgendes verwenden partition Funktion. Sie nimmt eine Bedingungsfunktion und eine Sequenz (genau wie map o filter ), und gibt zwei Generatoren zurück. Der erste Generator bildet eine Teilsequenz von Elementen, für die die Bedingung gilt, der zweite erzeugt die komplementäre Teilsequenz. Iteratoren und Generatoren ermöglichen träge Aufteilung auch 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 Teilanwendung in der Zukunft zu erleichtern (ähnlich wie bei der map y filter haben die Testfunktion als erstes Argument).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X