Какой самый «питонный» способ перебрать список по частям? - 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 ]

4 голосов
/ 29 сентября 2017

Если вы не возражаете против использования внешнего пакета, вы можете использовать iteration_utilities.grouper из iteration_utilties 1 . Поддерживаются все итерации (не только последовательности):

from iteration_utilities import grouper
seq = list(range(20))
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, 17, 18, 19)

Если длина не кратна размеру группы, он также поддерживает заполнение (неполная последняя группа) или усечение (исключение неполной последней группы) последней:

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 Отказ от ответственности: я являюсь автором этого пакета.

3 голосов
/ 21 февраля 2013

Использование маленьких функций и вещей действительно не привлекает меня; Я предпочитаю просто использовать ломтики:

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:
    ...
3 голосов
/ 12 января 2009

Если список большой, самый эффективный способ сделать это - использовать генератор:

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,)
2 голосов
/ 19 ноября 2014

С NumPy все просто:

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

выход:

1 2
3 4
5 6
7 8
2 голосов
/ 26 февраля 2014

Другой подход заключается в использовании формы с двумя аргументами iter:

from itertools import islice

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

Это может быть легко адаптировано для использования заполнения (это похоже на ответ 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)

Они могут даже комбинироваться для дополнительного заполнения:

_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)
2 голосов
/ 27 марта 2019

Если я не пропустил что-то, следующее простое решение с выражениями генератора не было упомянуто. Предполагается, что известны как размер, так и количество фрагментов (что часто имеет место) и что заполнение не требуется:

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 голос
/ 05 июня 2014

Вы можете использовать раздел или чанки функция из funcy библиотека:

from funcy import partition

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

Эти функции также имеют версии итераторов ipartition и ichunks, которые в этом случае будут более эффективными.

Вы также можете посмотреть на их реализацию .

1 голос
/ 12 января 2009

В вашем втором методе я бы перешел к следующей группе из 4, выполнив это:

ints = ints[4:]

Однако я не проводил измерения производительности, поэтому не знаю, какой из них может быть более эффективным.

Сказав это, я обычно выбираю первый метод. Это не красиво, но это часто является следствием взаимодействия с внешним миром.

1 голос
/ 18 октября 2014

Чтобы избежать всех преобразований в список import itertools и:

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

Производит:

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

Я проверил groupby, и он не конвертируется в список или не использует len, поэтому я (думаю) будет задерживать разрешение каждого значения, пока оно не будет фактически использовано. К сожалению, ни один из доступных ответов (в настоящее время), казалось, не предлагал этот вариант.

Очевидно, что если вам нужно обработать каждый элемент по очереди, вложите цикл for в 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

Мой особый интерес в этом заключался в необходимости использования генератора для отправки изменений в пакетах до 1000 в API 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)
1 голос
/ 12 ноября 2012

Еще один ответ, преимуществами которого являются:

1) Легко понятно
2) Работает с любыми повторяемыми, а не только с последовательностями (некоторые из приведенных выше ответов захлебнутся файловыми дескрипторами)
3) Не загружает чанк в память все сразу
4) Не создает длинный список ссылок на один и тот же итератор в памяти
5) Нет заполнения значений заполнения в конце списка

При этом я не рассчитал время, поэтому он может быть медленнее, чем некоторые из более умных методов, и некоторые преимущества могут быть неактуальными, учитывая вариант использования.

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 

Обновление:
Пара недостатков из-за того, что внутренний и внешний циклы извлекают значения из одного и того же итератора:
1) continue не работает должным образом во внешнем цикле - он просто переходит к следующему элементу, а не пропускает фрагмент. Однако это не выглядит проблемой, так как во внешнем цикле проверять нечего.
2) разрыв не работает так, как ожидалось во внутреннем цикле - элемент управления снова окажется во внутреннем цикле со следующим элементом в итераторе. Чтобы пропустить целые куски, либо оберните внутренний итератор (ii выше) в кортеж, например, for c in tuple(ii), или установите флаг и исчерпайте итератор.

...