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]
str.split()
, aby podzielić ją na uporządkowany zbiór kolejnych podlist. Npsplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
. W przeciwieństwie do dzielenia elementów listy według kategorii.IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n (1) zamiast n (o / 2), praktycznie bez różnicy w czytelności.Odpowiedzi:
Ten kod jest doskonale czytelny i wyjątkowo przejrzysty!
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:
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 ...źródło
[x for x in blah if ...]
- gadatliwy,lambda
jest niezdarny i ograniczony ... To jest jak jazda najfajniejszym samochodem od 1995 roku. Nie to samo co wtedy.źródło
good.append(x) if x in goodvals else bad.append(x)
jest bardziej czytelny.x
, możesz zrobić to w jednymappend
: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)
Oto leniwe podejście do iteratora:
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ść:
Zazwyczaj jednak podejście polegające na powrocie listy nieluzowanej jest lepsze:
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:
Stosowanie:
źródło
[ x for x in my_list if ExpensiveOperation(x) ]
zajmuje dużo czasu, na pewno nie chcesz tego zrobić dwa razy!tee
przechowuje 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.Problem ze wszystkimi proponowanymi rozwiązaniami polega na tym, że przeskanuje i zastosuje dwukrotnie funkcję filtrowania. Zrobiłbym prostą małą funkcję taką jak ta:
W ten sposób nie przetwarzasz niczego dwukrotnie, a także nie powtarzasz kodu.
źródło
Moje zdanie na ten temat. Proponuję leniwą
partition
funkcję jednoprzebiegową , która zachowuje względną kolejność w podsekwencjach wyjściowych.1. Wymagania
Zakładam, że wymagania są następujące:
i
)filter
lubgroupby
)2.
split
bibliotekaMoja
partition
funkcja (przedstawiona poniżej) i inne podobne funkcje przekształciły ją w małą bibliotekę:Można go normalnie zainstalować za pomocą PyPI:
Aby podzielić listę na podstawie, użyj
partition
funkcji:3.
partition
funkcja wyjaśnionaWewnę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
):SplitSeq
klasa zajmuje się sprzątaniem: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:Użytkownik końcowy powinien korzystać z
partition
funkcji. Pobiera funkcję warunku i sekwencję (podobnie jakmap
lubfilter
) 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.Wybrałem funkcję testową jako pierwszy argument ułatwiający częściowe zastosowanie w przyszłości (podobnie jak
map
ifilter
mam funkcję testową jako pierwszy argument).źródło
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).
źródło
Pierwsze przejście (edycja przed OP): Użyj zestawów:
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:
i to zwiększy wydajność. W przeciwnym razie to, co masz, wygląda dla mnie dobrze.
źródło
goodvals
.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.
daje:
Podobnie jak w przypadku innych rozwiązań, kluczową funkcję można zdefiniować, aby podzielić ją na dowolną liczbę grup.
źródło
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:
źródło
Jeśli chcesz zrobić to w stylu FP:
Nie jest to najbardziej czytelne rozwiązanie, ale przynajmniej raz przechodzi przez listę odtwarzania.
źródło
Osobiście podoba mi się wersja, którą zacytowałeś, zakładając, że masz już listę
goodvals
znajomych. Jeśli nie, coś takiego:Oczywiście jest to bardzo podobne do korzystania ze zrozumienia listy, tak jak pierwotnie, ale z funkcją zamiast wyszukiwania:
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
intersection
idifference
na zestawach również działałoby dobrze.źródło
filter(lambda x: is_good(x), mylist)
można zredukować dofilter(is_good, mylist)
Myślę, że uogólnienie podziału iterowalnego na podstawie warunków N jest przydatne
Na przykład:
Jeśli element może spełniać wiele warunków, usuń przerwanie.
źródło
Sprawdź to
źródło
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):
I zaczynamy
Korzystając z funkcji cmpthese , najlepszym wynikiem jest odpowiedź dbr:
źródło
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:
Następnie możesz użyć tej funkcji w następujący sposób:
Jeśli nie jesteś w porządku z powstałego
deque
obiektu można łatwo przekształcić golist
,set
cokolwiek lubisz (na przykładlist(lower)
). Konwersja jest znacznie szybsza, niż bezpośrednia konstrukcja list.Ta metoda utrzymuje porządek elementów, a także wszelkich duplikatów.
źródło
Na przykład dzielenie listy przez parzyste i nieparzyste
Lub ogólnie:
Zalety:
Niedogodności
źródło
Zainspirowani świetną (ale zwięzłą) odpowiedzią @ gnibbler , możemy zastosować to podejście do mapowania do wielu partycji:
Następnie
splitter
można go użyć w następujący sposób:Działa to na więcej niż dwóch partycjach z bardziej skomplikowanym mapowaniem (i na iteratorach):
Lub używając słownika do mapowania:
źródło
dołącz zwraca zwraca Brak, więc działa.
źródło
Aby uzyskać występ, spróbuj
itertools
.Zobacz itertools.ifilter lub imap.
źródło
[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.Czasami nie potrzebujesz drugiej połowy listy. Na przykład:
źródło
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%.Trochę krótszy:
Wyniki testu porównawczego:
Pełny kod testu porównawczego dla Python 3.7 (zmodyfikowany z FunkySayu):
źródło
Jeśli nalegasz na spryt, możesz wziąć rozwiązanie Windena i nieco fałszywą spryt:
źródło
Jest tu już sporo rozwiązań, ale jeszcze jednym sposobem na to byłoby -
Iteruje po liście tylko raz i wygląda nieco bardziej pytonicznie i dlatego jest dla mnie czytelny.
źródło
Przyjąłbym podejście dwuprzebiegowe, oddzielając ocenę predykatu od filtrowania listy:
Co jest miłego w tym, pod względem wydajności (oprócz oceny
pred
tylko raz na każdym elemencieiterable
), 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.
źródło
rozwiązanie
test
źródło
Jeśli nie masz nic przeciwko użyciu tam zewnętrznej biblioteki, wiem, że natywnie wykonują tę operację:
iteration_utilities.partition
:more_itertools.partition
źródło
Nie jestem pewien, czy jest to dobre podejście, ale można to zrobić również w ten sposób
źródło
Jeśli lista składa się z grup i przerywanych separatorów, możesz użyć:
Stosowanie:
źródło
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.
źródło
Jeszcze jedna odpowiedź, krótka, ale „zła” (dla efektów ubocznych związanych ze zrozumieniem listy).
źródło