Mam skrypt w języku Python, który przyjmuje jako dane wejściowe listę liczb całkowitych, które muszę obsługiwać jednocześnie z czterema liczbami całkowitymi. Niestety nie mam kontroli nad danymi wejściowymi lub przekazałbym je jako listę krotek czteroelementowych. Obecnie powtarzam to w ten sposób:
for i in xrange(0, len(ints), 4):
# dummy op for example code
foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]
Wygląda jednak podobnie do „C-think”, co sprawia, że podejrzewam, że istnieje bardziej pythonowy sposób radzenia sobie z tą sytuacją. Lista jest odrzucana po iteracji, więc nie trzeba jej zachowywać. Być może coś takiego byłoby lepsze?
while ints:
foo += ints[0] * ints[1] + ints[2] * ints[3]
ints[0:4] = []
Nadal jednak nie „wydaje się” właściwe. : - /
Powiązane pytanie: Jak podzielić listę na kawałki o jednakowej wielkości w Pythonie?
Odpowiedzi:
Zmodyfikowano z sekcji przepisów w dokumentacji itertools Pythona :
Przykład
W pseudokodzie, aby zachować przykładowy zwięzły.
Uwaga: w Pythonie 2 użyj
izip_longest
zamiastzip_longest
.źródło
izip_longest
otrzyma 256k argumentów.None
wypełniania ostatniego kawałka?Prosty. Łatwy. Szybki. Działa z dowolną sekwencją:
źródło
itertools
modułem.chunker
zwraca agenerator
. Zamień powrót na:return [...]
aby uzyskać listę.yield
:for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]
. Nie jestem pewien, czy wewnętrznie byłoby to potraktowane inaczej w jakimkolwiek istotnym aspekcie, ale może być nawet odrobinę jaśniejsze.__getitem__
metody.Jestem fanem
źródło
chunk
będzie miała 1, 2 lub 3 elementy dla ostatniej partii elementów. Zobacz pytanie dotyczące tego, dlaczego wskaźniki wycinków mogą być poza zakresem .Inny sposób:
źródło
size
, co jest czasem pożądane.len
połączenia, więc nie działają na innych generatorach.źródło
izip_longest
jest zastąpiony przezzip_longest
Idealne rozwiązanie tego problemu działa z iteratorami (nie tylko sekwencjami). Powinno być również szybkie.
Oto rozwiązanie dostarczone przez dokumentację dla itertools:
Używając ipython
%timeit
na moim Mac Book Air, otrzymuję 47,5 us na pętlę.Jednak to naprawdę nie działa dla mnie, ponieważ wyniki są wypełnione grupami o równej wielkości. Rozwiązanie bez wypełnienia jest nieco bardziej skomplikowane. Najbardziej naiwnym rozwiązaniem może być:
Proste, ale dość wolne: 693 us na pętlę
Najlepsze rozwiązanie, jakie mogłem wymyślić z wykorzystaniem
islice
pętli wewnętrznej:Przy tym samym zestawie danych otrzymuję 305 us na pętlę.
Nie jestem w stanie uzyskać czystego rozwiązania w jakikolwiek sposób, dlatego przedstawiam następujące rozwiązanie z ważnym zastrzeżeniem: Jeśli twoje dane wejściowe zawierają w sobie instancje
filldata
, możesz uzyskać błędną odpowiedź.Naprawdę nie lubię tej odpowiedzi, ale jest ona znacznie szybsza. 124 us na pętlę
źródło
itertools
importu;map
musi być Py3map
lubimap
)def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n)))))
. Twoja ostatnia funkcja może być mniej krucha za pomocą wartownika: pozbyć sięfillvalue
argumentu; dodaj pierwszy wierszfillvalue = object()
, a następnie zmień poleif
wyboruif i[-1] is fillvalue:
i linię, którą kontrolujeyield tuple(v for v in i if v is not fillvalue)
. Gwarantuje, że żadnej wartości nieiterable
można pomylić z wartością wypełniacza.islice
obiektów (# 3 wygrywa, jeślin
jest stosunkowo duży, np. Liczba grup jest niewielka, ale optymalizuje się w rzadkich przypadkach), ale nie spodziewałem się, że będzie to całkiem skrajny.izip_longest
nad ostatecznym krotki:yield i[:modulo]
. Ponadto, dlaargs
zmiennej, krotka go zamiast listy:args = (iter(iterable),) * n
. Goli kilka kolejnych cykli zegara. Wreszcie, jeśli zignorujemy wartość wypełnienia i założymyNone
, warunek może stać sięif None in i
dla jeszcze większej liczby cykli zegara.yield
), podczas gdy zwykły przypadek pozostaje niezmieniony.Potrzebowałem rozwiązania, które działałoby również z zestawami i generatorami. Nie mogłem wymyślić niczego bardzo krótkiego i ładnego, ale przynajmniej jest to dość czytelne.
Lista:
Zestaw:
Generator:
źródło
Podobnie jak inne propozycje, ale nie do końca identyczne, lubię to robić w ten sposób, ponieważ jest prosty i łatwy do odczytania:
W ten sposób nie dostaniesz ostatniej częściowej części. Jeśli chcesz dostać się
(9, None, None, None)
jako ostatni kawałek, po prostu użyjizip_longest
zitertools
.źródło
zip(*([it]*4))
Jeśli nie masz nic przeciwko użyciu zewnętrznego pakietu, możesz użyć
iteration_utilities.grouper
od 1 . Obsługuje wszystkie iterowalne (nie tylko sekwencje):iteration_utilties
który drukuje:
Jeśli długość nie jest wielokrotnością rozmiaru grupy, obsługuje także wypełnianie (niekompletna ostatnia grupa) lub obcinanie (odrzucanie niekompletnej ostatniej grupy) ostatniej:
Benchmarki
Postanowiłem także porównać czas działania kilku wymienionych podejść. Jest to wykres dziennika-dziennika grupujący w grupy elementów „10” na podstawie listy o różnej wielkości. Dla wyników jakościowych: Niższy oznacza szybszy:
Przynajmniej w tym teście
iteration_utilities.grouper
działa najlepiej. Następnie następuje podejście Craza .Benchmark został stworzony przy użyciu 1 . Kod użyty do uruchomienia tego testu porównawczego to:
simple_benchmark
1 Zastrzeżenie: Jestem autorem bibliotek
iteration_utilities
isimple_benchmark
.źródło
Ponieważ nikt o tym nie wspomniał, oto
zip()
rozwiązanie:Działa tylko wtedy, gdy długość sekwencji jest zawsze podzielna przez rozmiar porcji lub jeśli nie obchodzi cię końcowy fragment, jeśli tak nie jest.
Przykład:
Lub użyj itertools.izip, aby zwrócić iterator zamiast listy:
Wypełnienie można naprawić za pomocą odpowiedzi @ ΤΖΩΤΖΙΟΥ :
źródło
Użycie map () zamiast zip () rozwiązuje problem dopełniania w odpowiedzi JF Sebastiana:
Przykład:
źródło
itertools.izip_longest
(Py2) /itertools.zip_longest
(Py3); to użyciemap
jest podwójnie przestarzałe i niedostępne w Py3 (nie można przejśćNone
jako funkcja odwzorowująca, i zatrzymuje się, gdy wyczerpana zostanie najkrótsza iteracja, a nie najdłuższa; nie padnie).Innym podejściem byłoby użycie dwuargumentowej formy
iter
:Można to łatwo dostosować do użycia paddingu (jest to podobne do odpowiedzi Markusa Jarderota ):
Można je nawet połączyć w celu opcjonalnego wypełnienia:
źródło
Jeśli lista jest duża, najskuteczniejszym sposobem na to będzie użycie generatora:
źródło
iterable = range(100000000)
ichunksize
do 10000.Używanie małych funkcji i rzeczy tak naprawdę nie podoba mi się; Wolę po prostu użyć plasterków:
źródło
len
. możesz zrobić test za pomocąitertools.repeat
lubitertools.cycle
.[...for...]
listowych fizycznie zbudować listę zamiast użycia(...for...)
wyrażenia generatora , które po prostu dbają o kolejnym elemencie i pamięci wolnymAby uniknąć wszystkich konwersji do listy
import itertools
i:Produkuje:
sprawdziłem
groupby
i nie można przekonwertować na listę ani użyćlen
więc (sądzę) opóźni to rozdzielczość każdej wartości, dopóki nie zostanie faktycznie użyta. Niestety, żadna z dostępnych odpowiedzi (w tym czasie) nie oferowała tej odmiany.Oczywiście, jeśli chcesz obsłużyć każdy element z kolei, zagnieżdż pętlę for nad g:
Moim szczególnym zainteresowaniem była potrzeba użycia generatora, aby przesłać zmiany w partiach do 1000 do interfejsu API Gmaila:
źródło
groupby(messages, lambda x: x/3)
dałby ci TypeError (za próbę podzielenia ciągu przez liczbę całkowitą), a nie 3-literowe grupowanie. Jeśli tak,groupby(enumerate(messages), lambda x: x[0]/3)
możesz coś mieć. Ale nie powiedziałeś tego w swoim poście.Z NumPy jest to proste:
wynik:
źródło
źródło
O ile mi czegoś nie brakuje, nie wspomniano o prostym rozwiązaniu z wyrażeniami generatora. Zakłada, że zarówno rozmiar, jak i liczba porcji są znane (co często ma miejsce) i że nie jest wymagane wypełnianie:
źródło
W drugiej metodzie przejdę do następnej grupy 4, wykonując następujące czynności:
Nie wykonałem jednak żadnego pomiaru wydajności, więc nie wiem, który z nich może być bardziej wydajny.
Powiedziawszy to, zwykle wybrałbym pierwszą metodę. To nie jest ładne, ale często jest to konsekwencja kontaktu ze światem zewnętrznym.
źródło
Kolejna odpowiedź, której zaletami są:
1) Łatwo zrozumiałe
2) Działa na dowolnych iterowalnych, nie tylko sekwencjach (niektóre z powyższych odpowiedzi dławią się na uchwytach plików)
3) Nie ładuje porcji do pamięci naraz
4) Nie tworzy obszernej listy odniesień do ten sam iterator w pamięci
5) Brak dopełniania wartości wypełnienia na końcu listy
To powiedziawszy, nie zaplanowałem tego, więc może być wolniejsze niż niektóre bardziej sprytne metody, a niektóre zalety mogą być nieistotne w przypadku użycia.
Aktualizacja:
Kilka wad związanych z faktem, że wewnętrzne i zewnętrzne pętle pobierają wartości z tego samego iteratora:
1) kontynuacja nie działa zgodnie z oczekiwaniami w zewnętrznej pętli - po prostu przechodzi do następnego elementu zamiast pomijania fragmentu . Nie wydaje się to jednak problemem, ponieważ nie ma nic do przetestowania w zewnętrznej pętli.
2) przerwa nie działa zgodnie z oczekiwaniami w wewnętrznej pętli - kontrola ponownie skończy w wewnętrznej pętli z kolejnym elementem w iteratorze. Aby pominąć całe fragmenty, albo zawiń wewnętrzny iterator (ii powyżej) w krotkę, np.
for c in tuple(ii)
, Lub ustaw flagę i wyczerpaj iterator.źródło
źródło
Możesz użyć funkcji partycji lub porcji z biblioteki funcy :
Funkcje te mają również wersje iteratora
ipartition
iichunks
, które w tym przypadku będą bardziej wydajne.Możesz także zerknąć na ich implementację .
źródło
O rozwiązaniu podanym
J.F. Sebastian
tutaj :Jest sprytny, ale ma jedną wadę - zawsze wraca krotka. Jak zamiast tego uzyskać ciąg?
Oczywiście, że możesz pisać
''.join(chunker(...))
, ale tymczasowa krotka i tak jest skonstruowana.Możesz pozbyć się tymczasowej krotki, pisząc własną
zip
, w ten sposób:Następnie
Przykładowe użycie:
źródło
zip
zamiast korzystania z istniejącego nie wydaje się najlepszym pomysłem.Lubię to podejście. Jest prosty i nie magiczny, obsługuje wszystkie typy iteracyjne i nie wymaga importu.
źródło
Nigdy nie chcę, aby moje kawałki były wyściełane, więc ten wymóg jest niezbędny. Uważam, że umiejętność pracy nad dowolnym iterowalnym jest również wymagana. Biorąc to pod uwagę, zdecydowałem się rozszerzyć na zaakceptowaną odpowiedź, https://stackoverflow.com/a/434411/1074659 .
W przypadku tego podejścia wydajność jest niewielka, jeśli wypełnienie nie jest pożądane ze względu na konieczność porównania i filtrowania wartości wypełnienia. Jednak w przypadku dużych porcji narzędzie to jest bardzo wydajne.
źródło
Oto porcja bez importu, która obsługuje generatory:
Przykład zastosowania:
źródło
W Pythonie 3.8 możesz używać operatora morsa i
itertools.islice
.źródło
Wydaje się, że nie ma na to ładnego sposobu. Oto strona z wieloma metodami, w tym:
źródło
Jeśli listy mają ten sam rozmiar, możesz połączyć je w listy 4-krotne z
zip()
. Na przykład:Oto, co
zip()
wytwarza funkcja:Jeśli listy są duże i nie chcesz ich łączyć w większą listę, użyj opcji
itertools.izip()
, która tworzy iterator, a nie listę.źródło
Jednoliniowe, adhoc rozwiązanie iteracji po liście
x
w kawałkach wielkości4
-źródło