У меня есть скрипт 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 разбить список на части одинакового размера?
Я расширяю список так, чтобы его длина была кратна четырем, прежде чем он зашел так далеко.
@ ΤΖΩΤΖΙΟΥ - Вопросы очень похожи, но не повторяются. Он «разбивается на любое количество кусков размера N» по сравнению с «разбивается на N кусков любого размера». :-)
возможный дубликат Как в Python разбить список на части одинакового размера?






Кажется, нет красивого способа сделать это. Здесь - это страница, на которой есть несколько методов, в том числе:
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
Во втором методе я бы перешел к следующей группе из 4 человек, выполнив следующие действия:
ints = ints[4:]
Однако я не проводил никаких измерений производительности, поэтому не знаю, какой из них может быть более эффективным.
Сказав это, я обычно выбираю первый метод. Это некрасиво, но это часто является следствием взаимодействия с внешним миром.
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
+1 за использование генераторов, швов как самое "питоническое" из всех предложенных решений
Он довольно длинный и неуклюжий для чего-то настолько простого, что совсем не питонично. Я предпочитаю версию С. Лотта
@zenazn: это будет работать на экземплярах генератора, нарезка не будет
В дополнение к правильной работе с генераторами и другими итераторами, не допускающими нарезки, первое решение также не требует значения «заполнителя», если конечный фрагмент меньше, чем size, что иногда желательно.
Также +1 для генераторов. Для других решений требуется вызов len, поэтому они не работают с другими генераторами.
Я бы бросил try: block и перехватил исключение value error для обработки множественной проблемы <4.
Первая - хорошая, простая версия, не использующая fillvalue, но работающая на любых итерациях. Хороший!
Я фанат
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?
@AnnaVopureta chunk будет иметь 1, 2 или 3 элемента для последней партии элементов. См. Этот вопрос о том, почему индексы срезов могут быть вне пределов.
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']
Версия @Carlos Crasborn работает с любыми итерациями (а не только с последовательностями, как в приведенном выше коде); он краткий и, вероятно, такой же быстрый или даже быстрее. Хотя это может быть немного непонятно (непонятно) для людей, незнакомых с модулем itertools.
@ J.F. Себастьян - Теперь, когда у меня появилась возможность выяснить, работает ли Зачем его код, я чувствую себя обязанным изменить свой принятый ответ (что я делаю ненавидеть). Мне тоже нравится этот ответ, @nosklo, но этот трюк izip_longest кажется адаптированным для моей ситуации.
Согласовано. Это самый общий и питонический способ. Понятно и лаконично. (и работает на движке приложения)
У меня были проблемы с его использованием, но он начал работать, когда я заменил внешние скобки квадратными скобками. Синтаксис в ответе только на Python 3?
Обратите внимание, что chunker возвращает generator. Замените возврат на: return [...], чтобы получить список.
Вместо того, чтобы писать построение функции и затем возвращать генератор, вы также можете написать генератор напрямую, используя yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Я не уверен, что внутренне это будет обрабатываться по-другому в каком-либо соответствующем аспекте, но это может быть даже немного яснее.
Обратите внимание, что это работает только для последовательностей, которые поддерживают доступ к элементам по индексу, и не будет работать для общих итераторов, поскольку они могут не поддерживать метод __getitem__.
Я это переписал как генератор. Не могли бы вы сделать это? У него есть все преимущества, нет недостатков, и он более эффективен с точки зрения памяти.
@smci функция chunker() выше генератор - возвращает выражение генератора
@nosklo: Ну ладно, я его переписал как простой генератор: for i in range(0, len(seq), size): yield seq[i:i + size], который мне показался проще.
Мне это нравится! Его очень легко понять и преобразовать в генератор. +1
Если список большой, наиболее эффективным способом сделать это будет использование генератора:
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,)
(Я думаю, что предложение itertools от MizardX функционально эквивалентно этому.)
(На самом деле, подумав, нет. Itertools.islice возвращает итератор, но не использует существующий.)
Это красиво и просто, но почему-то даже без преобразования в кортеж в 4-7 раз медленнее, чем принятый метод группировщика на iterable = range(100000000) и chunksize до 10000.
Однако в целом я бы рекомендовал этот метод, потому что принятый может быть очень медленным, когда проверка последнего элемента выполняется медленно docs.python.org/3/library/itertools.html#itertools.zip_longe st
Если списки одинакового размера, вы можете объединить их в списки из 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):
...
from itertools import izip_longest
def chunker(iterable, chunksize, filler):
return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
Читаемый способ сделать это - stackoverflow.com/questions/434287/…
Обратите внимание, что в python 3 izip_longest заменен на zip_longest
Изменено из раздела рецепты документации Python itertools:
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.
Наконец-то появился шанс поиграть с этим в сеансе Python. Для тех, кто так же смущен, как и я, это несколько раз подает один и тот же итератор в izip_longest, заставляя его использовать последовательные значения одной и той же последовательности, а не чередующиеся значения из отдельных последовательностей. Я люблю это!
Как лучше всего отфильтровать значение заполнения? ([элемент для элемента в элементах, если элемент не является значением заполнения] для элементов в группировщике (итерируемый))?
Я не уверен, что это самый питонический ответ, но, возможно, это лучшее использование структуры [LIST]*n.
Это работает, но кажется, что это зависит от реализации интерпретатора. Гарантирует ли спецификация itertools.izip_longest чередующийся порядок доступа для итераторов (например, с 3 итераторами A, B и C порядок доступа будет A, B, C, A, B, C, A, Fill, C а не что-то вроде A, A, B, B, C, C, A, Fill, C или A, B, C, C, B, A, A, Fill, C? Я мог видеть, что последние порядки полезны для кеширования оптимизация производительности. Если упорядочение доступа с одним чередованием не гарантировано, это не является теоретически безопасным решением (хотя, говоря практически, большинство реализаций будут одношаговыми итераторами).
Вы можете объединить все это в короткий однострочник: zip(*[iter(yourList)]*n) (или izip_longest с fillvalue)
Я подозреваю, что производительность этого рецепта группировщика для блоков размером 256 КБ будет очень низкой, потому что izip_longest будет загружать 256 КБ аргументов.
Что такое объект izip_longest, почему он не ведет себя как список и почему он возвращается из него? Почему я должен вызывать list(), почему он просто не возвращает новый список?
@DavidB .: рецепт приведен в качестве примера кода в официальной документации. Если только это не ошибка; поведение гарантировано
Чтобы быть эффективным с большим n, вам нужно будет управлять пулом и кормить его islice, как здесь github.com/Suor/funcy/blob/1.0.0/funcy/seqs.py#L293
В нескольких местах комментаторы говорят: «Когда я наконец понял, как это работает…» Может потребоваться небольшое объяснение. В частности, аспект списка итераторов.
Я предпочитаю размещать аргументы в порядке сначала размера, а затем последовательность. Это упрощает создание партиалов для разбиения на определенное количество, а затем просто передачу им различных последовательностей.
О производительности @anatolytechtonik судят по измерениям реальных данных на реальном оборудовании, а не по гипотетической «логике». За эти годы я много раз использовал рецепт grouper (). Я встречал случаи, когда вы не могли использовать нарезку, потому что входные данные не являются последовательностью. Я не помню ни одного случая, когда я бы заменил код, похожий на grouper (), на код, подобный chunker (), из-за проблем с производительностью (я мог бы заменить его для удобства чтения в коде для начинающих). YMMV
@gotgenes, (filter(None, chunk) for chunk in zip_longest(*[iter(yourList)]*n) предоставит генератор фрагментов. Каждый блок сам по себе является генератором (с использованием фильтра), который пропускает значения заполнения.
если next () для исчерпанного итератора оказывается медленным (например, курсор psycopg2), последний фрагмент zip_longest в среднем n/2 раз медленнее, чем должен быть.
Есть ли способ использовать это, но без того, чтобы None заполнил последний кусок?
Это менее интенсивно / быстрее, чем кажется, по-видимому, потому что количество аргументов и итерация являются указателями на один и тот же объект (ы).
@CMCDragonkai, вы можете отфильтровать итератор с помощью (x for x in chunk if x), предполагая, что ваше значение заполнения - None.
Я бы не рекомендовал это, поскольку он основан на деталях реализации zip_longest. Даже если его поведение гарантировано, для чтения (и понимания) этого рецепта по-прежнему требуются знания, выходящие за рамки API. Однако интересно как упражнение на Python.
Поскольку об этом еще никто не упоминал, вот решение для 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)
Использование map () вместо zip () устраняет проблему с заполнением в ответе Дж. Ф. Себастьяна:
>>> 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')]
С этим лучше справиться с itertools.izip_longest (Py2) / itertools.zip_longest (Py3); такое использование map вдвойне устарело и недоступно в Py3 (вы не можете передать None в качестве функции сопоставления, и он останавливается, когда исчерпывается самая короткая итерация, а не самая длинная; она не заполняется).
Идеальное решение этой проблемы работает с итераторами (а не только с последовательностями). Это также должно быть быстро.
Это решение, предоставленное документацией для 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 на моем Mac Book Air, я получаю 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 может быть ошибочно принято за наполнитель.
Кстати, большие пальцы вверх на №4. Я собирался опубликовать свою оптимизацию № 3 как лучший ответ (с точки зрения производительности), чем то, что было опубликовано до сих пор, но с настройкой, делающей его надежным, отказоустойчивым № 4, работает в два раза быстрее, чем оптимизированный № 3; Я не ожидал, что решение с циклами уровня Python (и без теоретических алгоритмических различий AFAICT) победит. Я предполагаю, что №3 проигрывает из-за расходов на создание / повторение объектов islice (№3 выигрывает, если n относительно велик, например, количество групп невелико, но это оптимизация для необычного случая), но я не ожидал, что это будет совсем такая крайность.
Для # 4 первая ветвь условного выражения всегда берется только на последней итерации (последний кортеж). Вместо того, чтобы заново воссоздавать окончательный кортеж, кешируйте модуль длины исходного итерируемого элемента вверху и используйте его, чтобы вырезать нежелательное заполнение из izip_longest в последнем кортеже: yield i[:modulo]. Кроме того, для переменной args используйте кортеж вместо списка: args = (iter(iterable),) * n. Сокращает еще несколько тактов. Наконец, если мы проигнорируем значение заполнения и примем None, условное выражение может стать if None in i для еще большего количества тактовых циклов.
@Kumba: ваше первое предложение предполагает, что длина ввода известна. Если это итератор / генератор, а не коллекция известной длины, кэшировать нечего. В любом случае нет реальной причины использовать такую оптимизацию; вы оптимизируете необычный случай (последний yield), в то время как общий случай не изменяется.
Еще один ответ, преимущества которого:
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) break не работает должным образом во внутреннем цикле - управление снова перейдет во внутренний цикл со следующим элементом в итераторе. Чтобы пропустить целые фрагменты, либо оберните внутренний итератор (ii выше) в кортеж, например for c in tuple(ii), или установить флаг и исчерпать итератор.
Подобно другим предложениям, но не совсем идентично, мне нравится делать это таким образом, потому что это просто и легко читается:
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))
@ Жан-Франсуа Фабр: с точки зрения удобочитаемости я не считаю это улучшением. И это также немного медленнее. Это улучшение, если ты играешь в гольф, а я нет.
нет, я не играю в гольф, но что, если у вас есть 10 аргументов? Я читал эту конструкцию на какой-то официальной странице, но, конечно, не могу найти ее прямо сейчас :)
@ Жан-Франсуа Фабр: если у меня 10 аргументов или переменное количество аргументов, это вариант, но я бы лучше написал: zip (* (it,) * 10)
верно! это что я читал. не тот список, который я составил :)
Мне совсем не нравится использование мелких функций и вещей; Я предпочитаю просто использовать срезы:
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.
Кроме того, съедает память из-за использования [...for...]понимание списка для физического построения списка вместо использования (...for...)генератор выражения, который будет заботиться только о следующем элементе и свободной памяти.
Мне нужно было решение, которое также работало бы с наборами и генераторами. Я не мог придумать ничего очень короткого и красивого, но, по крайней мере, это вполне читабельно.
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]]
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с очень похожи
Другой подход - использовать форму 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)
предпочтительнее, потому что у вас есть возможность опустить отступы!
Вы можете использовать функцию перегородка или куски из библиотеки веселье:
from funcy import partition
for a, b, c, d in partition(4, ints):
foo += a * b * c * d
Эти функции также имеют версии итератора ipartition и ichunks, которые в этом случае будут более эффективными.
Вы также можете заглянуть в их реализация.
Однострочное специальное решение для перебора списка 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 ...
Чтобы избежать всех преобразований в список 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)
Что, если список, который вы разбиваете, представляет собой нечто иное, чем последовательность возрастающих целых чисел?
@PaulMcGuire см. группа по; учитывая функцию для описания порядка, тогда элементы итерации могут быть любыми, не так ли?
Да, я знаком с groupby. Но если бы сообщения были буквами «ABCDEFG», то groupby(messages, lambda x: x/3) выдал бы вам TypeError (для попытки разделить строку на int), а не трехбуквенные группировки. Теперь, если вы сделали groupby(enumerate(messages), lambda x: x[0]/3), у вас может быть что-нибудь. Но вы не сказали этого в своем посте.
С 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
Сначала я спроектировал его для разделения строк на подстроки для анализа строки, содержащей шестнадцатеричный код. Сегодня превратил его в сложный, но простой генератор.
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())
iterable - это любой итератор / итератор / генератор, продолжающий / генерирующий / повторяющий входные данные,size - это, конечно, размер фрагмента, который вы хотите получить,reductor - это вызываемый объект, который получает генератор, повторяющий содержимое блока.
Я ожидал, что он вернет последовательность или строку, но я этого не требую.
В качестве этого аргумента можно передать, например, list, tuple, set, frozenset,
.
или что-нибудь более интересное. Я бы передал эту функцию, вернув строку
(при условии, что iterable содержит / генерирует / перебирает строки):
def concatenate(iterable):
return ''.join(iterable)
Note that reductor can cause closing generator by raising exception.
condition - это вызываемый объект, который получает все, что вернул reductor.
Он решает утвердить и уступить его (возвращая что-либо оценивающее в True),
или отклонить его и завершить работу генератора (вернув что-нибудь другое или вызвав исключение).
Когда количество элементов в iterable не делится на size, когда it исчерпывается, reductor получит генератор, генерирующий меньше элементов, чем size.
Назовем эти элементы длится элементы.
Я предложил передать в качестве аргумента две функции:
lambda x:x - будет выдан длится элементы.
lambda x: len(x)==<size> - длится элементы будет отклонен.
replace <size> using number equal to size
По поводу решения, предоставленного 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'
Не критика, предназначенная для того, чтобы вы изменили свой ответ, а скорее комментарий: Код - это ответственность. Чем больше кода вы напишете, тем больше места вы создадите для скрытия ошибок. С этой точки зрения переписывание zip вместо использования существующего кажется не лучшей идеей.
Вот чанкер без импорта, который поддерживает генераторы:
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')]
itertools.groupby легко заставить работать, чтобы вы получали итерацию из итераций, без создания каких-либо временных списков:
groupby(iterable, (lambda x,y: (lambda z: x.next()/y))(count(),100))
Не пугайтесь вложенных лямбд, внешняя лямбда запускается только один раз, чтобы поместить генератор count() и константу 100 в область действия внутренней лямбды.
Я использую это для отправки кусков строк в mysql.
for k,v in groupby(bigdata, (lambda x,y: (lambda z: x.next()/y))(count(),100))):
cursor.executemany(sql, v)
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()
Мне нравится такой подход. Он кажется простым и не волшебным, поддерживает все итерируемые типы и не требует импорта.
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
Довольно питонический здесь (вы также можете встроить тело функции 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
Это ответ разбивает список строк, f.ex. для достижения соответствия длине строки 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']]
Я никогда не хочу, чтобы мои куски были набиты, поэтому это требование очень важно. Я считаю, что способность работать с любыми итерациями также является требованием. Учитывая это, я решил продолжить принятый ответ, 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
Если вы не против использования внешнего пакета, вы можете использовать iteration_utilities.grouper из iteration_utilties1. Он поддерживает все итерации (а не только последовательности):
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. Далее следует подход Сумасшествие.
Тест был создан с помощью simple_benchmark1. Для запуска этого теста использовался следующий код:
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.
Я надеюсь, что, выключая итератор из списка, я не просто копирую часть списка. Генераторы могут быть нарезаны, и они автоматически останутся генераторами, в то время как списки будут нарезаны на огромные фрагменты по 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)
Если я что-то не упустил, следующее простое решение с выражениями генератора не упоминалось. Предполагается, что известны как размер, так и количество блоков (что часто бывает) и что никаких отступов не требуется:
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))
Почему бы не использовать понимание списка
l = [1 , 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
n = 4
filler = 0
fills = len(l) % n
chunks = ((l + [filler] * fills)[x * n:x * n + n] for x in range(int((len(l) + n - 1)/n)))
print(chunks)
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 0]]
В 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]
В пакете more-itertools есть метод разбитый на части, который делает именно это:
import more_itertools
for s in more_itertools.chunked(range(9), 4):
print(s)
Печать
[0, 1, 2, 3]
[4, 5, 6, 7]
[8]
chunked возвращает элементы в списке. Если вы предпочитаете итерации, используйте ichunked.
Ваш код не работает, если размер списка не кратен четырем.