Какой самый «питонный» способ перебрать список по частям? - PullRequest
407 голосов
/ 12 января 2009

У меня есть скрипт Python, который принимает в качестве входных данных список целых чисел, которые мне нужно работать с четырьмя целыми числами одновременно. К сожалению, у меня нет контроля над входом, или я бы передал его в виде списка из четырех элементов. В настоящее время я повторяю это так:

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]

Это похоже на "C-think", что заставляет меня подозревать, что есть более питонический способ справиться с этой ситуацией. Список отбрасывается после итерации, поэтому его не нужно сохранять. Возможно, что-то вроде этого будет лучше?

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

Тем не менее, все еще не совсем "чувствую" себя хорошо. : - /

Смежный вопрос: Как разбить список на куски одинакового размера в Python?

Ответы [ 35 ]

1 голос
/ 20 февраля 2014
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 голос
/ 20 июля 2017

Я никогда не хочу, чтобы мои куски были набиты, так что требование является обязательным. Я считаю, что умение работать на любом итерируемом также является требованием. Учитывая это, я решил расширить принятый ответ: https://stackoverflow.com/a/434411/1074659.

При таком подходе производительность незначительно падает, если заполнение не требуется из-за необходимости сравнивать и фильтровать дополненные значения. Однако для больших размеров блоков эта утилита очень эффективна.

#!/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 голос
/ 25 февраля 2016
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()
1 голос
/ 09 июня 2017

Мне нравится этот подход. Он прост и не волшебен, поддерживает все повторяемые типы и не требует импорта.

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 голос
/ 10 апреля 2015

Вот чанкер без импорта, который поддерживает генераторы:

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

Пример использования:

>>> 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 голос
/ 02 декабря 2014

О решении, предоставленном J.F. Sebastian здесь :

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

Это умно, но имеет один недостаток - всегда возвращать кортеж. Как получить строку вместо?
Конечно, вы можете написать ''.join(chunker(...)), но временный кортеж все равно создается.

Вы можете избавиться от временного кортежа, написав собственный zip, например:

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

Тогда

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

Пример использования:

>>> 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'
0 голосов
/ 28 ноября 2018

Я надеюсь, что, превращая итератор из списка, я не просто копирую фрагмент списка. Генераторы можно разделить на части, и они автоматически останутся генераторами, а списки будут разбиты на огромные куски по 1000 записей, что менее эффективно.

def iter_group(iterable, batch_size:int):
    length = len(iterable)
    start = batch_size*-1
    end = 0
    while(end < length):
        start += batch_size
        end += batch_size
        if type(iterable) == list:
            yield (iterable[i] for i in range(start,min(length-1,end)))
        else:
            yield iterable[start:end]

Использование:

items = list(range(1,1251))

for item_group in iter_group(items, 1000):
    for item in item_group:
        print(item)
0 голосов
/ 12 января 2009

Если списки имеют одинаковый размер, вы можете объединить их в списки из 4-х кортежей с zip(). Например:

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

Вот что выдает функция zip():

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

Если списки велики, и вы не хотите объединять их в больший список, используйте itertools.izip(), который создает итератор, а не список.

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...
0 голосов
/ 13 июля 2017

Этот ответ разбивает список строк , например. для достижения соответствия длины линии PEP8:

def split(what, target_length=79):
    '''splits list of strings into sublists, each 
    having string length at most 79'''
    out = [[]]
    while what:
        if len("', '".join(out[-1])) + len(what[0]) < target_length:
            out[-1].append(what.pop(0))
        else:
            if not out[-1]: # string longer than target_length
                out[-1] = [what.pop(0)]
            out.append([])
    return out

Использовать как

>>> split(['deferred_income', 'long_term_incentive', 'restricted_stock_deferred', 'shared_receipt_with_poi', 'loan_advances', 'from_messages', 'other', 'director_fees', 'bonus', 'total_stock_value', 'from_poi_to_this_person', 'from_this_person_to_poi', 'restricted_stock', 'salary', 'total_payments', 'exercised_stock_options'], 75)
[['deferred_income', 'long_term_incentive', 'restricted_stock_deferred'], ['shared_receipt_with_poi', 'loan_advances', 'from_messages', 'other'], ['director_fees', 'bonus', 'total_stock_value', 'from_poi_to_this_person'], ['from_this_person_to_poi', 'restricted_stock', 'salary', 'total_payments'], ['exercised_stock_options']]
0 голосов
/ 07 июля 2017

Довольно питонический здесь (вы также можете вставить тело функции split_groups)

import itertools
def split_groups(iter_in, group_size):
    return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))

for x, y, z, w in split_groups(range(16), 4):
    foo += x * y + z * w
...