Python: podzielić listę na podstawie warunku?

272

Jaki jest najlepszy sposób, zarówno pod względem estetycznym, jak i pod względem wydajności, na podzielenie listy elementów na wiele list na podstawie warunkowej? Odpowiednik:

good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

czy jest na to bardziej elegancki sposób?

Aktualizacja: oto rzeczywisty przypadek użycia, aby lepiej wyjaśnić, co próbuję zrobić:

# 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
źródło
5
wylądowałem tutaj, szukając sposobu na spełnienie warunku w zestawieniu konstruktora, twoje pytanie odpowiedziało na moje pytanie :)
Anuvrat Parashar
5
split jest niefortunnym opisem tej operacji, ponieważ ma już określone znaczenie w odniesieniu do ciągów Python. Myślę, że dzielenie jest bardziej precyzyjnym (lub przynajmniej mniej przeładowanym w kontekście iterowalnym Pythonem) słowem opisującym tę operację. Wylądowałem tutaj, szukając ekwiwalentu listy str.split(), aby podzielić ją na uporządkowany zbiór kolejnych podlist. Np split([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6]). W przeciwieństwie do dzielenia elementów listy według kategorii.
Gulasz
Dyskusja na ten sam temat na liście python.
Xiong Chiamiov
IMAGE_TYPES powinien być zestaw zamiast krotki: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) zamiast n (o / 2), praktycznie bez różnicy w czytelności.
ChaimG,

Odpowiedzi:

110
good = [x for x in mylist if x in goodvals]
bad  = [x for x in mylist if x not in goodvals]

czy jest na to bardziej elegancki sposób?

Ten kod jest doskonale czytelny i wyjątkowo przejrzysty!

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

Znowu w porządku!

Może być niewielka poprawa wydajności przy użyciu zestawów, ale jest to trywialna różnica, a zrozumienie listy jest o wiele łatwiejsze do odczytania, i nie musisz się martwić, że kolejność zostanie pomieszana, a duplikaty usunięte podobnie.

W rzeczywistości mogę zrobić kolejny krok „wstecz” i po prostu użyć prostej pętli for:

images, anims = [], []

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

Zrozumienie lub użycie listy set()jest w porządku, dopóki nie musisz dodać innego czeku lub innej logiki - powiedz, że chcesz usunąć wszystkie 0-bajtowe pliki jpeg, po prostu dodajesz coś takiego ...

if f[1] == 0:
    continue
dbr
źródło
44
Czy nie istnieje sposób na zrozumienie listy bez konieczności dwukrotnego przeglądania listy?
balki
35
Problem polega na tym, że narusza to zasadę OSUSZANIA. Byłoby miło, gdyby był na to lepszy sposób.
Antymon
21
Gdy wzrośnie apetyt na funkcjonalne programowanie (Haskell) lub funkcjonalny styl (LINQ), zaczynamy wąchać Pythona ze względu na jego wiek [x for x in blah if ...]- gadatliwy, lambdajest niezdarny i ograniczony ... To jest jak jazda najfajniejszym samochodem od 1995 roku. Nie to samo co wtedy.
Tomasz Gandor
6
@TomaszGandor FTR, Haskell jest starszy od Pythona (i faktycznie wpłynął na jego projekt). Wydaje mi się, że składnia służąca do rozumienia list i lambdów została celowo utrzymana nieco po gadatliwie, być może po to, by zniechęcić do ich nadmiernego używania. Co jest w rzeczywistości trochę ryzykowne ... tak bardzo jak Haskell, rozumiem, dlaczego wiele osób uważa Python za bardziej czytelny.
leftaroundabout
4
prosta pętla dla to najlepszy sposób na zrobienie tego ... pojedyncza pętla, bardzo przejrzysta i czytelna
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
źródło
14
To niesamowicie genialne! Jednak zajęło mi trochę czasu, aby zrozumieć, co się dzieje. Chciałbym wiedzieć, czy inni uważają, że można to uznać za kod czytelny, czy nie.
jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)jest bardziej czytelny.
dansalmo
21
@dansalmo Zwłaszcza, że ​​możesz uczynić z niego jedno-liniową linię, a jeśli chcesz dołączyć coś bardziej skomplikowanego niż x, możesz zrobić to w jednym append:for x in mylist: (good if isgood(x) else bad).append(x)
yo
2
@MLister, w takim przypadku prawdopodobnie powinieneś dołączyć wyszukiwanie atrybutów(bad.append, good.append)
John La Rooy,
11
Nieco krótsza odmiana:(good if x in goodvals else bad).append(x)
Pi Delport
104

Oto leniwe podejście do iteratora:

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)

Ocenia stan raz na sztukę i zwraca dwa generatory, z których pierwsze dają wartości z sekwencji, w której warunek jest prawdziwy, a drugi w przypadku fałszu.

Ponieważ jest leniwy, możesz go używać na dowolnym iteratorze, nawet na nieskończoność:

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

Zazwyczaj jednak podejście polegające na powrocie listy nieluzowanej jest lepsze:

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

Edycja: W przypadku bardziej szczegółowego zastosowania dzielenia elementów na różne listy według jakiegoś klucza, oto ogólna funkcja, która to robi:

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

Stosowanie:

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']
Mrówka osocza
źródło
Prawdopodobnie masz rację, że narusza to zasadę YAGNI. Opiera się na założeniu, że w przyszłości wzrośnie liczba różnych list, na które można podzielić rzeczy.
Ants Aasma
17
Może to być dużo kodu, ale jeśli [ x for x in my_list if ExpensiveOperation(x) ]zajmuje dużo czasu, na pewno nie chcesz tego zrobić dwa razy!
dash-tom-bang
1
+1 za oferowanie wielu odmian, w tym opartych na iteratorze i konkretnego rozwiązania „in X”. OP w „goodvals” może być mały, ale zastąpienie go bardzo dużym słownikiem lub kosztownym orzeczeniem może być kosztowne. Zmniejsza również potrzebę dwukrotnego pisania listy ze wszystkich miejsc, w których jest to potrzebne, zmniejszając w ten sposób prawdopodobieństwo wprowadzenia literówek / błędów użytkownika. Niezłe rozwiązanie. Dzięki!
cod3monk3y
3
Zauważ, że teeprzechowuje wszystkie wartości między iteratorami, które zwraca, więc tak naprawdę nie zaoszczędzi pamięci, jeśli zapętlisz jeden cały generator, a następnie drugi.
John La Rooy,
25

Problem ze wszystkimi proponowanymi rozwiązaniami polega na tym, że przeskanuje i zastosuje dwukrotnie funkcję filtrowania. Zrobiłbym prostą małą funkcję taką jak ta:

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

W ten sposób nie przetwarzasz niczego dwukrotnie, a także nie powtarzasz kodu.

kręcony
źródło
Zgadzam się. Szukałem „eleganckiego” (tj. Tutaj oznaczającego krótki i wbudowany / niejawny) sposobu na zrobienie tego bez dwukrotnego skanowania listy, ale wydaje się, że jest to właściwa droga. Oczywiście i tak miałoby to znaczenie tylko w przypadku dużych ilości danych.
Matthew Flaschen
IMHO, jeśli znasz sposób na zrobienie tego przy mniejszym zużyciu procesora (a tym samym mniejszym zużyciu energii), nie ma powodu, aby go nie używać.
winden
2
@winden ... Przeniesienie całego mojego Pythona do C.;)
Elliot Cameron
19

Moje zdanie na ten temat. Proponuję leniwą partitionfunkcję jednoprzebiegową , która zachowuje względną kolejność w podsekwencjach wyjściowych.

1. Wymagania

Zakładam, że wymagania są następujące:

  • utrzymuj względną kolejność elementów (stąd brak zbiorów i słowników)
  • oceniaj warunek tylko raz dla każdego elementu (stąd nie używaj ( i) filterlub groupby)
  • zezwalaj na leniwe zużycie dowolnej sekwencji (jeśli możemy sobie pozwolić na ich wstępne obliczenie, to naiwna implementacja prawdopodobnie również będzie możliwa do zaakceptowania)

2. splitbiblioteka

Moja partitionfunkcja (przedstawiona poniżej) i inne podobne funkcje przekształciły ją w małą bibliotekę:

Można go normalnie zainstalować za pomocą PyPI:

pip install --user split

Aby podzielić listę na podstawie, użyj partitionfunkcji:

>>> 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. partitionfunkcja wyjaśniona

Wewnętrznie musimy zbudować dwa podsekwencje jednocześnie, więc użycie tylko jednej sekwencji wyjściowej zmusi drugą do obliczenia. I musimy zachować stan między żądaniami użytkowników (elementy przetworzone, ale jeszcze nie zamówione). Aby utrzymać stan, używam dwóch podwójnie zakończonych kolejek ( deques):

from collections import deque

SplitSeq klasa zajmuje się sprzątaniem:

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

Magia dzieje się w jej .getNext()metodzie. Jest prawie jak .next() z iteratorów, ale pozwala określić, jakiego rodzaju elementu chcemy tym razem. Za sceną nie odrzuca odrzuconych elementów, ale umieszcza je w jednej z dwóch kolejek:

    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)

Użytkownik końcowy powinien korzystać z partitionfunkcji. Pobiera funkcję warunku i sekwencję (podobnie jak maplub filter) i zwraca dwa generatory. Pierwszy generator buduje podsekwencję elementów, dla których warunek się utrzymuje, drugi generuje podsekwencję uzupełniającą. Iteratory i generatory pozwalają na leniwe dzielenie nawet długich lub nieskończonych sekwencji.

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

Wybrałem funkcję testową jako pierwszy argument ułatwiający częściowe zastosowanie w przyszłości (podobnie jak mapi filter mam funkcję testową jako pierwszy argument).

sastanina
źródło
15

Zasadniczo podoba mi się podejście Andersa, ponieważ jest ono bardzo ogólne. Oto wersja, która stawia klasyfikator na pierwszym miejscu (aby dopasować składnię filtra) i używa defaultdict (zakładając, że został zaimportowany).

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
źródło
Miałem zamiar wybrać stwierdzenia Zen z Pythona, które mają tu zastosowanie, ale jest ich zbyt wiele, by je komentować. =) Niesamowity kawałek kodu.
jpmc26
13

Pierwsze przejście (edycja przed OP): Użyj zestawów:

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]

Jest to dobre zarówno dla czytelności (IMHO), jak i wydajności.

Drugie przejście (edycja post-OP):

Utwórz listę dobrych rozszerzeń jako zestaw:

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

i to zwiększy wydajność. W przeciwnym razie to, co masz, wygląda dla mnie dobrze.

RichieHindle
źródło
4
nie najlepsze rozwiązanie, jeśli listy były w pewnej kolejności przed podzieleniem i potrzebujesz, aby pozostały w tej kolejności.
Daniyar
8
Czy to nie usunie duplikatów?
mavnn
Tworzenie zestawu to O (n log n). Dwukrotne powtórzenie listy to O (n). Ustalone rozwiązanie może być bardziej eleganckie (jeśli jest poprawne), ale z pewnością jest wolniejsze, gdy n wzrasta.
dash-tom-bang
1
@ dash-tom-bang Iteracja listy to O (n * n). Jest tak, ponieważ każdy element na liście może wymagać porównania z każdym elementem w goodvals.
ChaimG
@ChaimG dobra uwaga, chociaż musimy również wziąć pod uwagę koszt operacji przecięcia i różnicy (których nie znam od ręki, ale jestem pewien, że są one również superliniowe).
dash-tom-bang
10

itertools.groupby prawie robi to, co chcesz, z wyjątkiem tego, że wymaga sortowania elementów, aby upewnić się, że otrzymasz pojedynczy ciągły zakres, więc najpierw musisz posortować według klucza (w przeciwnym razie otrzymasz wiele przeplecionych grup dla każdego typu). na przykład.

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)

daje:

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

Podobnie jak w przypadku innych rozwiązań, kluczową funkcję można zdefiniować, aby podzielić ją na dowolną liczbę grup.

Brian
źródło
6
good.append(x) if x in goodvals else bad.append(x)

Ta elegancka i zwięzła odpowiedź @dansalmo pojawiła się w komentarzach, więc po prostu ponownie ją tutaj zamieszczam jako odpowiedź, aby zyskała na znaczeniu, na jakie zasługuje, szczególnie dla nowych czytelników.

Kompletny przykład:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)
John D.
źródło
5

Jeśli chcesz zrobić to w stylu FP:

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

Nie jest to najbardziej czytelne rozwiązanie, ale przynajmniej raz przechodzi przez listę odtwarzania.

Michau
źródło
1
Chociaż iteruje listę tylko jeden raz, wydajność nie jest tak dobra, ponieważ lista dołącza się. Dołączanie do listy jest potencjalnie kosztowną operacją (na przykład w porównaniu z deque.append). W rzeczywistości to rozwiązanie jest bardzo wolne w porównaniu z innymi rozwiązaniami tutaj (21,4 s na 100 000 losowych liczb całkowitych i testowanie ich wartości).
rlat
5

Osobiście podoba mi się wersja, którą zacytowałeś, zakładając, że masz już listę goodvalsznajomych. Jeśli nie, coś takiego:

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

Oczywiście jest to bardzo podobne do korzystania ze zrozumienia listy, tak jak pierwotnie, ale z funkcją zamiast wyszukiwania:

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

Ogólnie uważam, że estetyka listowego zrozumienia jest bardzo przyjemna. Oczywiście, jeśli tak naprawdę nie musisz zachowywać porządku i nie potrzebujesz duplikatów, użycie metod intersectioni differencena zestawach również działałoby dobrze.

BJ Homer
źródło
Oczywiście filter(lambda x: is_good(x), mylist)można zredukować dofilter(is_good, mylist)
robru
dodanie wywołania funkcji dodatkowej faktycznie podwaja (!) czas wykonania w porównaniu ze zrozumieniem listy, z tego co widziałem w profilowaniu. przez większość czasu trudno pokonać zrozumienie listy.
Corley Brigman
4

Myślę, że uogólnienie podziału iterowalnego na podstawie warunków N jest przydatne

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

Na przykład:

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]

Jeśli element może spełniać wiele warunków, usuń przerwanie.

Gekon
źródło
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)

Sprawdź to

Hanfei Sun
źródło
3

Czasami wygląda na to, że zrozumienie listy nie jest najlepszym rozwiązaniem!

Zrobiłem mały test oparty na odpowiedzi udzielonej przez ludzi na ten temat, przetestowany na losowo wygenerowanej liście. Oto generowanie listy (prawdopodobnie jest to lepszy sposób, ale nie o to chodzi):

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

I zaczynamy

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

Korzystając z funkcji cmpthese , najlepszym wynikiem jest odpowiedź dbr:

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
źródło
Szybsze funkcje dzięki zaktualizowanym testom porównawczym tutaj .
ChaimG
2

Jeszcze inne rozwiązanie tego problemu. Potrzebowałem rozwiązania tak szybko, jak to możliwe. Oznacza to tylko jedną iterację na liście i najlepiej O (1) w celu dodania danych do jednej z powstałych list. Jest to bardzo podobne do rozwiązania dostarczanego przez sastaninę , z wyjątkiem znacznie krótszego:

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

Następnie możesz użyć tej funkcji w następujący sposób:

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

Jeśli nie jesteś w porządku z powstałego dequeobiektu można łatwo przekształcić go list, setcokolwiek lubisz (na przykład list(lower)). Konwersja jest znacznie szybsza, niż bezpośrednia konstrukcja list.

Ta metoda utrzymuje porządek elementów, a także wszelkich duplikatów.

rlat
źródło
2

Na przykład dzielenie listy przez parzyste i nieparzyste

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

Lub ogólnie:

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

Zalety:

  • Najkrótsza możliwa droga
  • Predykat stosuje się tylko raz dla każdego elementu

Niedogodności

  • Wymaga znajomości paradygmatu programowania funkcjonalnego
Pavel Ilchenko
źródło
2

Zainspirowani świetną (ale zwięzłą) odpowiedzią @ gnibbler , możemy zastosować to podejście do mapowania do wielu partycji:

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

Następnie splittermożna go użyć w następujący sposób:

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

Działa to na więcej niż dwóch partycjach z bardziej skomplikowanym mapowaniem (i na iteratorach):

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

Lub używając słownika do mapowania:

>>> 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
źródło
... właśnie zauważyłem, że jest to w zasadzie to samo, co @ alan-isaac już odpowiedział.
Josh Bode
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

dołącz zwraca zwraca Brak, więc działa.

Biga
źródło
1

Aby uzyskać występ, spróbuj itertools.

Itertools moduł standaryzuje podstawowy zestaw szybkie, wydajne pamięci narzędzia, które są przydatne, same lub w połączeniu. Razem tworzą „algebrę iteratora”, która umożliwia zwięzłe i wydajne konstruowanie specjalistycznych narzędzi w czystym języku Python.

Zobacz itertools.ifilter lub imap.

itertools.ifilter (predykat, iterowalny)

Stwórz iterator, który filtruje elementy z iterowalnego zwracając tylko te, dla których predykat ma wartość True

gimel
źródło
ifilter / imap (i generatory w ogóle) są dość powolne ... ogólnie, w moim profilowaniu, jeśli weźmiesz rozumienie listy jak [x for x in a if x > 50000]na prostej tablicy 100000 liczb całkowitych (przez random.shuffle), filter(lambda x: x> 50000, a)zajmie dwa razy więcej, ifilter(lambda x: x> 50000, a); list(result)zajmuje około 2,3x tak długo. Dziwne ale prawdziwe.
Corley Brigman
1

Czasami nie potrzebujesz drugiej połowy listy. Na przykład:

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
źródło
1

To najszybszy sposób.

Wykorzystuje if else(jak odpowiedź dbr), ale najpierw tworzy zestaw. Zestaw zmniejsza liczbę operacji z O (m * n) do O (log m) + O (n), co powoduje zwiększenie prędkości o 45%.

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)

Trochę krótszy:

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)

Wyniki testu porównawczego:

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

Pełny kod testu porównawczego dla Python 3.7 (zmodyfikowany z 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
źródło
0

Jeśli nalegasz na spryt, możesz wziąć rozwiązanie Windena i nieco fałszywą spryt:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
źródło
3
„D lub {}” jest nieco niebezpieczne. Jeśli zostanie przekazany pusty dykt, nie zostanie on zmutowany na miejscu.
Brian
To prawda, ale jest zwracany, więc ... Właściwie jest to idealny przykład tego, dlaczego nie chcesz dodawać bardziej sprytnego kodu. :-P
Anders Eurenius
0

Jest tu już sporo rozwiązań, ale jeszcze jednym sposobem na to byłoby -

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

Iteruje po liście tylko raz i wygląda nieco bardziej pytonicznie i dlatego jest dla mnie czytelny.

>>> 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
źródło
0

Przyjąłbym podejście dwuprzebiegowe, oddzielając ocenę predykatu od filtrowania listy:

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

Co jest miłego w tym, pod względem wydajności (oprócz oceny predtylko raz na każdym elemencie iterable), to to, że przenosi wiele logiki z interpretera do wysoce zoptymalizowanego kodu iteracji i mapowania. Może to przyspieszyć iterację w przypadku długich iteracji, jak opisano w tej odpowiedzi .

Pod względem ekspresyjności wykorzystuje ekspresyjne idiomy, takie jak rozumienie i mapowanie.

Jim Witschey
źródło
0

rozwiązanie

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

test

[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
źródło
0

Jeśli nie masz nic przeciwko użyciu tam zewnętrznej biblioteki, wiem, że natywnie wykonują tę operację:

>>> 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
źródło
0

Nie jestem pewien, czy jest to dobre podejście, ale można to zrobić również w ten sposób

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
źródło
0

Jeśli lista składa się z grup i przerywanych separatorów, możesz użyć:

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

Stosowanie:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
źródło
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]

Miło, gdy warunek jest dłuższy, na przykład w twoim przykładzie. Czytelnik nie musi rozpoznawać warunku ujemnego i tego, czy uwzględnia wszystkie pozostałe przypadki.

Chrisjan
źródło
0

Jeszcze jedna odpowiedź, krótka, ale „zła” (dla efektów ubocznych związanych ze zrozumieniem listy).

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
źródło