Eu tenho um script Python que usa como entrada uma lista de números inteiros, que eu preciso trabalhar com quatro números inteiros por vez. Infelizmente, eu não tenho controle da entrada, ou ela seria passada como uma lista de tuplas de quatro elementos. Atualmente, eu estou iterando desta maneira:
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]
Parece muito com "C-think", no entanto, o que me faz suspeitar que há uma maneira mais pitônica de lidar com essa situação. A lista é descartada após a iteração, portanto, não precisa ser preservada. Talvez algo assim seria melhor?
while ints:
foo += ints[0] * ints[1] + ints[2] * ints[3]
ints[0:4] = []
Ainda não parece "certo", no entanto. : - /
Pergunta relacionada: Como você divide uma lista em partes iguais no Python?
Respostas:
Modificado na seção de receitas dos documentos de itertools do Python :
Exemplo
No pseudocódigo para manter o exemplo conciso.
Nota: no Python 2 use em
izip_longest
vez dezip_longest
.fonte
izip_longest
será alimentado com argumentos de 256k.None
encher o último pedaço?Simples. Fácil. Rápido. Funciona com qualquer sequência:
fonte
itertools
módulo.chunker
retorna agenerator
. Substitua o retorno para:return [...]
para obter uma lista.yield
:for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]
. Não tenho certeza se internamente isso seria tratado de maneira diferente em qualquer aspecto relevante, mas pode ser ainda um pouco mais claro.__getitem__
método.Sou fã de
fonte
chunk
terá 1, 2 ou 3 elementos para o último lote de elementos. Veja esta pergunta sobre por que os índices de fatia podem estar fora dos limites .Outra maneira:
fonte
size
que às vezes é desejável.len
ligação e, portanto, não funcionam em outros geradores.fonte
izip_longest
é substituído porzip_longest
A solução ideal para esse problema funciona com iteradores (não apenas sequências). Também deve ser rápido.
Esta é a solução fornecida pela documentação para itertools:
Usando ipython
%timeit
no meu mac book air, recebo 47,5 nós por loop.No entanto, isso realmente não funciona para mim, já que os resultados são preenchidos para formar grupos de tamanho uniforme. Uma solução sem o preenchimento é um pouco mais complicada. A solução mais ingênua pode ser:
Simples, mas bem lento: 693 nós por loop
A melhor solução que eu poderia usar
islice
para o loop interno:Com o mesmo conjunto de dados, recebo 305 nós por loop.
Não é possível obter uma solução pura mais rapidamente do que isso, forneço a seguinte solução com uma ressalva importante: Se os dados de entrada contiverem instâncias
filldata
, você poderá obter uma resposta errada.Eu realmente não gosto desta resposta, mas é significativamente mais rápida. 124 nós por loop
fonte
itertools
importações;map
deve ser PY3map
ouimap
):def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n)))))
. Sua função final pode ser menos quebradiça usando uma sentinela: livre-se dofillvalue
argumento; adicione uma primeira linhafillvalue = object()
e altere aif
verificação paraif i[-1] is fillvalue:
e a linha que controlayield tuple(v for v in i if v is not fillvalue)
. Garante que nenhum valoriterable
pode ser confundido com o valor do preenchimento.islice
objetos (o nº 3 vence sen
for relativamente grande, por exemplo, o número de grupos é pequeno, mas isso é otimizado para um caso incomum), mas eu não esperava que fosse exatamente isso extremo.izip_longest
na tupla final:yield i[:modulo]
. Além disso, para aargs
variável, tupla, em vez de uma lista:args = (iter(iterable),) * n
. Reduz mais alguns ciclos do relógio. Por fim, se ignorarmos o valor de preenchimento e assumirmosNone
, o condicional pode se tornarif None in i
para ainda mais ciclos de clock.yield
), enquanto o caso comum não é afetado.Eu precisava de uma solução que também funcionasse com conjuntos e geradores. Eu não conseguia pensar em nada muito curto e bonito, mas pelo menos é bastante legível.
Lista:
Conjunto:
Gerador:
fonte
Semelhante a outras propostas, mas não exatamente idêntico, eu gosto de fazê-lo desta maneira, porque é simples e fácil de ler:
Dessa forma, você não receberá o último pedaço parcial. Se você deseja obter
(9, None, None, None)
o último pedaço, basta usarizip_longest
fromitertools
.fonte
zip(*([it]*4))
Se você não se importa de usar um pacote externo, pode usar a
iteration_utilities.grouper
partir de 1 . Ele suporta todos os iterables (não apenas sequências):iteration_utilties
que imprime:
Caso o comprimento não seja múltiplo do tamanho do grupo, ele também suporta o preenchimento (o último grupo incompleto) ou truncamento (descartando o último grupo incompleto) o último:
Benchmarks
Também decidi comparar o tempo de execução de algumas das abordagens mencionadas. É um gráfico de log-log agrupado em grupos de "10" elementos com base em uma lista de tamanhos variados. Para resultados qualitativos: Menor significa mais rápido:
Pelo menos neste benchmark, o
iteration_utilities.grouper
melhor desempenho. Seguido pela abordagem de Craz .O benchmark foi criado com 1 . O código usado para executar esse benchmark foi:
simple_benchmark
1 Isenção de responsabilidade: sou o autor das bibliotecas
iteration_utilities
esimple_benchmark
.fonte
Como ninguém o mencionou ainda, aqui está uma
zip()
solução:Funciona apenas se o tamanho da sua sequência for sempre divisível pelo tamanho do pedaço ou se você não se importa com um pedaço à direita, se não for.
Exemplo:
Ou use itertools.izip para retornar um iterador em vez de uma lista:
O preenchimento pode ser corrigido usando a resposta de @ ΤΖΩΤΖΙΟΥ :
fonte
Usar map () em vez de zip () corrige o problema de preenchimento na resposta de JF Sebastian:
Exemplo:
fonte
itertools.izip_longest
(Py2) /itertools.zip_longest
(Py3); esse uso demap
é duplamente preterido e não está disponível no Py3 (você não pode passarNone
como a função de mapeador, e para quando o iterável mais curto se esgota, não o mais longo; não é preenchido).Outra abordagem seria usar a forma de dois argumentos de
iter
:Isso pode ser adaptado facilmente para usar o preenchimento (isso é semelhante à resposta de Markus Jarderot ):
Eles podem até ser combinados para preenchimento opcional:
fonte
Se a lista for grande, a melhor maneira de fazer isso será usar um gerador:
fonte
iterable = range(100000000)
&chunksize
até 10000.Usar pequenas funções e coisas realmente não me atrai; Eu prefiro usar apenas fatias:
fonte
len
. você pode fazer um teste comitertools.repeat
ouitertools.cycle
.[...for...]
compreensão de lista para construir fisicamente uma lista em vez de usar um(...for...)
gerador de expressão que apenas se preocupam com o próximo elemento e memória sobressalentePara evitar todas as conversões em uma lista
import itertools
e:Produz:
Eu verifiquei
groupby
e ele não se converte em lista ou uso,len
então eu acho que isso atrasará a resolução de cada valor até que seja realmente usado. Infelizmente, nenhuma das respostas disponíveis (no momento) parecia oferecer essa variação.Obviamente, se você precisar manipular cada item, anexe um loop for sobre g:
Meu interesse específico nisso foi a necessidade de consumir um gerador para enviar alterações em lotes de até 1000 à API do gmail:
fonte
groupby(messages, lambda x: x/3)
você forneceria um TypeError (para tentar dividir uma sequência por um int), não agrupamentos de três letras. Agora, se você fez,groupby(enumerate(messages), lambda x: x[0]/3)
você pode ter alguma coisa. Mas você não disse isso no seu post.Com o NumPy, é simples:
resultado:
fonte
fonte
A menos que eu perca algo, a seguinte solução simples com expressões geradoras não foi mencionada. Ele pressupõe que o tamanho e o número de blocos sejam conhecidos (o que geralmente ocorre) e que nenhum preenchimento é necessário:
fonte
No seu segundo método, eu avançaria para o próximo grupo de 4 fazendo o seguinte:
No entanto, não fiz nenhuma medição de desempenho, portanto não sei qual pode ser mais eficiente.
Dito isto, eu normalmente escolheria o primeiro método. Não é bonito, mas isso geralmente é uma conseqüência da interface com o mundo exterior.
fonte
Ainda outra resposta, cujas vantagens são:
1) Facilmente compreensível
2) Funciona em qualquer iterável, não apenas em seqüências (algumas das respostas acima ficam bloqueadas nas manipulações de arquivos)
3) Não carrega o pedaço na memória de uma só vez
4) Não faz uma lista longa de referências a o mesmo iterador na memória
5) Sem preenchimento dos valores de preenchimento no final da lista
Dito isto, não o cronometrei para que possa ser mais lento do que alguns dos métodos mais inteligentes e algumas das vantagens podem ser irrelevantes, dado o caso de uso.
Atualização:
algumas desvantagens devido ao fato de os loops internos e externos extraírem valores do mesmo iterador:
1) continue não funciona conforme o esperado no loop externo - ele apenas continua no próximo item em vez de ignorar um pedaço . No entanto, isso não parece ser um problema, pois não há nada para testar no loop externo.
2) break não funciona como esperado no loop interno - o controle será encerrado no loop interno novamente com o próximo item no iterador. Para pular pedaços inteiros, envolva o iterador interno (ii acima) em uma tupla, por exemplo
for c in tuple(ii)
, ou defina um sinalizador e esgote o iterador.fonte
fonte
Você pode usar partição ou pedaços funcionar a partir funcy biblioteca:
Essas funções também possuem versões do iterador
ipartition
eichunks
, o que será mais eficiente nesse caso.Você também pode espiar a implementação deles .
fonte
Sobre a solução dada por
J.F. Sebastian
aqui :É inteligente, mas tem uma desvantagem - sempre retorne a tupla. Como obter string em vez disso?
Claro que você pode escrever
''.join(chunker(...))
, mas a tupla temporária é construída de qualquer maneira.Você pode se livrar da tupla temporária escrevendo own
zip
, assim:Então
Exemplo de uso:
fonte
zip
vez de usar o existente parece não ser a melhor idéia.Eu gosto dessa abordagem. Parece simples e não é mágico, suporta todos os tipos iteráveis e não requer importações.
fonte
Eu nunca quero meus pedaços acolchoados, portanto esse requisito é essencial. Acho que a capacidade de trabalhar em qualquer iterável também é requisito. Dado isso, decidi estender a resposta aceita, https://stackoverflow.com/a/434411/1074659 .
O desempenho sofre um pequeno impacto nessa abordagem se o preenchimento não for desejado devido à necessidade de comparar e filtrar os valores preenchidos. No entanto, para tamanhos grandes de blocos, esse utilitário é muito eficiente.
fonte
Aqui está um chunker sem importações que suporta geradores:
Exemplo de uso:
fonte
Com o Python 3.8, você pode usar o operador morsa e
itertools.islice
.fonte
Não parece haver uma maneira bonita de fazer isso. Aqui está uma página que possui vários métodos, incluindo:
fonte
Se as listas tiverem o mesmo tamanho, você poderá combiná-las em listas de 4 tuplas
zip()
. Por exemplo:Aqui está o que a
zip()
função produz:Se as listas forem grandes e você não quiser combiná-las em uma lista maior, use
itertools.izip()
, que produz um iterador, em vez de uma lista.fonte
Solução adhoc de uma linha para iterar sobre uma lista
x
em pedaços de tamanho4
-fonte