Come si divide un elenco in blocchi di dimensioni uniformi?


2269

Ho un elenco di lunghezza arbitraria e ho bisogno di dividerlo in blocchi di uguali dimensioni e operare su di esso. Esistono alcuni modi ovvi per farlo, come mantenere un contatore e due elenchi e quando si riempie il secondo elenco, aggiungerlo al primo elenco e svuotare il secondo elenco per il prossimo round di dati, ma questo è potenzialmente estremamente costoso.

Mi chiedevo se qualcuno avesse una buona soluzione a questo per elenchi di qualsiasi lunghezza, ad esempio usando i generatori.

Stavo cercando qualcosa di utile itertoolsma non riuscivo a trovare nulla di ovviamente utile. Potrei averlo perso, però.

Domanda correlata: qual è il modo più “pitonico” per scorrere su un elenco in blocchi?


1
Prima di pubblicare una nuova risposta, considera che ci sono già più di 60 risposte per questa domanda. Assicurati che la tua risposta fornisca informazioni che non sono tra le risposte esistenti.
Janniks,

Per gli utenti che vogliono evitare un pezzo finale arbitrariamente piccolo, guarda Dividere un elenco in N parti di lunghezza approssimativamente uguale
wim

Risposte:


3152

Ecco un generatore che produce i pezzi che vuoi:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]

Se stai usando Python 2, dovresti usare xrange()invece di range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Inoltre puoi semplicemente usare la comprensione dell'elenco invece di scrivere una funzione, sebbene sia una buona idea incapsulare operazioni come questa nelle funzioni con nome in modo che il tuo codice sia più facile da capire. Python 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Versione Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]

72
Cosa succede se non riusciamo a stabilire la lunghezza dell'elenco? Prova questo su itertools.repeat ([1, 2, 3]), ad esempio
jespern

47
Questa è un'estensione interessante della domanda, ma la domanda originale era chiaramente posta sull'operare in un elenco.
Ned Batchelder,

33
questa funzione deve trovarsi nella dannata libreria standard
dgan

6
@Calimo: cosa mi consigliate? Ti do un elenco con 47 elementi. Come ti piacerebbe dividerlo in "pezzi di dimensioni uniformi"? L'OP ha accettato la risposta, quindi sono chiaramente OK con l'ultimo pezzo di dimensioni diverse. Forse la frase inglese è imprecisa?
Ned Batchelder,

8
Per favore non nominare le variabili l, sembra esattamente come 1 ed è confuso. Le persone stanno copiando il tuo codice e pensano che sia ok.
Yasen,

555

Se vuoi qualcosa di super semplice:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Utilizzare xrange()invece che range()nel caso di Python 2.x


6
Oppure (se stiamo rappresentando in modo diverso questa particolare funzione) potresti definire una funzione lambda tramite: lambda x, y: [x [i: i + y] per i nell'intervallo (0, len (x), y) ]. Adoro questo metodo di comprensione delle liste!
JP

4
dopo il ritorno ci deve essere [, non (
alwbtc,

2
"Super semplice" significa non dover eseguire il debug di loop infiniti - complimenti per il max().
Bob Stein,

non c'è niente di semplice di questa soluzione
mit

1
@Nhoj_Gonk Oops non è un ciclo infinito, ma blocchi (L, 0) genererebbero un ValueError senza il max (). Invece, il max () trasforma qualcosa di meno di 1 in un 1.
Bob Stein il

295

Direttamente dalla (vecchia) documentazione di Python (ricette per itertools):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

La versione attuale, come suggerito da JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Immagino che la macchina del tempo di Guido funzioni — ha funzionato — funzionerà — avrà funzionato — funzionava di nuovo.

Queste soluzioni funzionano perché [iter(iterable)]*n(o l'equivalente nella versione precedente) crea un iteratore, ntempi ripetuti nell'elenco. izip_longestquindi esegue efficacemente un round robin di "ogni" iteratore; poiché questo è lo stesso iteratore, è avanzato da ciascuna di queste chiamate, con il risultato che ciascuna di queste zip-roundrobin genera una tupla di nelementi.


@ninjagecko: list(grouper(3, range(10)))ritorna [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]e tutte le tuple sono lunghe 3. Per favore, elabora il tuo commento perché non riesco a capirlo; come si chiama una cosa e come si definisce che è un multiplo di 3 in "aspettandosi che la propria cosa sia un multiplo di 3"? Grazie in anticipo.
martedì

14
ha valutato questo perché funziona con generatori (no len) e utilizza il modulo itertools generalmente più veloce.
Michael Dillon,

88
Un classico esempio di sofisticato itertoolsapproccio funzionale che rivela alcuni fanghi illeggibili, rispetto a una semplice e ingenua implementazione in puro pitone
wim

15
@wim Dato che questa risposta è iniziata come frammento della documentazione di Python, ti suggerirei di aprire un problema su bugs.python.org .
martedì

1
@pedrosaurio se l==[1, 2, 3]allora f(*l)equivale a f(1, 2, 3). Vedi quella domanda e la documentazione ufficiale .
Tzot

227

So che questo è un po 'vecchio ma nessuno ancora menzionato numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]

12
Ciò consente di impostare il numero totale di blocchi, non il numero di elementi per blocco.
FizxMike,

6
puoi fare la matematica da solo. se hai 10 elementi puoi raggrupparli in pezzi da 2, 5 elementi o cinque pezzi da 2 elementi
Moj

24
+1 Questa è la mia soluzione preferita, in quanto divide l'array in array di dimensioni uniformi , mentre altre soluzioni no (in tutte le altre soluzioni che ho visto, l'ultimo array può essere arbitrariamente piccolo).
MiniQuark,

@MiniQuark ma cosa fa questo quando il numero di blocchi non è un fattore della dimensione originale dell'array?
Baldrickk,

1
@Baldrickk Se dividi N elementi in blocchi K, i primi blocchi N% K avranno elementi N // K + 1 e il resto avrà elementi N // K. Ad esempio, se dividi un array contenente 108 elementi in 5 blocchi, il primo 108% 5 = 3 blocchi conterrà 108 // 5 + 1 = 22 elementi e il resto dei blocchi avrà 108 // 5 = 21 elementi.
MiniQuark,

147

Sono sorpreso che nessuno abbia pensato di usare iterla forma a due argomenti :

from itertools import islice

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

demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Funziona con qualsiasi iterabile e produce pigramente output. Restituisce tuple anziché iteratori, ma penso che abbia comunque una certa eleganza. Inoltre non pad; se si desidera imbottitura, sarà sufficiente una semplice variazione di quanto sopra:

from itertools import islice, chain, repeat

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

demo:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Come le izip_longestsoluzioni basate su, sopra sempre pastiglie. Per quanto ne so, non esiste una ricetta itertools a una o due righe per una funzione che facoltativamente pad. Combinando i due approcci precedenti, questo si avvicina abbastanza:

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Credo che questo sia il pezzo più corto proposto che offre imbottiture opzionali.

Come osservato da Tomasz Gandor , i due blocchi di imbottitura si fermeranno inaspettatamente se incontrano una lunga sequenza di valori di pad. Ecco una variante finale che risolve il problema in modo ragionevole:

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

demo:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]

7
Meravigliosa, la tua versione semplice è la mia preferita. Anche altri hanno trovato l' islice(it, size)espressione di base e l'hanno incorporata (come avevo fatto io) in un costrutto a ciclo continuo. Solo tu hai pensato alla versione a due argomenti di iter()(non ne ero completamente a conoscenza), che la rende super elegante (e probabilmente la più efficace in termini di prestazioni). Non avevo idea che il primo argomento a itercambiare in una funzione argomento 0 quando veniva data la sentinella. Restituisci un (pot. Infinito) iteratore di blocchi, puoi usare un (pot. Infinito) iteratore come input, non avere len()e senza sezioni di array. Eccezionale!
ThomasH,

1
Questo è il motivo per cui ho letto le risposte piuttosto che scansionare solo la coppia migliore. Nel mio caso era necessario un padding opzionale e anch'io ho imparato a conoscere la forma a due argomenti di iter.
Kerr

Ho valutato questo, ma comunque - non esageriamo! Prima di tutto, la lambda può essere cattiva (chiusura lenta su ititeratore. In secondo luogo, e soprattutto importante - finirai prematuramente se padvalnel tuo iterabile esiste effettivamente un pezzo di realtà iterabile, e dovrebbe essere elaborato.
Tomasz Gandor,

@TomaszGandor, prendo il tuo primo punto! Sebbene la mia comprensione sia che lambda non sia più lento di una normale funzione, ovviamente hai ragione nel rallentare la chiamata di funzione e la ricerca di chiusura. Non so quale sia il risultato relativo delle prestazioni rispetto a questo izip_longestapproccio, ad esempio - sospetto che potrebbe essere un compromesso complesso. Ma ... il padvalproblema non è condiviso da ogni risposta qui che offre un padvalparametro?
mittente

1
@TomaszGandor, abbastanza giusto! Ma non è stato troppo difficile creare una versione che risolva questo problema. (Inoltre, si noti che la prima versione, che utilizza ()come sentinella, non funzionano correttamente Questo perché. tuple(islice(it, size))Rendimenti ()quando itè vuoto.)
senderle

93

Ecco un generatore che funziona su iterabili arbitrari:

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

Esempio:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

52
def chunk(input, size):
    return map(None, *([iter(input)] * size))

map(None, iter)uguale izip_longest(iter).
Thomas Ahle,

1
@TomaszWysocki Puoi spiegarci *di fronte alla tua tupla iteratrice? Forse nel tuo testo di risposta, ma ho notato che in precedenza era stato *usato in quel modo in Python. Grazie!
theJollySin

1
@theJollySin In questo contesto, si chiama operatore splat. Il suo uso è spiegato qui - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms

2
Chiudi ma l'ultimo pezzo ha Nessuno elementi per compilarlo. Questo può essere o meno un difetto. Modello davvero fantastico però.

49

Semplice ma elegante

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

o se preferisci:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)

18
Non devi duplicare una variabile a somiglianza di un numero arabo. In alcuni caratteri, 1e lsono indistinguibili. Come sono 0e O. E a volte anche Ie 1.
Alfe,

14
Font @Alfe difettosi. Le persone non dovrebbero usare tali caratteri. Non per la programmazione, non per niente .
Jerry B,

17
Le lambda sono pensate per essere usate come funzioni senza nome. Non ha senso usarli in quel modo. Inoltre, rende più difficile il debug in quanto il traceback riporterà "in <lambda>" anziché "in blocchi" in caso di errore. Ti auguro buona fortuna a trovare un problema se ne hai un sacco :)
Chris Koston,

1
dovrebbe essere 0 e non 1 all'interno di xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta il

NOTA: per gli utenti di Python 3 usare range.
Christian Dean,

40

Critica di altre risposte qui:

Nessuna di queste risposte è costituita da blocchi di dimensioni uniformi, alla fine lasciano un pezzo di sfregamento, quindi non sono completamente bilanciati. Se stavi usando queste funzioni per distribuire il lavoro, hai incorporato la prospettiva che uno probabilmente finisca molto prima degli altri, quindi rimarrebbe seduto senza fare nulla mentre gli altri continuano a lavorare sodo.

Ad esempio, l'attuale risposta in alto termina con:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

Alla fine odio quel brontolone!

Altri, come list(grouper(3, xrange(7))), e chunk(xrange(7), 3)sia di ritorno: [(0, 1, 2), (3, 4, 5), (6, None, None)]. I None's sono solo imbottitura, e piuttosto poco elegante a mio parere. NON stanno distribuendo uniformemente gli iterabili.

Perché non possiamo dividerli meglio?

Le mie soluzioni

Ecco una soluzione equilibrata, adattato da una funzione che ho usato in produzione (nota in Python 3 per sostituire xrangecon range):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

E ho creato un generatore che fa lo stesso se lo metti in un elenco:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

E infine, poiché vedo che tutte le funzioni sopra riportate restituiscono gli elementi in un ordine contiguo (come sono stati dati):

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

Produzione

Per provarli:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

Che stampa:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Si noti che il generatore contiguo fornisce blocchi con gli stessi schemi di lunghezza degli altri due, ma gli elementi sono tutti in ordine e sono equamente divisi come si potrebbe dividere un elenco di elementi discreti.


Dici che nessuna delle precedenti fornisce blocchi di dimensioni uniformi. Ma questo fa, così come questo .
mittente il

1
@senderle, Il primo, list(grouper(3, xrange(7)))ed il secondo, chunk(xrange(7), 3)entrambi ritorno: [(0, 1, 2), (3, 4, 5), (6, None, None)]. I None's sono solo imbottitura, e piuttosto poco elegante a mio parere. NON stanno distribuendo uniformemente gli iterabili. Grazie per il tuo voto!
Aaron Hall

4
Sollevi la domanda (senza farlo esplicitamente, quindi lo faccio ora qui) se blocchi di dimensioni uguali (tranne l'ultimo, se non possibile) o se un risultato equilibrato (il più buono possibile) è più spesso ciò che sarà necessario. Supponi che la soluzione equilibrata sia preferire; questo potrebbe essere vero se ciò che programmi è vicino al mondo reale (ad esempio un algoritmo di gestione delle carte per un gioco di carte simulato). In altri casi (come riempire le linee con le parole) si preferirà mantenere le linee più piene possibile. Quindi non posso davvero preferire l'uno all'altro; sono solo per diversi casi d'uso.
Alfe,

@ ChristopherBarrington-Leigh Un buon punto, per DataFrames, probabilmente dovresti usare le slice, dal momento che credo che gli oggetti DataFrame di solito non si copino all'affettatura, ad esempioimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHall Oops. Ho cancellato il mio commento perché ho indovinato la mia critica, ma sei stato veloce nel disegnare. Grazie! In realtà, la mia affermazione che non funziona per i frame di dati è vera. Se gli articoli sono un frame di dati, basta usare gli articoli di rendimento [range (x_i, item_count, cestini)] come ultima riga. Ho offerto una risposta separata (ancora un'altra), in cui si specifica la dimensione del gruppo (minima) desiderata.
CPBL,

38

Ho visto la più fantastica risposta di Python in un duplicato di questa domanda:

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Puoi creare n-tupla per qualsiasi n. Se a = range(1, 15), quindi il risultato sarà:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Se l'elenco è diviso uniformemente, è possibile sostituirlo zip_longestcon zip, altrimenti la terzina (13, 14, None)andrebbe persa. Python 3 è usato sopra. Per Python 2, utilizzare izip_longest.


che bello se la tua lista e pezzi sono brevi, come potresti adattarlo per dividere la tua lista in pezzi di 1000? non hai intenzione di codificare zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith,

9
zip(i, i, i, ... i)con argomenti "chunk_size" su zip () può essere scritto come zip(*[i]*chunk_size)Se questa sia una buona idea o meno sia discutibile, ovviamente.
Wilson F,

1
Il rovescio della medaglia di questo è che se non stai dividendo uniformemente, lascerai cadere gli elementi, poiché zip si interrompe nel più breve iterabile - e izip_longest aggiungerebbe elementi predefiniti.
Aaron Hall

zip_longestdovrebbe essere usato, come fatto in: stackoverflow.com/a/434411/1959808
Ioannis Filippidis,

Alla risposta range(1, 15)mancano già elementi, perché ci sono 14 elementi in range(1, 15), non 15.
Ioannis Filippidis,

35

Se conosci le dimensioni dell'elenco:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

In caso contrario (un iteratore):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

In quest'ultimo caso, può essere riformulato in un modo più bello se si può essere sicuri che la sequenza contenga sempre un numero intero di blocchi di dimensioni specifiche (ovvero non vi è un ultimo blocco incompleto).


Sono triste, questo è sepolto così lontano. IterChunks funziona per tutto ed è la soluzione generale e non ho avvertimenti che io conosca.
Jason Dunkelberger,

18

La libreria toolz ha la partitionfunzione per questo:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

Questo sembra il più semplice di tutti i suggerimenti. Mi sto solo chiedendo se può davvero essere vero che uno deve usare una libreria di terze parti per ottenere una tale funzione di partizione. Mi sarei aspettato che qualcosa di equivalente a quella funzione di partizione esistesse come linguaggio incorporato.
Kasperd,

1
puoi fare una partizione con itertools. ma mi piace la libreria toolz. è una biblioteca ispirata al clojure per lavorare su collezioni in uno stile funzionale. non si ottiene l'immutabilità ma si ottiene un piccolo vocabolario per lavorare su raccolte semplici. Inoltre, cytoolz è scritto in cython e ottiene un buon incremento delle prestazioni. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach

Il collegamento dal commento di zach funziona se si omette la barra finale: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit


16

Mi piace molto la versione del documento di Python proposta da tzot e JFSebastian, ma ha due carenze:

  • non è molto esplicito
  • Di solito non voglio un valore di riempimento nell'ultimo pezzo

Sto usando questo molto nel mio codice:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

AGGIORNAMENTO: una versione di blocchi pigri:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))

Qual è la condizione di interruzione per il while Trueloop?
wjandrea,

@wjandrea: Il rilancio StopIterationquando il tupleè vuoto e iterable.next()viene eseguito. Tuttavia, non funziona correttamente nel moderno Python, dove uscire da un generatore dovrebbe essere fatto return, non rilanciare StopIteration. A try/except StopIteration: returntutto il ciclo (e passando iterable.next()a next(iterable)per compatibilità con versioni incrociate) risolve questo problema con un minimo di sovraccarico.
ShadowRanger il

15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Dove AA è un array, SS ha una dimensione di blocco. Per esempio:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3

2
è il migliore e semplice.
F.Tamy,

2
breve e semplice. semplicità sopra complessità.
dkrynicki,

15

Ero curioso dell'esecuzione di diversi approcci ed eccolo qui:

Testato su Python 3.5.1

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([next(batchiter)], batchiter)


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

risultati:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
il benchmarking usando la timelibreria non è una grande idea quando abbiamo un timeitmodulo
Azat Ibrakov

13

codice:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print split_list(a_list, 3)

risultato:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

12

È inoltre possibile utilizzare la get_chunksfunzione di utilspielibreria come:

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

È possibile installare utilspietramite pip:

sudo pip install utilspie

Disclaimer: sono il creatore della libreria utilspie .


11

A questo punto, penso che abbiamo bisogno di un generatore ricorsivo , per ogni evenienza ...

In Python 2:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

In Python 3:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

Inoltre, in caso di massiccia invasione aliena, un generatore ricorsivo decorato potrebbe tornare utile:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

9

Con Assignment Expressions in Python 3.8 diventa abbastanza carino:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Funziona su un iterabile arbitrario, non solo su un elenco.

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

1
Questa è una nuova degna risposta a questa domanda. In realtà mi piace abbastanza. Sono scettico sulle espressioni di incarico, ma quando funzionano funzionano.
juanpa.arrivillaga,

7

eh, versione a una riga

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

36
Per favore, usa "def chunk" invece di "chunk = lambda". Funziona allo stesso modo. Una linea. Stesse caratteristiche. Molto più facile da leggere e comprendere per l'n00bz.
S. Lott,

4
@ S.Lott: non se l'n00bz proviene dallo schema: P questo non è un vero problema. c'è persino una parola chiave per google! quali altre funzionalità mostrano che evitiamo per il bene del n00bz? immagino che la resa non sia abbastanza imperativa / c-like per essere n00b amichevole neanche allora.
Janus Troelsen,

16
L'oggetto funzione risultante def chunkinvece di chunk=lambdaha .__ name__ l'attributo 'chunk' invece di '<lambda>'. Il nome specifico è più utile nei traceback.
Terry Jan Reedy,

1
@Alfe: Non sono sicuro che si possa chiamare una differenza semantica principale, ma se esiste o meno un nome utile in un traceback <lamba>è, almeno, una differenza notevole.
martineau,

1
Dopo averne testato alcuni per prestazioni, QUESTO è fantastico!
Sunny Patel,

7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

utilizzo:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for seq in split_seq(seq, 3):
    print seq

7

Un'altra versione più esplicita.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList

(12 set 2016) Questa risposta è la lingua più indipendente e più facile da leggere.
D Adams,

7

Senza chiamare len () che è buono per elenchi di grandi dimensioni:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

E questo è per iterables:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

Il sapore funzionale di quanto sopra:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

O:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

O:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))

16
Non c'è motivo di evitare len()elenchi di grandi dimensioni; è un'operazione a tempo costante.
Thomas Wouters,

7

Ecco un elenco di approcci aggiuntivi:

Dato

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Codice

La libreria standard

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Riferimenti

+ Una libreria di terze parti che implementa ricette itertools e altro ancora.> pip install more_itertools


6

Vedi questo riferimento

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

python3


3
Bello, ma rilascia elementi alla fine se la dimensione non corrisponde a un numero intero di blocchi, ad esempio zip(*[iter(range(7))]*3)restituisce [(0, 1, 2), (3, 4, 5)]e dimentica solo 6l'input.
Alfe,

6

Dal momento che tutti qui parlano di iteratori. boltonsha un metodo perfetto per questo, chiamato iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Produzione:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

Ma se non vuoi essere misericordioso nella memoria, puoi usare il vecchio stile e archiviare il pieno listin primo luogo con iterutils.chunked.


E questo funziona davvero indipendentemente dall'ordine in cui si guarda ai sottotitoli !!
Peter Gerdes,

6

Un'altra soluzione

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 

5
def chunks(iterable,n):
    """assumes n is an integer>0
    """
    iterable=iter(iterable)
    while True:
        result=[]
        for i in range(n):
            try:
                a=next(iterable)
            except StopIteration:
                break
            else:
                result.append(a)
        if result:
            yield result
        else:
            break

g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'

1
Anche se questo potrebbe non sembrare così breve o bello come molte delle risposte basate su itertools, questo in realtà funziona se si desidera stampare il secondo sotto-elenco prima di accedere al primo, cioè è possibile impostare i0 = next (g2); i1 = next (g2); e usa i1 prima di usare i0 e non si rompe !!
Peter Gerdes,

5

Prendi in considerazione l'uso di pezzi matplotlib.cbook

per esempio:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

Sembra che tu abbia creato accidentalmente due account. Puoi contattare il team per farli unire, il che ti consentirà di riguadagnare i privilegi di modifica diretta sui tuoi contributi.
Georgy,
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.