Jaki jest najbardziej „pytonowy” sposób na iterację po liście w częściach?

487

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?

Ben Blank
źródło
3
Twój kod nie działa, jeśli rozmiar listy nie jest wielokrotnością czterech.
Pedro Henriques,
5
Rozszerzam () listę, aby jej długość była wielokrotnością czterech, zanim dotrze tak daleko.
Ben Blank
4
@ ΤΖΩΤΖΙΟΥ - pytania są bardzo podobne, ale nie do końca powtarzają się. Jest „podzielony na dowolną liczbę kawałków o rozmiarze N” vs. „podzielony na N kawałków o dowolnym rozmiarze”. :-)
Ben Blank

Odpowiedzi:

339

Zmodyfikowano z sekcji przepisów w dokumentacji itertools Pythona :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Przykład
W pseudokodzie, aby zachować przykładowy zwięzły.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Uwaga: w Pythonie 2 użyj izip_longestzamiast zip_longest.

Craz
źródło
67
W końcu miałem okazję się z tym bawić w sesji Pythona. Dla tych, którzy są tak zdezorientowani jak ja, zasila to ten sam iterator izip_longest wiele razy, powodując, że zużywa on kolejne wartości tej samej sekwencji zamiast wartości rozłożonych z oddzielnych sekwencji. Kocham to!
Ben Blank
6
Jaki jest najlepszy sposób na odfiltrowanie wartości wypełnienia? ([pozycja dla pozycji w przedmiotach, jeśli pozycja nie jest wartością wypełnienia] dla pozycji w grupie (iterowalna))?
gotgenes
14
Podejrzewam, że wydajność tego przepisu dla grup wielkości 256k będzie bardzo słaba, ponieważ izip_longestotrzyma 256k argumentów.
anatoly techtonik
13
W kilku miejscach komentatorzy mówią „kiedy w końcu doszedłem do tego, jak to działa…” Może potrzebne jest trochę wyjaśnienia. W szczególności lista aspektów iteratorów.
LondonRob
6
Czy istnieje sposób, aby tego użyć, ale bez Nonewypełniania ostatniego kawałka?
CMCDragonkai,
420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Prosty. Łatwy. Szybki. Działa z dowolną sekwencją:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']
nosklo
źródło
16
Wersja @Carlos Crasborn działa dla każdej iterowalnej (nie tylko sekwencji jak powyższy kod); jest zwięzły i prawdopodobnie równie szybki lub nawet szybszy. Chociaż może to być nieco niejasne (niejasne) dla osób niezaznajomionych z itertoolsmodułem.
jfs
1
Zgoda. Jest to najbardziej ogólny i pytonowy sposób. Jasne i zwięzłe. (i działa na silniku aplikacji)
Matt Williamson,
3
Zauważ, że chunkerzwraca a generator. Zamień powrót na: return [...]aby uzyskać listę.
Dror
11
Zamiast pisać budynek funkcji, a następnie powrót do generatora, można również napisać generator bezpośrednio, przy użyciu 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.
Alfe,
3
Uwaga: działa to tylko w przypadku sekwencji, które obsługują dostęp do elementów według indeksu i nie będą działać dla ogólnych iteratorów, ponieważ mogą nie obsługiwać __getitem__metody.
apollov
135

Jestem fanem

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size
S.Lott
źródło
Jak się zachowuje, jeśli len (ints) nie jest wielokrotnością chunkSize?
PlsWork
3
@AnnaVopureta chunkbę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 .
Boris,
22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Inny sposób:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4
Markus Jarderot
źródło
2
+1 za korzystanie z generatorów, szwy jak najbardziej „pytoniczne” ze wszystkich sugerowanych rozwiązań
Sergey Golovchenko
7
Jest dość długi i niezgrabny jak na coś tak łatwego, co wcale nie jest bardzo pytoniczne. Wolę wersję S. Lott
zenazn
4
@zenazn: to zadziała na instancjach generatora, krojenie nie
Janus Troelsen
Oprócz poprawnej pracy z generatorami i innymi iteratorami nie do krojenia, pierwsze rozwiązanie również nie wymaga wartości „wypełniacza”, jeśli końcowa porcja jest mniejsza niż size, co jest czasem pożądane.
dano
1
Również +1 dla generatorów. Inne rozwiązania wymagają lenpołączenia, więc nie działają na innych generatorach.
Cuadue,
12
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
Pedro Henriques
źródło
Czytelnym sposobem na to jest stackoverflow.com/questions/434287/…
jfs
Zauważ, że w python 3 izip_longestjest zastąpiony przezzip_longest
mdmjsh,
11

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:

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

Używając ipython %timeitna 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ć:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Proste, ale dość wolne: 693 us na pętlę

Najlepsze rozwiązanie, jakie mogłem wymyślić z wykorzystaniem islicepętli wewnętrznej:

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

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ź.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Naprawdę nie lubię tej odpowiedzi, ale jest ona znacznie szybsza. 124 us na pętlę

rhettg
źródło
Można zmniejszyć czas pracy receptury # 3 przez ~ 10-15%, przesuwając go do warstwy C (z pominięciem itertoolsimportu; mapmusi być Py3 maplub imap) 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ę fillvalueargumentu; dodaj pierwszy wiersz fillvalue = object(), a następnie zmień pole ifwyboru if i[-1] is fillvalue:i linię, którą kontroluje yield tuple(v for v in i if v is not fillvalue). Gwarantuje, że żadnej wartości nie iterablemożna pomylić z wartością wypełniacza.
ShadowRanger
BTW, wielkie kciuki do góry na # 4. Już miałem opublikować moją optymalizację nr 3 jako lepszą odpowiedź (pod względem wydajności) niż to, co zostało opublikowane do tej pory, ale z poprawką, aby była niezawodna, odporna # 4 działa dwa razy szybciej niż zoptymalizowana # 3; Nie spodziewałem się, że wygra rozwiązanie z pętlami poziomu Pythona (i bez teoretycznych różnic algorytmicznych AFAICT). Zakładam, że # 3 przegrywa z powodu kosztów budowy / iteracji isliceobiektów (# 3 wygrywa, jeśli njest 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.
ShadowRanger
W przypadku # 4 pierwsza gałąź warunkowa jest pobierana tylko podczas ostatniej iteracji (ostatecznej krotki). Zamiast całego ponownie odtworzenie ostateczną krotki, buforować modulo długości oryginału iterable na górze i używać, aby odciąć niechcianych wyściółką z izip_longestnad ostatecznym krotki: yield i[:modulo]. Ponadto, dla argszmiennej, krotka go zamiast listy: args = (iter(iterable),) * n. Goli kilka kolejnych cykli zegara. Wreszcie, jeśli zignorujemy wartość wypełnienia i założymy None, warunek może stać się if None in idla jeszcze większej liczby cykli zegara.
Kumba
1
@Kumba: Twoja pierwsza sugestia zakłada, że ​​dane wejściowe mają znaną długość. Jeśli jest to iterator / generator, a nie kolekcja o znanej długości, nie ma nic do buforowania. Zresztą nie ma żadnego powodu, aby korzystać z takiej optymalizacji; optymalizujesz przypadek rzadki (ostatni yield), podczas gdy zwykły przypadek pozostaje niezmieniony.
ShadowRanger,
10

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.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Lista:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Zestaw:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Generator:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
bcoughlan
źródło
8

Podobnie jak inne propozycje, ale nie do końca identyczne, lubię to robić w ten sposób, ponieważ jest prosty i łatwy do odczytania:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

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żyj izip_longestz itertools.

Kriss
źródło
można ulepszyć za pomocązip(*([it]*4))
Jean-François Fabre
@ Jean-François Fabre: z punktu widzenia czytelności nie widzę w tym poprawy. Jest także nieco wolniejszy. To poprawa, jeśli grasz w golfa, a ja nie.
kriss
nie, nie gram w golfa, ale co, jeśli masz 10 argumentów? Przeczytałem ten konstrukt na jakiejś oficjalnej stronie. Ale oczywiście nie mogę go teraz znaleźć :)
Jean-François Fabre
@ Jean-François Fabre: jeśli mam 10 argumentów lub zmienną liczbę argumentów, jest to opcja, ale wolałbym napisać: zip (* (it,) * 10)
kriss
dobrze! tak czytam. nie lista rzeczy, które wymyśliłem :)
Jean-François Fabre
8

Jeśli nie masz nic przeciwko użyciu zewnętrznego pakietu, możesz użyć iteration_utilities.grouperod 1 . Obsługuje wszystkie iterowalne (nie tylko sekwencje):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

który drukuje:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

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:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

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:

wprowadź opis zdjęcia tutaj

Przynajmniej w tym teście iteration_utilities.grouperdział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

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Zastrzeżenie: Jestem autorem bibliotek iteration_utilitiesi simple_benchmark.

MSeifert
źródło
7

Ponieważ nikt o tym nie wspomniał, oto zip()rozwiązanie:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

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:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Lub użyj itertools.izip, aby zwrócić iterator zamiast listy:

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Wypełnienie można naprawić za pomocą odpowiedzi @ ΤΖΩΤΖΙΟΥ :

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)
jfs
źródło
5

Użycie map () zamiast zip () rozwiązuje problem dopełniania w odpowiedzi JF Sebastiana:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Przykład:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
kocica
źródło
2
Jest to lepiej obsługiwane przy pomocy itertools.izip_longest(Py2) / itertools.zip_longest(Py3); to użycie mapjest podwójnie przestarzałe i niedostępne w Py3 (nie można przejść Nonejako funkcja odwzorowująca, i zatrzymuje się, gdy wyczerpana zostanie najkrótsza iteracja, a nie najdłuższa; nie padnie).
ShadowRanger
4

Innym podejściem byłoby użycie dwuargumentowej formy iter:

from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Można to łatwo dostosować do użycia paddingu (jest to podobne do odpowiedzi Markusa Jarderota ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Można je nawet połączyć w celu opcjonalnego wypełnienia:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)
senderle
źródło
1
lepiej, ponieważ masz możliwość pominięcia wypełnienia!
n611x007
3

Jeśli lista jest duża, najskuteczniejszym sposobem na to będzie użycie generatora:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)
Robert Rossney
źródło
(Myślę, że sugestia MizardXa jest funkcjonalnie równoważna z tym.)
Robert Rossney,
1
(Właściwie, po zastanowieniu, nie, ja nie. Itertools.islice zwraca iterator, ale nie używa już istniejącego.)
Robert Rossney
Jest ładny i prosty, ale z jakiegoś powodu nawet bez konwersji na krotkę 4-7 razy wolniej niż akceptowana metoda grupowania na iterable = range(100000000)i chunksizedo 10000.
Valentas
Jednak ogólnie polecam tę metodę, ponieważ zaakceptowana metoda może być bardzo wolna, gdy sprawdzanie ostatniego elementu jest powolne docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas
3

Używanie małych funkcji i rzeczy tak naprawdę nie podoba mi się; Wolę po prostu użyć plasterków:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...
Będzie
źródło
ładne, ale nie dobre dla nieokreślonego strumienia, który nie jest znany len. możesz zrobić test za pomocą itertools.repeatlub itertools.cycle.
n611x007
1
Również zjada pamięci, ponieważ wynikające z zastosowania [...for...] listowych fizycznie zbudować listę zamiast użycia (...for...) wyrażenia generatora , które po prostu dbają o kolejnym elemencie i pamięci wolnym
n611x007
2

Aby uniknąć wszystkich konwersji do listy import itertoolsi:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Produkuje:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

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:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Moim szczególnym zainteresowaniem była potrzeba użycia generatora, aby przesłać zmiany w partiach do 1000 do interfejsu API Gmaila:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)
John Mee
źródło
Co zrobić, jeśli tworzona lista jest czymś innym niż sekwencja rosnących liczb całkowitych?
PaulMcG
@PaulMcGuire patrz groupby ; biorąc pod uwagę funkcję opisującą porządek, elementy iterowalne mogą być czymkolwiek, prawda?
John Mee,
1
Tak, znam Groupby. Ale jeśli wiadomości byłyby literami „ABCDEFG”, to 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.
PaulMcG,
2

Z NumPy jest to proste:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

wynik:

1 2
3 4
5 6
7 8
endolit
źródło
2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()
Kamil Sindi
źródło
2

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:

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))
Alexey
źródło
1

W drugiej metodzie przejdę do następnej grupy 4, wykonując następujące czynności:

ints = ints[4:]

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.

Greg Hewgill
źródło
1

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.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

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.

elhefe
źródło
1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist
Wilfred Hughes
źródło
+1 pomija dopełnienie; twoje i bcoughlan 's są bardzo podobne
n611x007
1

Możesz użyć funkcji partycji lub porcji z biblioteki funcy :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Funkcje te mają również wersje iteratora ipartitioni ichunks, które w tym przypadku będą bardziej wydajne.

Możesz także zerknąć na ich implementację .

Suor
źródło
1

O rozwiązaniu podanym J.F. Sebastian tutaj :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

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:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Następnie

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Przykładowe użycie:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'
GingerPlusPlus
źródło
2
Nie krytyka przeznaczona do zmiany odpowiedzi, ale komentarz: Kod to zobowiązanie. Im więcej kodu napiszesz, tym więcej miejsca tworzysz dla błędów do ukrycia. Z tego punktu widzenia przepisywanie zipzamiast korzystania z istniejącego nie wydaje się najlepszym pomysłem.
Alfe
1

Lubię to podejście. Jest prosty i nie magiczny, obsługuje wszystkie typy iteracyjne i nie wymaga importu.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk
BallpointBen
źródło
1

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.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks
frankish
źródło
1

Oto porcja bez importu, która obsługuje generatory:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Przykład zastosowania:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]
Cuadue
źródło
1

W Pythonie 3.8 możesz używać operatora morsa i itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
Kafran
źródło
0

Wydaje się, że nie ma na to ładnego sposobu. Oto strona z wieloma metodami, w tym:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq
Harley Holcombe
źródło
0

Jeśli listy mają ten sam rozmiar, możesz połączyć je w listy 4-krotne z zip(). Na przykład:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Oto, co zip()wytwarza funkcja:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

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ę.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...
Brian Clapper
źródło
0

Jednoliniowe, adhoc rozwiązanie iteracji po liście xw kawałkach wielkości 4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
Tutul
źródło