Qual è il modo più “pitonico” per scorrere su un elenco in blocchi?


488

Ho uno script Python che accetta come input un elenco di numeri interi, che devo lavorare con quattro numeri interi alla volta. Sfortunatamente, non ho il controllo dell'input, o lo farei passare come un elenco di tuple a quattro elementi. Attualmente, sto ripetendo in questo modo:

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]

Sembra molto simile a "C-think", il che mi fa sospettare che ci sia un modo più pitonico di affrontare questa situazione. L'elenco viene eliminato dopo l'iterazione, quindi non è necessario conservarlo. Forse qualcosa del genere sarebbe meglio?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Comunque non "sente" bene, comunque. : - /

Domanda correlata: come dividere un elenco in blocchi di dimensioni uniformi in Python?


3
Il codice non funziona se la dimensione dell'elenco non è un multiplo di quattro.
Pedro Henriques,

5
Sto estendendo () la lista in modo che la sua lunghezza sia un multiplo di quattro prima che arrivi così lontano.
Ben Blank,

4
@ ΤΖΩΤΖΙΟΥ - Le domande sono molto simili, ma non del tutto duplicate. È "suddiviso in un numero qualsiasi di blocchi di dimensioni N" rispetto a "suddiviso in blocchi N di qualsiasi dimensione". :-)
Ben Blank


Risposte:


340

Modificato dalla sezione ricette dei documenti itertools di Python :

from itertools import zip_longest

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

Esempio
In pseudocodice per mantenere conciso l'esempio.

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

Nota: su Python 2 utilizzare izip_longestinvece di zip_longest.


67
Finalmente ho avuto la possibilità di giocare con questo in una sessione di Python. Per coloro che sono confusi quanto me, questo sta alimentando lo stesso iteratore per izip_longest più volte, inducendolo a consumare valori successivi della stessa sequenza anziché valori a strisce da sequenze separate. Lo adoro!
Ben Blank,

6
Qual è il modo migliore per filtrare indietro il valore di riempimento? ([articolo per articolo in articoli se l'articolo non è valore di riempimento] per gli articoli in cernia (iterabile))?
gotgenes,

14
Ho il sospetto che le prestazioni di questa ricetta di cernia per blocchi di dimensioni 256k saranno molto scadenti, perché izip_longestverranno alimentati argomenti 256k.
anatoly techtonik,

13
In diversi punti i commentatori dicono "quando finalmente ho capito come funzionava ...." Forse è necessario un po 'di spiegazione. In particolare l'elenco degli aspetti iteratori.
Londra,

6
C'è un modo per usarlo ma senza Noneriempire l'ultimo pezzo?
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)

Semplice. Facile. Veloce. Funziona con qualsiasi sequenza:

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

16
La versione di @Carlos Crasborn funziona per qualsiasi iterabile (non solo sequenze come il codice sopra); è conciso e probabilmente altrettanto veloce o persino più veloce. Anche se potrebbe essere un po 'oscuro (poco chiaro) per le persone che non hanno familiarità con il itertoolsmodulo.
jfs,

1
Concordato. Questo è il modo più generico e pitonico. Chiaro e conciso. (e funziona sul motore dell'app)
Matt Williamson

3
Si noti che chunkerrestituisce a generator. Sostituisci il ritorno a: return [...]per ottenere un elenco.
Dror,

11
Invece di scrivere un edificio funzione e poi tornare un generatore, si potrebbe anche scrivere un generatore direttamente, tramite yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Non sono sicuro che internamente questo sarebbe gestito in modo diverso in qualsiasi aspetto rilevante, ma potrebbe essere anche un po 'più chiaro.
Alfe,

3
Nota che funziona solo per sequenze che supportano l'accesso agli elementi per indice e non funzionano per iteratori generici, perché potrebbero non supportare il __getitem__metodo.
apollov

135

Sono un fan di

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

Come si comporta se len (ints) non è un multiplo di chunkSize?
PlsWork,

3
@AnnaVopureta chunkavrà 1, 2 o 3 elementi per l'ultimo lotto di elementi. Vedi questa domanda sul perché gli indici delle sezioni possono essere fuori limite .
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)

Un altro modo:

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

2
+1 per l'utilizzo di generatori, cuciture come la più "pitonica" di tutte le soluzioni suggerite
Sergey Golovchenko,

7
È piuttosto lungo e goffo per qualcosa di così facile, che non è affatto molto pitonico. Preferisco la versione di S. Lott
zenazn,

4
@zenazn: questo funzionerà su istanze del generatore, lo slicing no
Janus Troelsen,

Oltre a funzionare correttamente con generatori e altri iteratori non affettabili, la prima soluzione non richiede un valore di "riempimento" se il blocco finale è più piccolo di size, il che a volte è desiderabile.
dano,

1
Anche +1 per i generatori. Altre soluzioni richiedono una lenchiamata e quindi non funzionano su altri generatori.
Cuadue,


11

La soluzione ideale per questo problema funziona con gli iteratori (non solo con le sequenze). Dovrebbe anche essere veloce.

Questa è la soluzione fornita dalla documentazione per 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)

Usando ipython's %timeitsul mio mac book air, ottengo 47.5 us per loop.

Tuttavia, questo non funziona davvero per me poiché i risultati sono imbottiti per essere gruppi di dimensioni pari. Una soluzione senza imbottitura è leggermente più complicata. La soluzione più ingenua potrebbe essere:

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

Semplice, ma piuttosto lento: 693 us per loop

La migliore soluzione che ho potuto trovare utilizza isliceper il ciclo interno:

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

Con lo stesso set di dati, ottengo 305 us per loop.

Incapace di ottenere una soluzione pura più velocemente di così, fornisco la seguente soluzione con un avvertimento importante: se i tuoi dati di input contengono istanze di filldataesso, potresti ottenere una risposta sbagliata.

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

Non mi piace davvero questa risposta, ma è significativamente più veloce. 124 us per loop


È possibile ridurre il runtime per la ricetta # 3 by ~ 10-15% spostandolo allo strato C (omettendo itertoolsimportazioni; mapdeve essere PY3 mapo imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). La tua funzione finale può essere resa meno fragile usando una sentinella: sbarazzati fillvaluedell'argomento; aggiungi una prima riga fillvalue = object(), quindi modifica il ifsegno di spunta if i[-1] is fillvalue:e la riga in cui controlla yield tuple(v for v in i if v is not fillvalue). Garantisce che nessun valore in iterablepossa essere confuso con il valore di riempimento.
ShadowRanger

A proposito, grandi pollici in su # 4. Stavo per pubblicare la mia ottimizzazione del n. 3 come una risposta migliore (dal punto di vista delle prestazioni) rispetto a quanto era stato pubblicato finora, ma con la modifica per renderlo affidabile, il resiliente n. 4 corre due volte più veloce del n. Ottimizzato 3; Non mi aspettavo una soluzione con loop di livello Python (e nessuna differenza teorica algoritmica AFAICT) per vincere. Presumo che il n. 3 perda a causa delle spese di costruzione / iterazione di isliceoggetti (il n. 3 vince se nè relativamente grande, ad esempio il numero di gruppi è piccolo, ma si sta ottimizzando per un caso non comune), ma non mi aspettavo che fosse abbastanza estremo.
ShadowRanger

Per # 4, il primo ramo del condizionale è sempre e solo preso sull'ultima iterazione (la tupla finale). Invece di ricostituire daccapo tupla finale, memorizzare nella cache il modulo della lunghezza iterable originale all'inizio e utilizzarlo per tagliare via l'imbottitura indesiderato dal izip_longestsulla tupla finale: yield i[:modulo]. Inoltre, per la argsvariabile tupla invece di una lista: args = (iter(iterable),) * n. Rade alcuni altri cicli di clock. Infine, se ignoriamo il valore di riempimento e assumiamo None, il condizionale può diventare if None in iper ancora più cicli di clock.
Kumba,

1
@Kumba: il tuo primo suggerimento presuppone che l'input abbia una lunghezza nota. Se è un iteratore / generatore, non una raccolta con lunghezza nota, non c'è nulla da memorizzare nella cache. Non c'è alcun motivo reale per utilizzare tale ottimizzazione comunque; stai ottimizzando il caso non comune (l'ultimo yield), mentre il caso comune non è interessato.
ShadowRanger,

10

Avevo bisogno di una soluzione che funzionasse anche con gruppi e generatori. Non riuscivo a trovare niente di molto breve e carino, ma almeno abbastanza leggibile.

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

Elenco:

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

Impostato:

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

Generatore:

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

8

Simile ad altre proposte, ma non esattamente identico, mi piace farlo in questo modo, perché è semplice e facile da leggere:

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)

In questo modo non otterrai l'ultimo pezzo parziale. Se vuoi ottenere (9, None, None, None)l'ultimo pezzo, usa semplicemente izip_longestda itertools.


può essere migliorato conzip(*([it]*4))
Jean-François Fabre

@ Jean-François Fabre: da un punto di vista della leggibilità non lo vedo come un miglioramento. Ed è anche leggermente più lento. È un miglioramento se stai giocando a golf, cosa che non sono.
Kriss,

no non sto giocando a golf, ma cosa succede se hai 10 argomenti? Ho letto quel costrutto in qualche pagina ufficiale, ma ovviamente non riesco a trovarlo in questo momento :)
Jean-François Fabre

@ Jean-François Fabre: se ho 10 argomenti o un numero variabile di argomenti, è un'opzione, ma preferirei scrivere: zip (* (it,) * 10)
kriss

destra! questo è quello che ho letto. non la roba dell'elenco che ho inventato :)
Jean-François Fabre

8

Se non ti dispiace usare un pacchetto esterno puoi usare iteration_utilities.grouperda 1 . Supporta tutti gli iterabili (non solo le sequenze):iteration_utilties

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

che stampa:

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

Nel caso in cui la lunghezza non sia un multiplo del gruppo, supporta anche il riempimento (l'ultimo gruppo incompleto) o il troncamento (eliminando l'ultimo gruppo incompleto) l'ultimo:

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)

Punti di riferimenti

Ho anche deciso di confrontare il tempo di esecuzione di alcuni degli approcci citati. È un diagramma log-log che raggruppa in gruppi di "10" elementi basati su un elenco di dimensioni variabili. Per risultati qualitativi: inferiore significa più veloce:

inserisci qui la descrizione dell'immagine

Almeno in questo benchmark le iteration_utilities.grouperprestazioni migliori. Seguito dall'approccio di Craz .

Il benchmark è stato creato con 1 . Il codice utilizzato per eseguire questo benchmark era: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 Disclaimer: sono l'autore delle biblioteche iteration_utilitiese simple_benchmark.


7

Dal momento che nessuno lo ha menzionato, ecco una zip()soluzione:

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

Funziona solo se la lunghezza della sequenza è sempre divisibile per la dimensione del blocco o non ti interessa un pezzo finale se non lo è.

Esempio:

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

O usando itertools.izip per restituire un iteratore invece di un elenco:

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

L'imbottitura può essere riparata usando la risposta di @ ΤΖΩΤΖΙΟΥ :

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

5

L'uso di map () anziché zip () risolve il problema del riempimento nella risposta di JF Sebastian:

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

Esempio:

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

2
Questo è meglio gestito con itertools.izip_longest(Py2) / itertools.zip_longest(Py3); questo uso mapè doppiamente deprecato e non disponibile in Py3 (non è possibile passare Nonecome funzione mapper e si interrompe quando si esaurisce il più breve iterabile, non il più lungo; non si pad).
ShadowRanger

4

Un altro approccio sarebbe quello di utilizzare la forma a due argomenti di iter:

from itertools import islice

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

Questo può essere facilmente adattato per usare l'imbottitura (è simile alla risposta di Markus Jarderot ):

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)

Questi possono anche essere combinati per imbottitura opzionale:

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

1
preferibile perché hai la possibilità di omettere l'imbottitura!
n611x007,

3

Se l'elenco è grande, il modo più efficace per farlo sarà usare un generatore:

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

(Penso che il suggerimento di itertools di MizardX sia funzionalmente equivalente a questo.)
Robert Rossney,

1
(In realtà, a pensarci bene, no, no. Itertools.islice restituisce un iteratore, ma non ne usa uno esistente.)
Robert Rossney,

È bello e semplice, ma per qualche ragione anche senza conversione in tupla 4-7 volte più lenta del metodo di cernia accettato iterable = range(100000000)e chunksizefino a 10000.
Valentas

Tuttavia, in generale consiglierei questo metodo, perché quello accettato può essere estremamente lento quando il controllo dell'ultimo elemento è lento docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

L'uso di piccole funzioni e cose non mi piace davvero; Preferisco usare solo le sezioni:

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

bello ma non va bene per un flusso indefinito che non ha conosciuto len. puoi fare un test con itertools.repeato itertools.cycle.
n611x007,

1
Inoltre, consuma memoria a causa dell'utilizzo di una [...for...] comprensione dell'elenco per creare fisicamente un elenco anziché utilizzare (...for...) un'espressione del generatore che si preoccuperebbe solo dell'elemento successivo e della memoria di riserva
n611x007

2

Per evitare tutte le conversioni in un elenco import itertoolse:

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

produce:

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

Ho controllato groupbye non si converte in elenco o utilizzo, lenquindi (penso) questo ritarderà la risoluzione di ciascun valore fino a quando non viene effettivamente utilizzato. Purtroppo nessuna delle risposte disponibili (in questo momento) sembrava offrire questa variazione.

Ovviamente se hai bisogno di gestire ogni elemento a sua volta annidare a for loop over 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

Il mio interesse specifico per questo era la necessità di consumare un generatore per inviare modifiche in batch fino a 1000 all'API di Gmail:

    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)

Cosa succede se l'elenco che stai raggruppando è qualcosa di diverso da una sequenza di numeri interi ascendenti?
PaulMcG,

@PaulMcGuire vedi groupby ; data una funzione per descrivere l'ordine, allora gli elementi dell'iterabile possono essere qualsiasi cosa, giusto?
John Mee,

1
Sì, ho familiarità con il groupby. Ma se i messaggi fossero le lettere "ABCDEFG", allora groupby(messages, lambda x: x/3)ti darebbe un TypeError (per cercare di dividere una stringa per un int), non raggruppamenti di 3 lettere. Ora se lo facessi groupby(enumerate(messages), lambda x: x[0]/3)potresti avere qualcosa. Ma non l'hai detto nel tuo post.
PaulMcG,

2

Con NumPy è semplice:

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

produzione:

1 2
3 4
5 6
7 8

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

2

A meno che non mi manchi qualcosa, non è stata menzionata la seguente semplice soluzione con espressioni di generatore. Presuppone che siano noti sia la dimensione che il numero di blocchi (che è spesso il caso) e che non è richiesta alcuna imbottitura:

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

1

Nel tuo secondo metodo, avanzerei al prossimo gruppo di 4 facendo questo:

ints = ints[4:]

Tuttavia, non ho eseguito alcuna misurazione delle prestazioni, quindi non so quale potrebbe essere più efficiente.

Detto questo, di solito sceglierei il primo metodo. Non è carino, ma è spesso una conseguenza dell'interfaccia con il mondo esterno.


1

Ancora un'altra risposta, i cui vantaggi sono:

1) Facilmente comprensibile
2) Funziona su qualsiasi sequenza iterabile, non solo su sequenze (alcune delle risposte di cui sopra si soffocano sui filehandle)
3) Non carica il blocco in memoria tutto in una volta
4) Non crea un lungo elenco di riferimenti a lo stesso iteratore in memoria
5) Nessun riempimento dei valori di riempimento alla fine dell'elenco

Detto questo, non l'ho cronometrato, quindi potrebbe essere più lento di alcuni dei metodi più intelligenti e alcuni dei vantaggi potrebbero essere irrilevanti dato il caso d'uso.

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 

Aggiornamento:
un paio di inconvenienti dovuti al fatto che i loop interni ed esterni stanno tirando i valori dallo stesso iteratore:
1) continue non funziona come previsto nel loop esterno - continua solo con l'elemento successivo invece di saltare un blocco . Tuttavia, questo non sembra un problema in quanto non c'è nulla da testare nel circuito esterno.
2) l'interruzione non funziona come previsto nell'anello interno - il controllo si riavvolge nuovamente nell'anello interno con l'elemento successivo nell'iteratore. Per saltare interi pezzi, avvolgere l'iteratore interno (ii sopra) in una tupla, ad esempio for c in tuple(ii), oppure impostare una bandiera e esaurire l'iteratore.


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

+1 omette l'imbottitura; la vostra e bcoughlan 's è molto simile
n611x007

1

È possibile utilizzare la funzione di partizione o blocchi dalla libreria funcy :

from funcy import partition

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

Queste funzioni hanno anche versioni iteratrici ipartitione ichunks, che saranno più efficienti in questo caso.

Puoi anche dare un'occhiata alla loro implementazione .


1

Informazioni sulla soluzione fornita J.F. Sebastian qui :

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

È intelligente, ma presenta uno svantaggio: restituire sempre la tupla. Come ottenere invece la stringa?
Ovviamente puoi scrivere ''.join(chunker(...)), ma la tupla temporanea è costruita comunque.

Puoi sbarazzarti della tupla temporanea scrivendo proprio zip, in questo modo:

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

Poi

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

Esempio di utilizzo:

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

2
Non una critica significa per te cambiare la tua risposta, ma piuttosto un commento: il codice è una responsabilità. Più codice scrivi, più spazio crei per nascondere i bug. Da questo punto di vista, riscrivere zipinvece di usare quello esistente sembra non essere la migliore idea.
Alfe,

1

Mi piace questo approccio. Sembra semplice e non magico e supporta tutti i tipi iterabili e non richiede importazioni.

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

1

Non voglio mai imbottire i miei pezzi, quindi questo requisito è essenziale. Trovo che sia necessaria anche la capacità di lavorare su qualsiasi iterabile. Detto questo, ho deciso di estendere la risposta accettata, https://stackoverflow.com/a/434411/1074659 .

Le prestazioni subiscono un leggero colpo in questo approccio se il padding non è desiderato a causa della necessità di confrontare e filtrare i valori imbottiti. Tuttavia, per pezzi di grandi dimensioni, questa utility è molto performante.

#!/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

1

Ecco un grosso pezzo senza importazioni che supporta i generatori:

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

Esempio di utilizzo:

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

1

Con Python 3.8 puoi usare l'operatore tricheco e 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]

0

Non sembra esserci un modo carino per farlo. Ecco una pagina che ha una serie di metodi, tra cui:

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

0

Se gli elenchi hanno le stesse dimensioni, puoi combinarli in elenchi di 4 tuple con zip(). Per esempio:

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

Ecco cosa zip()produce la funzione:

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

Se gli elenchi sono di grandi dimensioni e non si desidera combinarli in un elenco più grande, utilizzare itertools.izip(), che produce un iteratore, anziché un elenco.

from itertools import izip

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

0

Soluzione ad hoc per una sola riga per scorrere su un elenco xin blocchi di dimensioni 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 ...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.