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]

4voto

MSeifert Punkte 131411

Wenn es Ihnen nichts ausmacht, eine externe Bibliothek zu verwenden, kenne ich zwei, die diesen Vorgang 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')]

4voto

Hanfei Sun Punkte 41905
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)

Siehe este

2voto

Pavel Ilchenko Punkte 21

Zum Beispiel, Aufteilung der Liste nach gerade und ungerade

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

Oder ganz allgemein:

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

Vorteile:

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

Benachteiligungen

  • Erfordert Kenntnisse des Paradigmas der funktionalen Programmierung

2voto

Josh Bode Punkte 3173

Inspiriert von @gnibbler's tolle (aber knappe!) Antwort können wir diesen Ansatz für die Abbildung auf mehrere 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

Entonces splitter kann 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 bei mehr als zwei Partitionen mit einer komplizierteren Zuordnung (und auch bei 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 die Verwendung eines Wörterbuchs für die Zuordnung:

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

2voto

rlat Punkte 452

Eine weitere Lösung für dieses Problem. Ich brauchte eine Lösung, die so schnell wie möglich ist. Das bedeutet nur eine Iteration über die Liste und vorzugsweise O(1) für das Hinzufügen von Daten zu einer der resultierenden Listen. Dies ist sehr ähnlich zu der Lösung, die von sastanin , nur viel kürzer:

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

Dann können Sie die Funktion auf folgende Weise 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 mit dem Ergebnis nicht einverstanden sind deque Objekt, können Sie es einfach in list , set , was immer Sie wollen (zum Beispiel list(lower) ). Die Umwandlung ist viel schneller, als der Aufbau der Listen direkt.

Mit dieser Methode wird die Reihenfolge der Elemente beibehalten, ebenso wie eventuelle Duplikate.

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