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


488

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?


3
Seu código não funcionará se o tamanho da lista não for múltiplo de quatro.
Pedro Henriques

5
Estou estendendo () a lista para que seu comprimento seja múltiplo de quatro antes que chegue tão longe.
Ben Blank

4
@ ΤΖΩΤΖΙΟΥ - As perguntas são muito semelhantes, mas não totalmente duplicadas. É "dividido em qualquer número de pedaços de tamanho N" vs. "dividido em N pedaços de qualquer tamanho". :-)
Ben Blank


Respostas:


340

Modificado na seção de receitas dos documentos de itertools do Python :

from itertools import zip_longest

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

Exemplo
No pseudocódigo para manter o exemplo conciso.

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

Nota: no Python 2 use em izip_longestvez de zip_longest.


67
Finalmente tive a chance de brincar com isso em uma sessão python. Para aqueles que estão tão confusos quanto eu, isso está alimentando o mesmo iterador izip_longest várias vezes, fazendo com que consuma valores sucessivos da mesma sequência em vez de valores listrados de sequências separadas. Eu amo isso!
11459 Ben Blank

6
Qual é a melhor maneira de filtrar novamente o valor de preenchimento? ([item para item em itens se item não for valor de preenchimento] para itens na garoupa (iterável))?
gotgenes

14
Suspeito que o desempenho dessa receita de garoupa para pedaços de 256k será muito ruim, porque izip_longestserá alimentado com argumentos de 256k.
precisa saber é o seguinte

13
Em vários lugares, os comentaristas dizem "quando finalmente descobri como isso funcionava ..." Talvez seja necessário um pouco de explicação. Particularmente o aspecto da lista de iteradores.
LondonRob

6
Existe uma maneira de usar isso, mas sem Noneencher o último pedaço?
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)

Simples. Fácil. Rápido. Funciona com qualquer sequência:

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
A versão do @Carlos Crasborn funciona para qualquer iterável (não apenas sequências como o código acima); é conciso e provavelmente tão rápido ou até mais rápido. Embora possa ser um pouco obscuro (claro) para pessoas não familiarizadas com o itertoolsmódulo.
JFS

1
Acordado. Esta é a maneira mais genérica e pitônica. Claro e conciso. (e trabalhos sobre app engine)
Matt Williamson

3
Observe que chunkerretorna a generator. Substitua o retorno para: return [...]para obter uma lista.
Dror

11
Em vez de escrever um edifício função e, em seguida, retornar um gerador, você também pode escrever um gerador diretamente, utilizando 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.
Alfe15

3
Observe que isso funciona apenas para sequências que suportam o acesso de itens por índice e não funcionam para iteradores genéricos, porque eles podem não suportar o __getitem__método.
Apollov

135

Sou fã de

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

Como se comporta se len (ints) não é um múltiplo do chunkSize?
PlsWork 17/02/19

3
O @AnnaVopureta chunkterá 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 .
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)

Outra maneira:

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 para a utilização de geradores, costuras como o mais "pythônico" fora de todas as soluções sugeridas
Sergey Golovchenko

7
É bastante longo e desajeitado para algo tão fácil, o que não é muito pitonico. Prefiro a versão de S. Lott
zenazn

4
@zenazn: isso funcionará em instâncias de gerador, o fatiamento não será #
Janus Troelsen

Além de trabalhar adequadamente com geradores e outros iteradores não fatiáveis, a primeira solução também não exige um valor de "preenchimento" se o pedaço final for menor que o sizeque às vezes é desejável.
Dano

1
Também +1 para geradores. Outras soluções requerem uma lenligação e, portanto, não funcionam em outros geradores.
Cuadue 10/04/2015


11

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:

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 %timeitno 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:

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

Simples, mas bem lento: 693 nós por loop

A melhor solução que eu poderia usar islicepara o loop interno:

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

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.

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

Eu realmente não gosto desta resposta, mas é significativamente mais rápida. 124 nós por loop


É possível reduzir o tempo de execução para a receita # 3 por ~ 10-15%, movendo-o para a camada C (omitindo itertoolsimportações; mapdeve ser PY3 mapou imap): 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 do fillvalueargumento; adicione uma primeira linha fillvalue = object()e altere a ifverificação para if i[-1] is fillvalue:e a linha que controla yield tuple(v for v in i if v is not fillvalue). Garante que nenhum valor iterablepode ser confundido com o valor do preenchimento.
ShadowRanger

Entre, grandes polegares para cima no # 4. Eu estava prestes a postar minha otimização nº 3 como uma resposta melhor (em termos de desempenho) do que a publicada até agora, mas com o ajuste para torná-lo confiável, resiliente, o nº 4 é executado duas vezes mais rápido que o nº 3 otimizado; Eu não esperava uma solução com loops no nível do Python (e nenhuma diferença algorítmica teórica AFAICT) vencesse. Eu assumo que o nº 3 perde devido às despesas de construção / iteração de isliceobjetos (o nº 3 vence se nfor 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.
ShadowRanger

Para o nº 4, a primeira ramificação da condicional só é obtida na última iteração (a tupla final). Em vez de reconstituir a tupla final, mais uma vez, cache o modulo do comprimento do iterable original no início e usar isso para cortar o preenchimento indesejada de izip_longestna tupla final: yield i[:modulo]. Além disso, para a argsvariá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 assumirmos None, o condicional pode se tornar if None in ipara ainda mais ciclos de clock.
Kumba

1
@ Kumba: Sua primeira sugestão assume que a entrada tem tamanho conhecido. Se for um iterador / gerador, não uma coleção com tamanho conhecido, não há nada para armazenar em cache. Não há motivo real para usar essa otimização de qualquer maneira; você está otimizando o caso incomum (o último yield), enquanto o caso comum não é afetado.
ShadowRanger

10

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.

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

Lista:

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

Conjunto:

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

Gerador:

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

8

Semelhante a outras propostas, mas não exatamente idêntico, eu gosto de fazê-lo desta maneira, porque é simples e fácil de ler:

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)

Dessa forma, você não receberá o último pedaço parcial. Se você deseja obter (9, None, None, None)o último pedaço, basta usar izip_longestfrom itertools.


pode ser melhorado comzip(*([it]*4))
Jean-François Fabre

@ Jean-François Fabre: do ponto de vista da legibilidade, não vejo isso como uma melhoria. E também é um pouco mais lento. É uma melhoria se você estiver jogando golfe, o que eu não sou.
kriss

não, eu não estou jogando golfe, mas e se você tiver 10 argumentos? Eu li esse construto em alguma página oficial. Mas é claro que não consigo encontrá-lo agora :)
Jean-François Fabre

@ Jean-François Fabre: se eu tiver 10 argumentos, ou um número variável de argumentos, é uma opção, mas eu prefiro escrever: zip (* (it,) * 10)
kriss

direita! foi o que li. não é o material da lista que eu inventei :)
Jean-François Fabre

8

Se você não se importa de usar um pacote externo, pode usar a iteration_utilities.grouperpartir de 1 . Ele suporta todos os iterables (não apenas sequências):iteration_utilties

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

que imprime:

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

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:

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)

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:

insira a descrição da imagem aqui

Pelo menos neste benchmark, o iteration_utilities.groupermelhor desempenho. Seguido pela abordagem de Craz .

O benchmark foi criado com 1 . O código usado para executar esse benchmark foi: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 Isenção de responsabilidade: sou o autor das bibliotecas iteration_utilitiese simple_benchmark.


7

Como ninguém o mencionou ainda, aqui está uma zip()solução:

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

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:

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

Ou use itertools.izip para retornar um iterador em vez de uma lista:

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

O preenchimento pode ser corrigido usando a resposta de @ ΤΖΩΤΖΙΟΥ :

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

Usar map () em vez de zip () corrige o problema de preenchimento na resposta de JF Sebastian:

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

Exemplo:

>>> 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
Isso é melhor tratado com itertools.izip_longest(Py2) / itertools.zip_longest(Py3); esse uso de mapé duplamente preterido e não está disponível no Py3 (você não pode passar Nonecomo a função de mapeador, e para quando o iterável mais curto se esgota, não o mais longo; não é preenchido).
ShadowRanger

4

Outra abordagem seria usar a forma de dois argumentos de iter:

from itertools import islice

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

Isso pode ser adaptado facilmente para usar o preenchimento (isso é semelhante à resposta de 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)

Eles podem até ser combinados para preenchimento opcional:

_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
preferível porque você tem a opção de omitir o preenchimento!
N611x007

3

Se a lista for grande, a melhor maneira de fazer isso será usar um gerador:

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

(Acho que a sugestão itertools de MizardX é funcionalmente equivalente a isso.)
Robert Rossney

1
(Na verdade, pensando bem, não, eu não itertools.islice retorna um iterador, mas ele não usa uma já existente..)
Robert Rossney

É agradável e simples, mas por alguma razão, mesmo sem conversão para tupla de 4-7 vezes mais lento do que o método garoupa Aceito em iterable = range(100000000)& chunksizeaté 10000.
Valentas

No entanto, em geral, eu recomendo este método, porque o aceite pode ser extremamente lento quando a verificação de último item é lento docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

Usar pequenas funções e coisas realmente não me atrai; Eu prefiro usar apenas fatias:

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

bom, mas não é bom para um fluxo indefinido que não é conhecido len. você pode fazer um teste com itertools.repeatou itertools.cycle.
N611x007

1
Além disso, consome memória, porque de usar uma [...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 sobressalente
n611x007

2

Para evitar todas as conversões em uma lista import itertoolse:

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

Produz:

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

Eu verifiquei groupbye ele não se converte em lista ou uso, lenentã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:

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

Meu interesse específico nisso foi a necessidade de consumir um gerador para enviar alterações em lotes de até 1000 à API do 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)

E se a lista que você está dividindo for algo diferente de uma sequência de números inteiros ascendentes?
21315 PaulMcG

@PaulMcGuire veja groupby ; dada uma função para descrever a ordem, os elementos do iterável podem ser qualquer coisa, certo?
John Mee

1
Sim, eu estou familiarizado com o groupby. Mas se as mensagens fossem as letras "ABCDEFG", 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.
21915 PaulMcG

2

Com o NumPy, é simples:

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

resultado:

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

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

No seu segundo método, eu avançaria para o próximo grupo de 4 fazendo o seguinte:

ints = ints[4:]

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.


1

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.

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 

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.


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 omite preenchimento; seu e bcoughlan é é muito semelhante
n611x007

1

Você pode usar partição ou pedaços funcionar a partir funcy biblioteca:

from funcy import partition

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

Essas funções também possuem versões do iterador ipartitione ichunks, o que será mais eficiente nesse caso.

Você também pode espiar a implementação deles .


1

Sobre a solução dada por J.F. Sebastian aqui :

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

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

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

Então

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

Exemplo de uso:

>>> 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
Não é uma crítica para você mudar sua resposta, mas um comentário: o código é um passivo. Quanto mais código você escreve, mais espaço você cria para os bugs ocultarem. Desse ponto de vista, reescrever em zipvez de usar o existente parece não ser a melhor idéia.
Alfe

1

Eu gosto dessa abordagem. Parece simples e não é mágico, suporta todos os tipos iteráveis ​​e não requer importações.

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

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.

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

Aqui está um chunker sem importações que suporta geradores:

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

Exemplo de uso:

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

Com o Python 3.8, você pode usar o operador morsa 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

Não parece haver uma maneira bonita de fazer isso. Aqui está uma página que possui vários métodos, incluindo:

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 as listas tiverem o mesmo tamanho, você poderá combiná-las em listas de 4 tuplas zip(). Por exemplo:

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

Aqui está o que a zip()função produz:

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

from itertools import izip

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

0

Solução adhoc de uma linha para iterar sobre uma lista xem pedaços de tamanho 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 ...
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.