Pergunta

Eu tenho um script Python que toma como entrada uma lista de inteiros, o que eu preciso trabalhar com quatro inteiros de cada vez. Infelizmente, eu não tenho o controle da entrada, ou eu tê-lo passado como uma lista de tuplas de quatro elementos. Atualmente, estou Iterando sobre ele 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]

Ele se parece muito com "C-pensar", embora, que me faz suspeitar que há uma maneira mais Python de lidar com esta situação. A lista é descartado após a iteração, por isso não precisa ser preservada. Talvez algo como isso seria melhor?

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

Ainda não bastante "sente" certo, no entanto. : - /

questão relacionada: Como fazer você dividir uma lista em pedaços de tamanho uniforme em Python?

Foi útil?

Solução

modificado a partir da href="https://docs.python.org/library/itertools.html#itertools-recipes" rel="noreferrer"> receitas seção de Python itertools docs:

from itertools import zip_longest

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

Exemplo
Em pseudocódigo para manter o exemplo concisa.

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

Nota:. em Python 2 uso izip_longest vez de zip_longest

Outras dicas

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

Eu sou um 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
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 forma:

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
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)

Eu precisava de uma solução que também trabalham com conjuntos e geradores. Eu não conseguia pensar em nada muito curto e muito, mas é bastante legível, pelo menos.

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

Set:

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

Semelhante a outras propostas, mas não exatamente iguais, 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)

Desta forma, você não terá o último pedaço parcial. Se você deseja obter (9, None, None, None) com a última parcela, basta usar izip_longest de itertools.

A solução ideal para este problema trabalha com iteradores (não apenas as sequências). Ele 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 %timeit de ipython no meu ar mac book, eu recebo 47,5 nós por loop.

No entanto, isso realmente não funciona para mim desde que os resultados são acolchoada para ser grupos mesmo porte. Uma solução, sem o preenchimento é ligeiramente mais complicado. A solução mais simples 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 muito lento: 693 nós por laço

A melhor solução que eu poderia vir acima com usos islice para 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 rápido do que isso, eu forneço a seguinte solução com uma ressalva importante:. Se os dados de entrada tem instâncias de filldata nele, você pode obter 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 dessa resposta, mas é significativamente mais rápido. 124 nós por laço

Uma vez que ninguém mencionou ainda aqui está uma solução zip():

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

Ele só funciona se o comprimento de sua seqüência é sempre divisível pelo tamanho do bloco ou você não se preocupam com um pedaço de fuga se não é.

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 usando 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 @ do ??O????? resposta :

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

Usando o mapa () em vez de zip () corrige o problema de preenchimento na resposta de J.F. 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')]

Se você não se importa com um pacote externo que você poderia usar iteration_utilities.grouper de iteration_utilties 1 . Ele suporta todos os iterables (e não apenas as sequências):

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

que impressões:

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

No caso do comprimento não é um múltiplo do groupsize que também suporta o enchimento (o último grupo incompleto) ou truncando (descartando o último grupo incompleta) a última:

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)

1 Disclaimer:. Eu sou o autor desse pacote

Se a lista é grande, o caminho mais alto desempenho para fazer isso é 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,)

Usando pequenas funções e as coisas realmente não apelar para mim; Eu prefiro fatias usar apenas:

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

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

Isto pode ser facilmente adaptado para usar preenchimento (isto é semelhante à resposta Markus Jarderot 's):

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)

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

Com o NumPy-lo simples:

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

saída:

1 2
3 4
5 6
7 8

A menos que eu sente falta de alguma coisa, a seguinte solução simples com gerador de expressões não foi mencionado. Assume-se que tanto o tamanho eo número de pedaços são conhecidos (que é frequentemente o caso), e que não é necessária qualquer padding:

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

Em seu segundo método, gostaria de avançar para o próximo grupo de 4 por fazer isso:

ints = ints[4:]

No entanto, eu não fiz qualquer medição de desempenho, então eu não sei qual deles pode ser mais eficiente.

Dito isto, eu normalmente escolher o primeiro método. Não é bonito, mas é muitas vezes consequência de interface com o mundo exterior.

No entanto, outra resposta, cujas vantagens são:

1) facilmente compreensível
2) funciona em qualquer iteráveis, não apenas seqüências (algumas das respostas acima vai engasgar com filehandles)
3) Não carregar o trecho na memória de uma só vez
4) Não faça uma lista pedaço de comprimento de referências ao mesmo iterador na memória
5) No preenchimento dos valores de preenchimento no final da lista

Dito isto, eu não programado ele para que ele possa ser mais lento do que alguns dos métodos mais inteligentes, e algumas das vantagens pode ser irrelevante 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 

Update:
Um par de desvantagens devido ao facto das espiras internas e externas estão puxando os valores a partir do mesmo iteração:
1) continuar não funciona como esperado no loop externo - ele só continua para o próximo item, em vez de pular um pedaço. No entanto, este não parece ser um problema, pois não há nada para teste no circuito exterior.
2) pausa não funciona como esperado no laço interno - controle vai acabar no circuito interno novamente com o próximo item no iterador. Para passar pedaços inteiros, quer envolva a iteração interior (ii acima) em um tuplo, v.g. for c in tuple(ii), ou definir um sinalizador e esgotar o iterador.

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

Você pode usar partição ou pedaços função de funcy biblioteca:

from funcy import partition

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

Estas funções também tem iteradoras versões ipartition e ichunks, que será mais eficiente neste caso.

Você também pode espreitar sua implementação .

Para evitar todas as conversões a uma lista import itertools e:

>>> 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 groupby e não converter a lista ou o uso len então eu (acho que) isso vai atrasar resolução de cada valor até que seja realmente utilizado. Infelizmente nenhuma das respostas disponíveis (neste momento) parecia oferecer esta variação.

Obviamente, se você precisa lidar com cada item, por sua vez um ninho para varrer 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 nesta era a necessidade de consumir um gerador para enviar alterações em lotes de até 1000 para a 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)

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

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

É inteligente, mas tem uma desvantagem - sempre voltar tupla. Como chegar cadeia em vez
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 própria zip, como este:

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

Em seguida

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

Exemplo de utilização:

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

Eu gosto desta abordagem. Parece simples e não mágico e 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

Eu nunca quero que meus pedaços acolchoado, para que exigência é essencial. Acho que a capacidade de trabalhar em qualquer iterable também é exigência. Dado que, eu decidi estender na resposta aceita, https://stackoverflow.com/a/434411/1074659 .

Performance leva uma ligeira sucesso nesta abordagem, se o preenchimento não é desejado, devido à necessidade de comparar e filtrar os valores acolchoados. No entanto, para grandes tamanhos do pedaço, este utilitário é muito alto desempenho.

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

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

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 utilização:

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

Não parece ser uma maneira bonita de fazer isso. Aqui é uma página que tem uma série de 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

Se as listas são do mesmo tamanho, você pode combiná-los em listas de 4-tuplas com 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 função zip() 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 são grandes, e você não quer combiná-los em uma lista maior, uso 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):
    ...

One-liner, solução ad hoc para iterar sobre uma lista x em 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 ...

Em primeiro lugar, eu projetei-lo para cordas divididas em substrings para analisar string contendo hex.
Hoje eu transformou-o em complexo, mas ainda gerador simples.

def chunker(iterable, size, reductor, condition):
    it = iter(iterable)
    def chunk_generator():
        return (next(it) for _ in range(size))
    chunk = reductor(chunk_generator())
    while condition(chunk):
        yield chunk
        chunk = reductor(chunk_generator())

Argumentos:

óbvios

  • iterable é qualquer iteráveis ??/ iteração / gerador containg / gerador / iteração sobre os dados de entrada,
  • size é, naturalmente, o tamanho do pedaço que você quer obter,

Mais interessante

  • reductor é um exigível, que recebe a iteração gerador sobre o conteúdo do pedaço.
    Eu esperaria que ele retorne seqüência ou string, mas eu não exigem isso.

    Você pode passar como este argumento, por exemplo list, tuple, set, frozenset,
    ou mais extravagante nada. Eu passaria esta função, retornar string
    (Desde que contém iterable / gera itera sobre cordas /):

    def concatenate(iterable):
        return ''.join(iterable)
    

    Note que reductor pode causar fechamento gerador elevando exceção.

  • condition é um exigível que recebe tudo o que reductor retornado.
    Ele decide aprovar e deu-lo (por retornando nada avaliando a True),
    ou recusá-la e trabalho de gerador de acabamento (retornando outra coisa ou levantar exceção).

    Quando o número de elementos em iterable não é divisível por size, quando it fica exausto, reductor receberá gerador gerando menos elementos do que size.
    Vamos chamar esses elementos dura elementos .

    Convidei duas funções para passar como este argumento:

    • lambda x:x -. dura elementos serão cedidos

    • lambda x: len(x)==<size> - a dura elementos será rejeitada
      . substituir <size> usando número igual ao size

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top