Какой самый «питонный» способ перебрать список по частям?


488

У меня есть сценарий 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?


3
Ваш код не работает, если размер списка не кратен четырем.
Педро Энрикес

5
Я расширяю () список так, чтобы его длина была кратна четырем, прежде чем он зайдет так далеко.
Бен Бланк

4
@ ΤΖΩΤΖΙΟΥ - Вопросы очень похожи, но не совсем дублируют. Он «разделен на любое количество фрагментов размера N» против «разделен на N фрагментов любого размера». :-)
Бен Бланк


Ответы:


340

Изменено в разделе рецептов документации iterotools Python :

from itertools import zip_longest

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

Пример
В псевдокоде оставить пример кратким.

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

Примечание: на Python 2 используйте izip_longestвместо zip_longest.


67
Наконец-то появился шанс поиграть с этим на сессии Python. Для тех, кто так же запутался, как и я, это отправляет один и тот же итератор в izip_longest несколько раз, заставляя его использовать последовательные значения одной и той же последовательности, а не чередующиеся значения из отдельных последовательностей. Я люблю это!
Бен Бланк

6
Какой лучший способ отфильтровать значение заполнения? ([элемент для элемента в элементах, если элемент не является значением заполнения] для элементов в группе (итеративно))?
получил

14
Я подозреваю, что производительность этого рецепта группировщика для кусков размером 256КБ будет очень низкой, потому что izip_longestбудет передаваться 256К аргументов.
анатолий техтоник

13
В некоторых местах комментаторы говорят: «Когда я наконец-то понял, как это работает…». Может быть, требуется немного пояснений. В частности, список итераторов аспект.
LondonRob

6
Есть ли способ использовать это, но без Noneзаполнения последнего куска?
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)

Просто. Легко. Быстрый. Работает с любой последовательностью:

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
Версия @Carlos Crasborn работает для любых итераций (не только для последовательностей, как в приведенном выше коде); это сжато и вероятно столь же быстро или даже быстрее. Хотя это может быть немного неясным (неясным) для людей, незнакомых с itertoolsмодулем.
JFS

1
Согласовано. Это самый общий и питонный способ. Ясно и сжато. (и работает над движком приложения)
Мэтт Уильямсон

3
Обратите внимание, что chunkerвозвращает generator. Заменить возврат на: return [...]чтобы получить список.
Дрор

11
Вместо того , чтобы писать функции здания , а затем возвращающая генератор, вы также можете написать генератор напрямую, используя yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Я не уверен, что внутренне это будет обрабатываться по-другому в любом соответствующем аспекте, но это может быть даже немного яснее.
Alfe

3
Обратите внимание, что это работает только для последовательностей, которые поддерживают доступ к элементам по индексу, и не будет работать для универсальных итераторов, поскольку они могут не поддерживать __getitem__метод.
Аполлов

135

Я фанат

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

Как ведет себя, если len (ints) не кратно chunkSize?
PlsWork

3
@AnnaVopureta chunkбудет иметь 1, 2 или 3 элемента для последней партии элементов. Посмотрите этот вопрос о том, почему индексы срезов могут выходить за пределы .
Борис

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)

Другой путь:

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 за использование генераторов, швы как наиболее «питонические» из всех предложенных решений
Сергей Головченко

7
Он довольно длинный и неуклюжий для чего-то такого легкого, что вовсе не очень питонично. Я предпочитаю версию С. Лотта
zenazn

4
@zenazn: это будет работать на экземплярах генератора, нарезка не будет
Janus Troelsen

В дополнение к правильной работе с генераторами и другими неслайсируемыми итераторами, первое решение также не требует значения «заполнитель», если конечный фрагмент меньше, чем size, что иногда желательно.
Дано

1
Также +1 для генераторов. Другие решения требуют lenвызова и поэтому не работают на других генераторах.
Cuadue


11

Идеальное решение этой проблемы работает с итераторами (а не только с последовательностями). Это также должно быть быстро.

Это решение, предоставляемое документацией для 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)

Использование ipython %timeit на моем , я получаю 47,5 нас за цикл.

Тем не менее, это действительно не работает для меня, так как результаты дополняются до групп равного размера. Решение без дополнения немного сложнее. Наиболее наивное решение может быть:

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

Простой, но довольно медленный: 693 нас за цикл

Лучшее решение, которое я мог придумать, использует isliceдля внутреннего цикла:

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

С тем же набором данных я получаю 305 нас за цикл.

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

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

Мне действительно не нравится этот ответ, но он значительно быстрее. 124 доллара за петлю


Можно уменьшить время выполнения для рецепта # 3 на ~ 10-15%, перемещая его к слою C (минуя itertoolsимпорт, mapдолжны быть PY3 mapили imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Ваша последняя функция может быть сделана менее хрупкой с помощью часового: избавьтесь от fillvalueаргумента; добавьте первую строку fillvalue = object(), затем измените ifчек на if i[-1] is fillvalue:и строку, которой он управляет yield tuple(v for v in i if v is not fillvalue). Гарантии, что никакое значение в не iterableможет быть принято за значение наполнителя.
ShadowRanger

Кстати, большие пальцы на # 4. Я собирался опубликовать свою оптимизацию # 3 как лучший ответ (с точки зрения производительности), чем то, что было опубликовано до сих пор, но с настройкой, чтобы сделать его надежным, эластичный # 4 работает вдвое быстрее, чем оптимизированный # 3; Я не ожидал, что решение с петлями уровня Python (и без теоретических алгоритмических различий AFAICT) победит. Я предполагаю, что # 3 проигрывает из-за затрат на конструирование / итерацию isliceобъектов (# 3 выигрывает, если nотносительно велико, например, количество групп мало, но это оптимизирует для нечастого случая), но я не ожидал, что это будет так крайность.
ShadowRanger

Для # 4 первая ветвь условия берется только на последней итерации (последний кортеж). Вместо воссоздание окончательного кортежа снова, кэшировать по модулю длины оригинала итерации в верхней части и использовать, чтобы отрезать нежелательное дополнение от izip_longestна конечном наборе: yield i[:modulo]. Кроме того , для argsпеременной, кортеж его вместо списка: args = (iter(iterable),) * n. Бреет еще несколько тактов. Наконец, если мы проигнорируем fillvalue и предположим None, что условное условие может стать if None in iеще больше тактов.
Кумба

1
@ Kumba: Ваше первое предложение предполагает, что вход имеет известную длину. Если это итератор / генератор, а не коллекция с известной длиной, то кешировать нечего. В любом случае, нет реальной причины использовать такую ​​оптимизацию; вы оптимизируете необычный случай (последнийyield ), в то время как общий случай не затронут.
ShadowRanger

10

Мне нужно решение, которое также будет работать с наборами и генераторами. Я не мог придумать ничего очень короткого и красивого, но это, по крайней мере, вполне читабельно.

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

Список:

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

Набор:

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

Генератор:

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

8

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

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)

Таким образом, вы не получите последний частичный кусок. Если вы хотите получить (9, None, None, None)последний кусок, просто используйте izip_longestиз itertools.


можно улучшить с помощьюzip(*([it]*4))
Жан-Франсуа Фабр

@ Жан-Франсуа Фабр: с точки зрения читабельности я не вижу в этом улучшения. И это также немного медленнее. Это улучшение, если вы играете в гольф, а я нет.
Kriss

нет я не играю в гольф, но что, если у вас есть 10 аргументов? Я прочитал эту конструкцию на какой-то официальной странице. Но, конечно, я не могу найти ее прямо сейчас :)
Жан-Франсуа Фабр

@ Жан-Франсуа Фабр: если у меня есть 10 аргументов, или ряд переменных аргументов, это вариант, но я предпочел бы написать: застежка - молния (* (он,) * 10)
Kriss

правильно! вот что я прочитал. не список вещей, которые я составил :)
Жан-Франсуа Фабр

8

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

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)

Ориентиры

Я также решил сравнить время выполнения некоторых из упомянутых подходов. Это график журнала, объединяющий группы по 10 элементов на основе списка разного размера. Для качественных результатов: чем ниже, тем быстрее:

введите описание изображения здесь

По крайней мере, в этом тесте результаты iteration_utilities.grouperработают лучше всего. Затем следует подход Craz .

Тест был создан с 1 . Код, используемый для запуска этого теста: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 Отказ от ответственности: я автор библиотек iteration_utilitiesи simple_benchmark.


7

Поскольку никто еще не упомянул об этом, вот zip()решение:

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

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

Пример:

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

Или используя itertools.izip для возврата итератора вместо списка:

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

Заполнение можно исправить с помощью ответа @ ΤΖΩΤΖΙΟΥ :

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

Использование map () вместо zip () решает проблему заполнения в ответе JF Sebastian:

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

Пример:

>>> 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
Это лучше обрабатывается с помощью itertools.izip_longest(Py2) / itertools.zip_longest(Py3); такое использование mapне рекомендуется вдвойне и недоступно в Py3 (вы не можете передать Noneкак функцию отображения, и оно останавливается, когда самая короткая итерация исчерпана, а не самая длинная; она не дополняется).
ShadowRanger

4

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

from itertools import islice

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

Это может быть легко адаптировано для использования отступов (это похоже на ответ Маркуса Жардеро ):

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)

1
предпочтительнее, потому что у вас есть возможность опустить заполнение!
n611x007

3

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

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

(Я думаю, что предложение itztools MizardX функционально эквивалентно этому.)
Роберт Россни

1
(На самом деле, если подумать, нет, нет. Itertools.islice возвращает итератор, но он не использует существующий.)
Роберт Россни

Это красиво и просто, но по какой-то причине даже без преобразования в кортеж в 4-7 раз медленнее, чем принятый метод группирования на iterable = range(100000000)& chunksizeдо 10000.
Valentas

Однако в целом я бы рекомендовал этот метод, потому что принятый метод может быть очень медленным, когда проверка последнего элемента выполняется медленно docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

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

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

хорошо, но бесполезно для неопределенного потока, о котором ничего не известно len. Вы можете сделать тест с помощью itertools.repeatили itertools.cycle.
n611x007

1
Кроме того, расходуется память из-за использования [...for...] понимания списков для физического построения списка вместо использования (...for...) выражения-генератора, которое просто заботится о следующем элементе и резервной памяти
n611x007

2

Чтобы избежать всех преобразований в список 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поэтому (думаю) это задержит разрешение каждого значения, пока оно не будет фактически использовано. К сожалению, ни один из доступных ответов (в настоящее время), казалось, не предлагал этот вариант.

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

Что если список, который вы разбиваете на части, является чем-то отличным от последовательности восходящих целых чисел?
PaulMcG

@PaulMcGuire смотри groupby ; если задана функция для описания порядка, то элементы итерируемого могут быть чем угодно, верно?
Джон Ми

1
Да, я знаком с группой. Но если бы сообщения были буквами «ABCDEFG», то groupby(messages, lambda x: x/3)вы бы получили ошибку TypeError (для попытки разделить строку на int), а не 3-буквенные группировки. Теперь, если бы ты это сделал, у groupby(enumerate(messages), lambda x: x[0]/3)тебя могло бы быть что-то. Но вы не сказали этого в своем посте.
PaulMcG

2

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

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

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

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

ints = ints[4:]

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

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


1

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

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) продолжение не работает должным образом во внешнем цикле - оно просто переходит к следующему элементу, а не пропускает фрагмент , Тем не менее, это не кажется проблемой, так как во внешнем цикле проверять нечего.
2) разрыв не работает так, как ожидалось во внутреннем цикле - элемент управления снова окажется во внутреннем цикле со следующим элементом в итераторе. Чтобы пропустить целые куски, либо оберните внутренний итератор (ii выше) в кортеж, например for c in tuple(ii), либо установите флаг и исчерпайте итератор.


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 он пропускает отступы; ваши и bcoughlan «S очень похожи
n611x007

1

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

from funcy import partition

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

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

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


1

О решении, представленном 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'

2
Не критика для вас, чтобы изменить свой ответ, а скорее комментарий: Кодекс является обязательством. Чем больше кода вы пишете, тем больше места вы создаете для скрытия ошибок. С этой точки зрения переписывание zipвместо использования существующего, кажется, не лучшая идея.
Alfe

1

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

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

Я никогда не хочу, чтобы мои куски были мягкими, поэтому это требование крайне важно. Я считаю, что умение работать на любом итерируемом также является требованием. Учитывая это, я решил расширить принятый ответ, 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

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

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

С Python 3.8 вы можете использовать оператор моржа и 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

Там, кажется, не очень хороший способ сделать это. Вот страница, которая имеет несколько методов, в том числе:

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

Если списки имеют одинаковый размер, вы можете объединить их в списки из 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

Однострочное, временное решение для перебора списка xпо размерам 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 ...
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.