Como você divide uma lista em partes iguais?

2268

Eu tenho uma lista de tamanho arbitrário e preciso dividi-la em partes do mesmo tamanho e operar com ela. Existem algumas maneiras óbvias de fazer isso, como manter um contador e duas listas e, quando a segunda lista for preenchida, adicione-a à primeira lista e esvazie a segunda lista para a próxima rodada de dados, mas isso é potencialmente extremamente caro.

Fiquei me perguntando se alguém tinha uma boa solução para isso para listas de qualquer tamanho, por exemplo, usando geradores.

Eu estava procurando por algo útil, itertoolsmas não consegui encontrar nada obviamente útil. Pode ter perdido, no entanto.

Pergunta relacionada: Qual é a maneira mais “pitônica” de iterar sobre uma lista em pedaços?

jespern
fonte
1
Antes de postar uma nova resposta, considere que já existem mais de 60 respostas para esta pergunta. Por favor, verifique se sua resposta contribui com informações que não estão entre as existentes.
janniks 03/02
Para usuários que desejam evitar uma parte final arbitrariamente pequena, consulte Dividir uma lista em N partes de comprimento aproximadamente igual
wim

Respostas:

3151

Aqui está um gerador que produz os pedaços que você deseja:

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 você estiver usando o Python 2, use em xrange()vez de 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]

Além disso, você pode simplesmente usar a compreensão da lista em vez de escrever uma função, embora seja uma boa idéia encapsular operações como esta em funções nomeadas para que seu código seja mais fácil de entender. Python 3:

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

Versão do Python 2:

[lst[i:i + n] for i in xrange(0, len(lst), n)]
Ned Batchelder
fonte
72
O que acontece se não soubermos o comprimento da lista? Tente isso em itertools.repeat ([1, 2, 3]), por exemplo
jespern 23/11/08
47
Essa é uma extensão interessante da pergunta, mas a pergunta original claramente perguntou sobre como operar em uma lista.
Ned Batchelder
33
Esta função precisa ser na biblioteca padrão maldita
dgan
6
@Calimo: o que você sugere? Entrego uma lista com 47 elementos. Como você gostaria de dividi-lo em "pedaços de tamanho uniforme"? O OP aceitou a resposta, portanto está claramente bem com o último pedaço de tamanho diferente. Talvez a frase em inglês seja imprecisa?
Ned Batchelder
8
Por favor, não nomeie suas variáveis ​​l, ela se parece exatamente com 1 e é confusa. As pessoas estão copiando seu código e acham que está tudo bem.
Yasen
555

Se você quer algo super simples:

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

Use em xrange()vez de range()no caso do Python 2.x

oremj
fonte
6
Ou (se estivermos fazendo representações diferentes dessa função específica), você poderá definir uma função lambda via: lambda x, y: [x [i: i + y] para i no intervalo (0, len (x), y) ] Eu amo esse método de compreensão de lista!
JP
4
após o retorno, deve haver [, não (
alwbtc
2
"Super simples" significa não ter que depurar loops infinitos - parabéns pelo max().
Bob Stein
não há nada simples sobre esta solução
mit
1
@Nhoj_Gonk Opa, não é um loop infinito, mas pedaços (L, 0) aumentariam um ValueError sem o max (). Em vez disso, o max () transforma qualquer coisa menor que 1 em um 1.
Bob Stein
295

Diretamente da documentação do Python (antiga) (receitas para 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)

A versão atual, conforme sugerido por 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)

Acho que a máquina do tempo de Guido funciona - funcionou - funcionará - terá funcionado - estava funcionando novamente.

Essas soluções funcionam porque [iter(iterable)]*n(ou o equivalente na versão anterior) cria um iterador, repetidas nvezes na lista. izip_longestefetivamente executa um round-robin de "cada" iterador; como esse é o mesmo iterador, ele é avançado em cada chamada, resultando em cada zip-roundrobin gerando uma tupla de nitens.

tzot
fonte
@ ninjagecko: list(grouper(3, range(10)))retorna [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], e todas as tuplas são de comprimento 3. Por favor, elabore seu comentário porque eu não consigo entender; como você chama uma coisa e como você define que ela é um múltiplo de 3 em "esperar que sua coisa seja um múltiplo de 3"? Agradeço antecipadamente.
tzot
14
votou positivo porque funciona em geradores (sem len) e usa o módulo de ferramentas geralmente mais rápido.
Michael Dillon
88
Um exemplo clássico de fantasia itertoolsabordagem funcional transformar alguns lama ilegível, quando comparado com uma implementação python pura ingenuidade simples e
wim
15
@wim Dado que essa resposta começou como um trecho da documentação do Python, sugiro que você abra um problema no bugs.python.org .
tzot
1
@pedrosaurio se l==[1, 2, 3], em seguida, f(*l)é equivalente a f(1, 2, 3). Veja essa pergunta e a documentação oficial .
tzot 21/08/19
226

Eu sei que isso é meio velho, mas ninguém ainda mencionou 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])]
Moj
fonte
12
Isso permite que você defina o número total de partes, não o número de elementos por parte.
FizxMike 9/09/15
6
você pode fazer as contas sozinho. Se você tem 10 elementos que você pode agrupá-los em 2, 5 elementos pedaços ou cinco 2 elementos pedaços
Moj
24
+1 Esta é a minha solução favorita, pois divide a matriz em matrizes de tamanho uniforme , enquanto outras soluções não (em todas as outras soluções que observei, a última matriz pode ser arbitrariamente pequena).
MiniQuark
@MiniQuark, mas o que isso faz quando o número de blocos não é um fator do tamanho da matriz original?
Baldrickk
1
@Baldrickk Se você dividir N elementos em K pedaços, os primeiros N% K pedaços terão N // K + 1 elementos e o restante terá N // K elementos. Por exemplo, se você dividir uma matriz contendo 108 elementos em 5 pedaços, os primeiros 108% 5 = 3 pedaços conterão 108 // 5 + 1 = 22 elementos e o restante dos pedaços terá 108 // 5 = 21 elementos.
MiniQuark
147

Estou surpreso que ninguém tenha pensado em usar itera forma de dois argumentos :

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

Isso funciona com qualquer iterável e produz resultados preguiçosamente. Ele retorna tuplas em vez de iteradores, mas acho que, apesar disso, tem uma certa elegância. Também não acolchoa; se você quiser preenchimento, basta uma simples variação no item acima:

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

Como as izip_longestsoluções baseadas, o acima sempre se aplica. Até onde eu sei, não há receita de ferramentas de uma ou duas linhas para uma função que é opcional . Combinando as duas abordagens acima, essa chega bem perto:

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

Acredito que este seja o menor chunker proposto que oferece preenchimento opcional.

Como Tomasz Gandor observou , os dois chunkers de preenchimento irão parar inesperadamente se encontrarem uma longa sequência de valores de preenchimento. Aqui está uma variação final que soluciona esse problema de maneira razoável:

_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)]
remetente
fonte
7
Maravilhoso, sua versão simples é a minha favorita. Outros também criaram a islice(it, size)expressão básica e a incorporaram (como eu havia feito) em uma construção de loop. Somente você pensou na versão de dois argumentos iter()(eu desconhecia completamente), o que a torna super elegante (e provavelmente mais eficaz em termos de desempenho). Eu não tinha ideia de que o primeiro argumento itermuda para uma função de 0 quando recebe o sentinela. Você retorna um iterador (pote. Infinito) de blocos, pode usar um iterador (pote. Infinito) como entrada, len()sem fatias de matriz e sem. Impressionante!
ThomasH
1
É por isso que eu leio as respostas em vez de escanear apenas os melhores. O preenchimento opcional era um requisito no meu caso, e eu também aprendi sobre a forma de iter de dois argumentos.
Kerr
Eu votei isso de positivo, mas ainda assim - não vamos exagerar! Primeiro de tudo, lambda pode ser ruim (fechamento lenta ao longo de ititerador segundo lugar, e mais importanlty -. Você vai acabar prematuramente se um pedaço de padvalrealmente existe em seu iterable, e deve ser processado.
Tomasz Gandor
@ TomaszGandor, eu entendo o seu primeiro ponto! Embora meu entendimento seja de que o lambda não é mais lento que uma função comum, é claro que você está certo que a chamada de função e a pesquisa de fechamento retardarão isso. Não sei qual seria o impacto relativo no desempenho em relação à izip_longestabordagem, por exemplo - suspeito que possa ser uma troca complexa. Mas ... o padvalproblema não é compartilhado por todas as respostas aqui que oferecem um padvalparâmetro?
Senderle
1
@TomaszGandor, bastante justo! Mas não foi muito difícil criar uma versão que corrija isso. (Além disso, nota que a primeira versão, que utiliza ()como a sentinela, faz trabalho corretamente Isto porque. tuple(islice(it, size))Rendimentos ()quando itestá vazio.)
senderle
93

Aqui está um gerador que trabalha em iterables arbitrários:

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

Exemplo:

>>> 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]]
Markus Jarderot
fonte
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Tomasz Wysocki
fonte
map(None, iter)é igual izip_longest(iter).
Thomas Ahle
1
@TomaszWysocki Você pode explicar a *tupla do iterador à sua frente? Possivelmente no seu texto de resposta, mas notei que isso foi *usado dessa maneira em Python antes. Obrigado!
theJollySin
1
@theJollySin Nesse contexto, é chamado de operador splat. Seu uso é explicado aqui - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
Rlms
2
Feche, mas o último pedaço possui Nenhum elemento para preenchê-lo. Isso pode ou não ser um defeito. Padrão muito legal embora.
49

Simples, mas elegante

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

ou se você preferir:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
lebenf
fonte
18
Não dublarás uma variável à semelhança de um número árabe. Em algumas fontes, 1e lsão indistinguíveis. Como são 0e O. E às vezes até Ie 1.
Alfe
14
@ Fontes defeituosas. As pessoas não devem usar essas fontes. Não para programação, não para nada .
Jerry B
17
As lambdas devem ser usadas como funções sem nome. Não faz sentido usá-los assim. Além disso, torna a depuração mais difícil, pois o traceback relatará "em <lambda>" em vez de "em pedaços" em caso de erro. Desejo-lhe sorte para encontrar um problema se você tem todo grupo destes :)
Chris Koston
1
deve ser 0 e não 1 dentro de xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
NOTA: Para usuários do Python 3, use range.
Christian Dean
40

Crítica de outras respostas aqui:

Nenhuma dessas respostas é de tamanho uniforme; todas deixam um pedaço de acréscimo no final, para que não fiquem completamente equilibradas. Se você estava usando essas funções para distribuir trabalho, incorporou a perspectiva de uma provável conclusão bem antes das outras, para que ela permanecesse sem fazer nada enquanto as outras continuassem trabalhando duro.

Por exemplo, a resposta principal atual termina com:

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

Eu apenas odeio esse idiota no final!

Outros, como list(grouper(3, xrange(7))), e chunk(xrange(7), 3)tanto retorno: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Eles Nonesão apenas estofados e bastante deselegantes na minha opinião. Eles NÃO estão uniformemente dividindo os iteráveis.

Por que não podemos dividir isso melhor?

Minha (s) solução (s)

Aqui está uma solução equilibrada, adaptada de uma função que eu usei na produção (Nota em Python 3 para substituir xrangecom 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 eu criei um gerador que faz o mesmo se você o colocar em uma lista:

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, finalmente, como vejo que todas as funções acima retornam elementos em uma ordem contígua (como foram dadas):

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

Resultado

Para testá-los:

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

Que imprime:

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

Observe que o gerador contíguo fornece blocos nos mesmos padrões de comprimento que os outros dois, mas os itens estão todos em ordem e são tão uniformemente divididos quanto se pode dividir uma lista de elementos discretos.

Aaron Hall
fonte
Você diz que nenhuma das opções acima fornece pedaços de tamanho uniforme. Mas este faz, assim como este .
Senderle
1
@senderle, O primeiro, list(grouper(3, xrange(7)))e o segundo, chunk(xrange(7), 3)ambos retorno: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Eles Nonesão apenas estofados e bastante deselegantes na minha opinião. Eles NÃO estão uniformemente dividindo os iteráveis. Obrigado pelo seu voto!
Aaron Hall
4
Você levanta a questão (sem fazê-lo explicitamente, então faço isso agora) se pedaços de tamanho igual (exceto o último, se não possível) ou se um resultado equilibrado (o melhor possível) é o mais necessário. Você assume que a solução equilibrada é preferir; isso pode ser verdade se o que você programa estiver próximo do mundo real (por exemplo, um algoritmo de negociação de cartas para um jogo de cartas simulado). Em outros casos (como preencher linhas com palavras), é preferível manter as linhas o mais cheias possível. Então, eu realmente não posso preferir um sobre o outro; eles são apenas para diferentes casos de uso.
Alfe
@ ChristopherBarrington-Leigh Bom ponto, para DataFrames, você provavelmente deve usar fatias, já que acredito que os objetos DataFrame não costumam copiar no fatiamento, por exemploimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall
1
@AaronHall Oops. Excluí meu comentário porque adivinhei minha crítica, mas você foi rápido no empate. Obrigado! De fato, minha afirmação de que não funciona para quadros de dados é verdadeira. Se itens for um quadro de dados, use apenas itens de rendimento [intervalo (x_i, número de itens, cestas)] como a última linha. Ofereci uma resposta separada (mais uma), na qual você especifica o tamanho de grupo (mínimo) desejado.
CPBL
38

Eu vi a resposta mais impressionante do Python-ish em uma duplicata desta pergunta:

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

Você pode criar n-tupla para qualquer n. Se a = range(1, 15), então o resultado será:

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

Se a lista estiver dividida igualmente, você poderá substituí-lo zip_longestpor zip, caso contrário, o trigêmeo (13, 14, None)seria perdido. Python 3 é usado acima. Para Python 2, use izip_longest.

Noich
fonte
isso é bom se sua lista e partes são curtas, como você pode adaptar isso para dividir sua lista em partes de 1000? você "não vai zip code (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith
9
zip(i, i, i, ... i)com argumentos "chunk_size", zip () pode ser escrito como zip(*[i]*chunk_size)se é uma boa ideia ou não, é discutível, é claro.
Wilson F
1
A desvantagem disso é que, se você não estiver dividindo uniformemente, soltará elementos, pois o zip pára no mínimo iterável - & izip_longest adicionaria elementos padrão.
Aaron Hall
zip_longestdeve ser usado, como feito em: stackoverflow.com/a/434411/1959808
Ioannis Filippidis
A resposta com range(1, 15)já está faltando elementos, porque existem 14 elementos em range(1, 15), não 15.
Ioannis Filippidis
35

Se você souber o tamanho da lista:

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

Caso contrário (um iterador):

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

No último caso, ele pode ser reformulado de uma maneira mais bonita, se você tiver certeza de que a sequência sempre contém um número inteiro de pedaços de determinado tamanho (ou seja, não há um último pedaço incompleto).

atzz
fonte
Estou triste que isso esteja enterrado tão longe. O IterChunks funciona para tudo e é a solução geral e não possui ressalvas que eu conheça.
Jason Dunkelberger
18

A biblioteca toolz tem a partitionfunção para isso:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
zach
fonte
Parece a mais simples de todas as sugestões. Só estou me perguntando se realmente pode ser verdade que é preciso usar uma biblioteca de terceiros para obter essa função de partição. Eu esperava que algo equivalente a essa função de partição existisse como uma linguagem embutida.
kasperd
1
você pode fazer uma partição com itertools. mas eu gosto da biblioteca toolz. é uma biblioteca inspirada em clojure para trabalhar em coleções em um estilo funcional. você não obtém imutabilidade, mas possui um pequeno vocabulário para trabalhar em coleções simples. Além disso, o cytoolz é escrito em cython e obtém um bom desempenho. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach
O link do comentário de zach funcionará se você omitir a barra final: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
mit
17

Se você tivesse um tamanho de 3, por exemplo, você poderia:

zip(*[iterable[i::3] for i in range(3)]) 

fonte: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/

Eu usaria isso quando meu tamanho de bloco for um número fixo que eu possa digitar, por exemplo, '3', e nunca mudaria.

ninjagecko
fonte
11
Isso não funciona se len (iterável)% 3! = 0. O último (curto) grupo de números não será retornado.
sherbang
16

Eu gosto muito da versão do doc do Python proposta pelo tzot e pelo JFSebastian, mas tem duas deficiências:

  • não é muito explícito
  • Eu normalmente não quero um valor de preenchimento no último pedaço

Estou usando muito este no meu código:

from itertools import islice

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

ATUALIZAÇÃO: Uma versão preguiçosa dos pedaços:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
Nikipore
fonte
Qual é a condição de interrupção para o while Trueloop?
wjandrea 6/09/19
@wjandrea: O StopIterationgerado quando o tupleestá vazio e iterable.next()é executado. Porém, não funciona corretamente no Python moderno, onde a saída de um gerador deve ser feita return, e não a elevação StopIteration. Uma try/except StopIteration: returnvolta ao redor de todo o loop (e a alteração iterable.next()para next(iterable)compatibilidade entre versões) corrige isso com uma sobrecarga mínima, pelo menos.
ShadowRanger 22/01
15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Onde AA é matriz, SS é tamanho de bloco. Por exemplo:

>>> 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
Riaz Rizvi
fonte
2
é o melhor e simples.
F.Tamy
2
curto e simples. simplicidade sobre complexidade.
dkrynicki 28/01
15

Fiquei curioso sobre o desempenho de diferentes abordagens e aqui está:

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

Resultados:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844
Alex T
fonte
3
aferição utilizando timebiblioteca não é uma ótima idéia quando temos timeitmódulo
Azat Ibrakov
13

código:

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)

resultado:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
Art B
fonte
12

Você também pode usar a get_chunksfunção da utilspiebiblioteca como:

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

Você pode instalar utilspievia pip:

sudo pip install utilspie

Disclaimer: Eu sou o criador da biblioteca utilspie .

Moinuddin Quadri
fonte
11

Neste ponto, acho que precisamos de um gerador recursivo , apenas no caso de ...

No python 2:

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

No python 3:

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

Além disso, em caso de invasão massiva de alienígenas, um gerador recursivo decorado pode se tornar útil:

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
Mazieres
fonte
9

Com as Expressões de atribuição no Python 3.8, torna-se bastante agradável:

import itertools

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

Isso funciona em um iterável arbitrário, não apenas em uma lista.

>>> 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]]
nirvana-msu
fonte
1
Agora, esta é uma nova resposta digna para esta pergunta. Na verdade, eu gosto disso. Sou cético em relação às expressões de atribuição, mas quando elas funcionam, elas funcionam.
juanpa.arrivillaga
7

heh, versão de uma linha

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]]
slav0nic
fonte
36
Por favor, use "def chunk" em vez de "chunk = lambda". Funciona da mesma maneira. Uma linha. Mesmos recursos. MUITO mais fácil para o n00bz ler e entender.
24568 S.Lott
4
@ S.Lott: não se o n00bz vier do esquema: P isso não é um problema real. existe até uma palavra-chave no google! que outros recursos mostram que evitamos pelo n00bz? Eu acho que o rendimento não é imperativo / c-like o suficiente para ser n00b amigável também.
Janus Troelsen
16
O objeto de função resultante de em def chunkvez de chunk=lambdapossui o atributo .__ name__ 'chunk' em vez de '<lambda>'. O nome específico é mais útil no rastreamento.
Terry Jan Reedy
1
@ Alfe: Não tenho certeza se isso poderia ser chamado de principal diferença semântica, mas se existe um nome útil em um traceback em vez de <lamba>ou não é, pelo menos, uma diferença notável.
22680 martineau
1
Depois de testar vários deles para desempenho, isso é ótimo!
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

uso:

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

for seq in split_seq(seq, 3):
    print seq
Corey Goldberg
fonte
7

Outra versão mais explícita.

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
Ranaivo
fonte
(12 de setembro de 2016) Esta resposta é a mais independente de idioma e mais fácil de ler.
D #
7

Sem chamar len (), o que é bom para grandes listas:

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

E isso é para iterables:

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

O sabor funcional do acima exposto:

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

OU:

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

OU:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))
Marte
fonte
16
Não há razão para evitar len()em grandes listas; é uma operação de tempo constante.
Thomas Wouters
7

Aqui está uma lista de abordagens adicionais:

Dado

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Código

A Biblioteca Padrão

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

Referências

+ Uma biblioteca de terceiros que implementa receitas de ferramentas e muito mais.> pip install more_itertools

pylang
fonte
6

Veja esta referência

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

macm
fonte
3
Bom, mas descarta elementos no final se o tamanho não corresponder a um número inteiro de pedaços, por exemplo, zip(*[iter(range(7))]*3)apenas retorna [(0, 1, 2), (3, 4, 5)]e esquece o 6da entrada.
Alfe 14/08/13
6

Já que todo mundo aqui está falando sobre iteradores. boltonstem um método perfeito para isso, chamado iterutils.chunked_iter.

from boltons import iterutils

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

Resultado:

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

Mas se você não quer ter piedade da memória, pode usar o modo antigo e armazenar o máximo listem primeiro lugar iterutils.chunked.

vishes_shell
fonte
E este realmente funciona independentemente da ordem que se olha para os subiteradores !!
Peter Gerdes
6

Mais uma solução

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]
>>> 
Анатолий Панин
fonte
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]]'
Robert King
fonte
1
Embora isso possa não parecer tão curto ou tão bonito quanto muitas das respostas baseadas em itertools, essa funciona realmente se você quiser imprimir a segunda sub-lista antes de acessar a primeira, ou seja, você pode definir i0 = next (g2); i1 = próximo (g2); e use i1 antes de usar i0 e ele não quebra !!
Peter Gerdes
5

Considere usar partes do matplotlib.cbook

por exemplo:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
Schwater
fonte
Parece que você criou acidentalmente duas contas. Você pode entrar em contato com a equipe para mesclá-los, o que permitirá recuperar privilégios de edição direta em suas contribuições.
Georgy