У меня есть список произвольной длины, и мне нужно разбить его на части равного размера и работать с ним. Есть несколько очевидных способов сделать это, например, сохранить счетчик и два списка, а когда второй список заполнится, добавить его в первый список и очистить второй список для следующего раунда данных, но это потенциально чрезвычайно дорого.
Мне было интересно, есть ли у кого-нибудь хорошее решение для списков любой длины, например с помощью генераторов.
Я искал что-нибудь полезное в itertools, но ничего явно полезного не нашел. Хотя, возможно, пропустил.
Связанный вопрос: Каков наиболее «питонический» способ перебора списка по частям?






Вот генератор, который выдает нужные вам фрагменты:
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]
Если вы используете Python 2, вам следует использовать xrange() вместо range():
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in xrange(0, len(lst), n):
yield lst[i:i + n]
Также вы можете просто использовать понимание списка вместо написания функции, хотя рекомендуется инкапсулировать подобные операции в именованные функции, чтобы ваш код было легче понять. Python 3:
[lst[i:i + n] for i in range(0, len(lst), n)]
Версия Python 2:
[lst[i:i + n] for i in xrange(0, len(lst), n)]
Что произойдет, если мы не сможем определить длину списка? Попробуйте это на itertools.repeat ([1, 2, 3]), например
Это интересное расширение вопроса, но исходный вопрос явно задан о работе со списком.
@jespern Я думаю, что с бесконечным или неопределенным списком вы переходите к связанный вопрос, который связан с Дж. Ф. Себастьян: Каков наиболее «питонический» способ перебора списка по частям?
эти функции должны быть в чертовой стандартной библиотеке
Я бы добавил пример выражения генератора в дополнение к спискам понимания. (Просто используйте (), а не [])
-1. Вопрос конкретно касается «кусков одинакового размера». Это неверный ответ на вопрос, так как последний кусок будет произвольно маленьким.
@Calimo: что вы предлагаете? Я передаю вам список из 47 элементов. Как бы вы хотели разделить его на «куски одинакового размера»? OP принял ответ, поэтому они явно в порядке с последним фрагментом другого размера. Возможно, английская фраза неточна?
@NedBatchelder Я согласен, что вопрос довольно нечеткий, но вы можете разделить список из 47 элементов на 5 частей по 9, 9, 9, 10 и 10 элементов вместо 7, 10, 10, 10 и 10. Это не совсем даже, но это то, что я имел в виду, когда гуглил ключевые слова "блоки одинакового размера". Это означает, что вам нужно n определять количество фрагментов, а не их размер. Другой ответ ниже предлагает способ сделать это на самом деле. Ваш ответ в основном такой же, как и в связанном «связанном вопросе».
Большинство людей будут смотреть на это для пакетной обработки и ограничения скорости, поэтому обычно не имеет значения, меньше ли последний кусок
В моей системе я получаю не тот же результат, а список объектов диапазона. Чтобы получить тот же результат, что и вы, я использовал это: [list(s) for s in chunks(range(10, 75), 10)]
@Alvaro вы обнаружите, что если ваша последняя партия содержит только один элемент, у вас есть потраченное впустую ядро, которому нечего делать, в то время как у других может остаться довольно много, что легко можно было бы сделать в другом месте.
Я предпочитаю заполнять последний фрагмент, чтобы все куски были одинакового размера. Итак, def ichunks(it, n, pad=None): и т. д. И да, +1 для итератора в (возможно, бесконечном) потоке. Гораздо более общее, чем предположение о коллекции конечного размера.
Я удивлен, что в стандартной библиотеке уже нет функции для этого.
почему это принятый ответ, он не распространяет их наилучшим образом.
Если вы знаете размер списка:
def SplitList(mylist, chunk_size):
return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]
Если вы этого не сделаете (итератор):
def IterChunks(sequence, chunk_size):
res = []
for item in sequence:
res.append(item)
if len(res) >= chunk_size:
yield res
res = []
if res:
yield res # yield the last, incomplete, portion
В последнем случае его можно перефразировать более красиво, если вы можете быть уверены, что последовательность всегда содержит целое количество фрагментов заданного размера (т.е. нет неполного последнего фрагмента).
Мне грустно, что это похоронено так далеко. IterChunks работает для всего и является общим решением и не имеет никаких оговорок, о которых я знаю.
Вот генератор, который работает с произвольными итерациями:
def split_seq(iterable, size):
it = iter(iterable)
item = list(itertools.islice(it, size))
while item:
yield item
item = list(itertools.islice(it, size))
Пример:
>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[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]]
хех, однострочная версия
In [48]: chunk = lambda ulist, step: map(lambda i: ulist[i:i+step], xrange(0, len(ulist), step))
In [49]: chunk(range(1,100), 10)
Out[49]:
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]
Пожалуйста, используйте def chunk вместо chunk = lambda. Работает так же. Одна линия. Те же функции. НАМНОГО легче читать и понимать n00bz.
@ S.Lott: нет, если n00bz исходит из схемы: P, это не настоящая проблема. есть даже ключевое слово для Google! Какие еще функции показывают, чего мы избегаем ради n00bz? Я думаю, yield не является обязательным / c-подобным достаточно, чтобы быть дружелюбным к n00b.
Функциональный объект, полученный из def chunk вместо chunk=lambda, имеет атрибут .__ name__ 'chunk' вместо '<lambda>'. Конкретное имя более полезно при трассировке.
@Alfe: Я не уверен, можно ли назвать это основным семантическим различием, но есть ли полезное имя в трассировке вместо <lamba> или нет, это, по крайней мере, заметное различие.
После тестирования нескольких из них на производительность, ЭТО отлично!
Непосредственно из (старой) документации Python (рецепты для itertools):
from itertools import izip, chain, repeat
def grouper(n, iterable, padvalue=None):
"grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)
Текущая версия, предложенная Дж. Ф. Себастьяном:
#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)
def grouper(n, iterable, padvalue=None):
"grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)
Думаю, машина времени Гвидо работает - работает - будет работать - будет работать - снова заработала.
Эти решения работают, потому что [iter(iterable)]*n (или эквивалент в более ранней версии) создает итератор один, повторяющийся n раз в списке. izip_longest затем эффективно выполняет циклический перебор «каждого» итератора; поскольку это один и тот же итератор, он продвигается вперед при каждом таком вызове, в результате чего каждый такой zip-roundrobin генерирует один кортеж элементов n.
@ninjagecko: list(grouper(3, range(10))) возвращает [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], и все кортежи имеют длину 3. Пожалуйста, уточните свой комментарий, потому что я не могу его понять; что вы называете предмет и как вы определяете его как кратное 3 в «ожидании, что ваша величина будет кратна 3»? Заранее спасибо.
Если код пользователя имеет кортеж с None - это неправильное поведение, им необходимо явно выдать ошибку, если len('0123456789')%3 != 0. Это неплохая вещь, но вещь, которая может быть задокументирована. Ой, подождите, мои извинения ... это неявно задокументировано аргументом padvalue = None. (Также под «3» я имел в виду «n») Хороший код.
поддержал это, потому что он работает с генераторами (без len) и использует в целом более быстрый модуль itertools.
Классический пример причудливого функционального подхода itertools, в результате которого получается нечитаемый осадок по сравнению с простой и наивной реализацией на чистом питоне.
@wim Учитывая, что этот ответ начался как фрагмент из документации Python, я предлагаю вам открыть проблему на bugs.python.org.
Для справки, решение является частью документации, в разделе рецептов itertools: docs.python.org/3/library/itertools.html#itertools-recipes
Может ли кто-нибудь объяснить или указать мне правильную концепцию того, почему существует * до [iter(iterable)]*n?
@pedrosaurio, если l==[1, 2, 3], то f(*l) эквивалентен f(1, 2, 3). См. этот вопрос и официальная документация.
Моя проблема с этим решением заключается в том, что результатом является не только разделение содержимого итератора ввода, но и добавление дополнительных элементов к последнему фрагменту, чтобы сделать его «правильным» размером.
@GrahamLea Я не уверен, что вы имеете в виду под «разделением»; ты имеешь в виду «потреблен»? Что касается заполнения последней группы, я вернусь с версией без заполнения. Обновлено: он уже предоставлен отправителем.
def split_seq(seq, num_pieces):
start = 0
for i in xrange(num_pieces):
stop = start + len(seq[i::num_pieces])
yield seq[start:stop]
start = stop
использование:
seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for seq in split_seq(seq, 3):
print seq
def chunk(lst):
out = []
for x in xrange(2, len(lst) + 1):
if not len(lst) % x:
factor = len(lst) / x
break
while lst:
out.append([lst.pop(0) for x in xrange(factor)])
return out
>>> def f(x, n, acc=[]): return f(x[n:], n, acc+[(x[:n])]) if x else acc
>>> f("Hallo Welt", 3)
['Hal', 'lo ', 'Wel', 't']
>>>
Если в скобки - взял книгу по Erlang :)
Если вы хотите что-то очень простое:
def chunks(l, n):
n = max(1, n)
return (l[i:i+n] for i in range(0, len(l), n))
Используйте xrange() вместо range() в случае Python 2.x
Или (если мы делаем разные представления этой конкретной функции) вы можете определить лямбда-функцию с помощью: lambda x, y: [x [i: i + y] для i в диапазоне (0, len (x), y) ]. Мне нравится этот метод понимания списков!
после возврата должно быть [, а не (
«Супер простой» означает отсутствие необходимости отлаживать бесконечные циклы - спасибо max().
в этом решении нет ничего простого
Обратите внимание, что результатом со списком ввода ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'] будет [['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H', 'I'], ['J']], а не [['A', 'B', 'C'], ['D', 'E', 'F'], ['G', 'H'], ['I', 'J']].
@BobStein Я новичок, как работает max ()?
@Nhoj_Gonk К сожалению, это не бесконечный цикл, но фрагменты (L, 0) вызовут ValueError без max (). Вместо этого max () превращает все, что меньше 1, в 1.
Без вызова len (), что хорошо для больших списков:
def splitter(l, n):
i = 0
chunk = l[:n]
while chunk:
yield chunk
i += n
chunk = l[i:i+n]
И это для итераций:
def isplitter(l, n):
l = iter(l)
chunk = list(islice(l, n))
while chunk:
yield chunk
chunk = list(islice(l, n))
Функциональный аромат из вышеперечисленных:
def isplitter2(l, n):
return takewhile(bool,
(tuple(islice(start, n))
for start in repeat(iter(l))))
ИЛИ ЖЕ:
def chunks_gen_sentinel(n, seq):
continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
return iter(imap(tuple, continuous_slices).next,())
ИЛИ ЖЕ:
def chunks_gen_filter(n, seq):
continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
return takewhile(bool,imap(tuple, continuous_slices))
Нет причин избегать len() в больших списках; это операция с постоянным временем.
def chunk(input, size):
return map(None, *([iter(input)] * size))
map(None, iter) равен izip_longest(iter).
@TomaszWysocki Не могли бы вы объяснить * перед вашим кортежем итератора? Возможно, в тексте вашего ответа, но я заметил, что * раньше использовал этот способ в Python. Спасибо!
@theJollySin В этом контексте он называется оператором splat. Его использование объясняется здесь - stackoverflow.com/questions/5917522/unzipping-and-the-operat или.
Close, но в последнем фрагменте нет элементов, которые нужно заполнить. Это может быть или не быть дефектом. Хотя действительно классный узор.
Простой, но элегантный
l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]
или если вы предпочитаете:
def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
Не дублируй переменную как арабское число. В некоторых шрифтах 1 и l неотличимы. Как и 0 и O. А иногда даже I и 1.
@Alfe Неисправные шрифты. Люди не должны использовать такие шрифты. Не для программирования, не для что-нибудь.
Лямбды предназначены для использования в качестве безымянных функций. Нет смысла их так использовать. Вдобавок это усложняет отладку, поскольку в случае ошибки трассировка будет сообщать «в <лямбда>», а не «по частям». Желаю вам удачи в поиске проблемы, если у вас их целая куча :)
внутри xrange в print [l[x:x+10] for x in xrange(1, len(l), 10)] должно быть 0, а не 1
range.
Если у вас, например, размер блока 3, вы можете сделать:
zip(*[iterable[i::3] for i in range(3)])
источник: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/
Я бы использовал это, когда размер моего фрагмента - это фиксированное число, которое я могу ввести, например. «3» и никогда не изменится.
Это не работает, если len (iterable)% 3! = 0. Последняя (короткая) группа чисел не будет возвращена.
Рассмотрите возможность использования частей matplotlib.cbook
Например:
import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
print s
Похоже, вы случайно создали две учетные записи. Вы можете связаться с командой объединить их, что позволит вам восстановить права прямого редактирования ваших вкладов.
def chunks(iterable,n):
"""assumes n is an integer>0
"""
iterable=iter(iterable)
while True:
result=[]
for i in range(n):
try:
a=next(iterable)
except StopIteration:
break
else:
result.append(a)
if result:
yield result
else:
break
g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
Хотя это может выглядеть не таким коротким или красивым, как многие ответы на основе itertools, этот на самом деле работает, если вы хотите распечатать второй подсписок перед доступом к первому, т.е. вы можете установить i0 = next (g2); i1 = следующий (g2); и используйте i1 перед использованием i0, и он не сломается !!
Я понимаю, что этот вопрос старый (наткнулся на него в Google), но наверняка что-то вроде следующего намного проще и понятнее любого из огромных сложных предложений и использует только нарезку:
def chunker(iterable, chunksize):
for i,c in enumerate(iterable[::chunksize]):
yield iterable[i*chunksize:(i+1)*chunksize]
>>> for chunk in chunker(range(0,100), 10):
... print list(chunk)
...
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
... etc ...
См. эта ссылка
>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>>
Python3
Хорошо, но отбрасывает элементы в конце, если размер не соответствует целому количеству фрагментов, например. грамм. zip(*[iter(range(7))]*3) возвращает только [(0, 1, 2), (3, 4, 5)] и забывает 6 из ввода.
OP написал: «У меня есть список произвольной длины, и мне нужно разбить его на части равного размера и работать с ним». Может быть, я что-то упускаю, но как получить «куски равного размера» из списка произвольной длины, не отбрасывая куски, которые короче «равного размера»
использование списков Python
[range(t,t+10) for t in range(1,1000,10)]
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
[11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27, 28, 29, 30],....
....[981, 982, 983, 984, 985, 986, 987, 988, 989, 990],
[991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
посетите эта ссылка, чтобы узнать о составлении списков
Как бы вы применили свой подход к существующему списку, который приходит на вход?
@Alfe for chunk in [some_list[i:i + 10] for i in range(0, len(some_list), 10)]: print chunk
Таким образом, это очень похоже на принятый верхний ответ ;-)
Я знаю, что это старовато, но numpy.array_split еще никто не упомянул:
import numpy as np
lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
# array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
# array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
# array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
# array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]
Это позволяет вам установить общее количество фрагментов, а не количество элементов в каждом фрагменте.
вы можете посчитать сами. если у вас есть 10 элементов, вы можете сгруппировать их в блоки по 2, 5 элементов или пять блоков по 2 элемента
+1 Это мое любимое решение, поскольку оно разбивает массив на массивы размером равномерно, в то время как другие решения этого не делают (во всех других рассмотренных мною решениях последний массив может быть сколь угодно маленьким).
@MiniQuark, но что это делать, если количество блоков не является фактором исходного размера массива?
@Baldrickk Если вы разделите N элементов на K фрагментов, то первые N% K фрагментов будут содержать N // K + 1 элементов, а остальные будут иметь N // K элементов. Например, если вы разделите массив, содержащий 108 элементов, на 5 фрагментов, то первые 108% 5 = 3 фрагментов будут содержать 108 // 5 + 1 = 22 элемента, а остальные фрагменты будут иметь 108 // 5 = 21 элементы.
Обратите внимание: если фрагментов больше, чем элементов массива, это решение дополнит результат пустыми массивами.
Как разбить его на массив равного размера? В качестве примера, скажем, я хочу разделить массив np.arange (14) на равный размер массива размером 5. Окончательный результат, который я ищу, - это три массива, два размера - 5 и один - 4.
In [259]: get_in_chunks = lambda itr,n: ( (v for _,v in g) for _,g in itertools.groupby(enumerate(itr),lambda (ind,_): ind/n)) In [260]: list(list(x) for x in get_in_chunks(range(30),7)) Out[260]: [[0, 1, 2, 3, 4, 5, 6], [7, 8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27], [28, 29]]
g = get_in_chunks (диапазон (30), 7); i0 = следующий (g); i1 = следующий (g); список (i1); список (i0); Последняя оценка пуста. Скрытое требование о доступе ко всем подспискам по порядку кажется мне здесь действительно плохим, потому что цель таких утилит часто состоит в том, чтобы перетасовать данные различными способами.
Мне очень нравится версия документа Python, предложенная Цотом и Дж. Ф. Себастьяном, но у него есть два недостатка:
Я часто использую это в своем коде:
from itertools import islice
def chunks(n, iterable):
iterable = iter(iterable)
while True:
yield tuple(islice(iterable, n)) or iterable.next()
ОБНОВЛЕНИЕ: версия с ленивыми кусками:
from itertools import chain, islice
def chunks(n, iterable):
iterable = iter(iterable)
while True:
yield chain([next(iterable)], islice(iterable, n-1))
Какое условие разрыва цикла while True?
@wjandrea: StopIteration возникает, когда tuple пуст и iterable.next() запускается. Однако не работает должным образом в современном Python, где выход из генератора должен выполняться с помощью return, а не с помощью StopIteration. try/except StopIteration: return вокруг всего цикла (и изменение iterable.next() на next(iterable) для совместимости между версиями) исправляет это, по крайней мере, с минимальными накладными расходами.
В библиотеке Toolz есть функция partition для этого:
from toolz.itertoolz.core import partition
list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
Это кажется самым простым из всех предложений. Мне просто интересно, действительно ли может быть правдой, что для получения такой функции разделения нужно использовать стороннюю библиотеку. Я ожидал, что что-то эквивалентное с этой функцией разделения будет существовать как встроенный язык.
вы можете сделать раздел с помощью itertools. но мне нравится библиотека toolz. это библиотека, вдохновленная Clojure, для работы с коллекциями в функциональном стиле. вы не получаете неизменяемости, но получаете небольшой словарный запас для работы с простыми коллекциями. В качестве плюса, cytoolz написан на cython и имеет хороший прирост производительности. github.com/pytoolz/cytoolzmatthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz
Ссылка из комментария Зака работает, если вы опустите косую черту в конце: matthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz
Да, это старый вопрос, но мне пришлось его опубликовать, потому что он даже немного короче, чем аналогичные. Да, результат выглядит смешанным, но если он примерно одинаковой длины ...
>>> n = 3 # number of groups
>>> biglist = range(30)
>>>
>>> [ biglist[i::n] for i in xrange(n) ]
[[0, 3, 6, 9, 12, 15, 18, 21, 24, 27],
[1, 4, 7, 10, 13, 16, 19, 22, 25, 28],
[2, 5, 8, 11, 14, 17, 20, 23, 26, 29]]
«Куски одинакового размера» для меня означает, что все они имеют одинаковую длину или, если не считать эту опцию, длину минимальная дисперсия. Например. 5 корзин на 21 предмет могут дать следующие результаты:
>>> import statistics
>>> statistics.variance([5,5,5,5,1])
3.2
>>> statistics.variance([5,4,4,4,4])
0.19999999999999998
Практическая причина предпочесть последний результат: если вы использовали эти функции для распределения работы, у вас была заложена перспектива того, что одна из них, вероятно, закончит намного раньше других, поэтому он будет сидеть без дела, пока другие будут продолжать усердно работать.
Когда я изначально писал этот ответ, ни один из других ответов не был кусками одинакового размера - все они оставляют короткий фрагмент в конце, поэтому они плохо сбалансированы и имеют более высокое, чем необходимо, изменение длины.
Например, текущий лучший ответ заканчивается на:
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]
Другие, такие как list(grouper(3, range(7))) и chunk(range(7), 3), возвращают: [(0, 1, 2), (3, 4, 5), (6, None, None)]. None - это просто набивка и, на мой взгляд, довольно неэлегантная. Они НЕ разделяют итерации равномерно.
Почему мы не можем разделить их лучше?
Сбалансированное решение высокого уровня с использованием itertools.cycle, как я мог бы это сделать сегодня. Вот установка:
from itertools import cycle
items = range(10, 75)
number_of_baskets = 10
Теперь нам нужны наши списки для заполнения элементов:
baskets = [[] for _ in range(number_of_baskets)]
Наконец, мы заархивируем элементы, которые собираемся выделить, вместе с циклом корзин, пока у нас не закончатся элементы, что семантически это именно то, что мы хотим:
for element, basket in zip(items, cycle(baskets)):
basket.append(element)
Вот результат:
>>> from pprint import pprint
>>> pprint(baskets)
[[10, 20, 30, 40, 50, 60, 70],
[11, 21, 31, 41, 51, 61, 71],
[12, 22, 32, 42, 52, 62, 72],
[13, 23, 33, 43, 53, 63, 73],
[14, 24, 34, 44, 54, 64, 74],
[15, 25, 35, 45, 55, 65],
[16, 26, 36, 46, 56, 66],
[17, 27, 37, 47, 57, 67],
[18, 28, 38, 48, 58, 68],
[19, 29, 39, 49, 59, 69]]
Чтобы реализовать это решение, мы пишем функцию и предоставляем аннотации типов:
from itertools import cycle
from typing import List, Any
def cycle_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
baskets = [[] for _ in range(min(maxbaskets, len(items)))]
for item, basket in zip(items, cycle(baskets)):
basket.append(item)
return baskets
В приведенном выше примере мы берем наш список предметов и максимальное количество корзин. Мы создаем список пустых списков, в который добавляем каждый элемент в циклическом стиле.
Другое элегантное решение - использовать срезы, в частности, менее часто используемый аргумент шаг для срезов. то есть:
start = 0
stop = None
step = number_of_baskets
first_basket = items[start:stop:step]
Это особенно элегантно, поскольку срезы не заботятся о длине данных - результат, наша первая корзина, имеет ровно столько, сколько нужно. Нам нужно только увеличить начальную точку для каждой корзины.
Фактически, это может быть однострочный текст, но мы сделаем его многострочным для удобства чтения и во избежание чрезмерно длинной строки кода:
from typing import List, Any
def slice_baskets(items: List[Any], maxbaskets: int) -> List[List[Any]]:
n_baskets = min(maxbaskets, len(items))
return [items[i::n_baskets] for i in range(n_baskets)]
А islice из модуля itertools обеспечит ленивый итерационный подход, подобный тому, о котором изначально просили в вопросе.
Я не ожидаю, что большинство вариантов использования принесут большую пользу, поскольку исходные данные уже полностью материализованы в списке, но для больших наборов данных это может сэкономить почти половину использования памяти.
from itertools import islice
from typing import List, Any, Generator
def yield_islice_baskets(items: List[Any], maxbaskets: int) -> Generator[List[Any], None, None]:
n_baskets = min(maxbaskets, len(items))
for i in range(n_baskets):
yield islice(items, i, None, n_baskets)
Просматривайте результаты с помощью:
from pprint import pprint
items = list(range(10, 75))
pprint(cycle_baskets(items, 10))
pprint(slice_baskets(items, 10))
pprint([list(s) for s in yield_islice_baskets(items, 10)])
Вот еще одно сбалансированное решение, адаптированное из функции, которую я использовал в производстве в прошлом, которая использует оператор по модулю:
def baskets_from(items, maxbaskets=25):
baskets = [[] for _ in range(maxbaskets)]
for i, item in enumerate(items):
baskets[i % maxbaskets].append(item)
return filter(None, baskets)
И я создал генератор, который делает то же самое, если вы поместите его в список:
def iter_baskets_from(items, maxbaskets=3):
'''generates evenly balanced baskets from indexable iterable'''
item_count = len(items)
baskets = min(item_count, maxbaskets)
for x_i in range(baskets):
yield [items[y_i] for y_i in range(x_i, item_count, baskets)]
И наконец, поскольку я вижу, что все вышеперечисленные функции возвращают элементы в непрерывном порядке (в том виде, в котором они были заданы):
def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
'''
generates balanced baskets from iterable, contiguous contents
provide item_count if providing a iterator that doesn't support len()
'''
item_count = item_count or len(items)
baskets = min(item_count, maxbaskets)
items = iter(items)
floor = item_count // baskets
ceiling = floor + 1
stepdown = item_count % baskets
for x_i in range(baskets):
length = ceiling if x_i < stepdown else floor
yield [items.next() for _ in range(length)]
Чтобы проверить их:
print(baskets_from(range(6), 8))
print(list(iter_baskets_from(range(6), 8)))
print(list(iter_baskets_contiguous(range(6), 8)))
print(baskets_from(range(22), 8))
print(list(iter_baskets_from(range(22), 8)))
print(list(iter_baskets_contiguous(range(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(range(26), 5))
print(list(iter_baskets_from(range(26), 5)))
print(list(iter_baskets_contiguous(range(26), 5)))
Что распечатывает:
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]
Обратите внимание, что непрерывный генератор предоставляет фрагменты той же длины, что и два других, но все элементы в порядке, и они разделены так же поровну, как можно разделить список дискретных элементов.
Вы говорите, что ни одно из вышеперечисленного не обеспечивает куски одинакового размера. Но Вот этот делает, как и Вот этот.
@senderle, и первый, list(grouper(3, xrange(7))), и второй, chunk(xrange(7), 3), оба возвращают: [(0, 1, 2), (3, 4, 5), (6, None, None)]. None - это просто набивка и, на мой взгляд, довольно неэлегантная. Они НЕ разделяют итерации равномерно. Спасибо за голос!
Вы поднимаете вопрос (не делая этого явно, поэтому я делаю это сейчас здесь), будут ли блоки одинакового размера (кроме последнего, если это невозможно) или сбалансированный (как можно более хороший) результат чаще всего будет необходим. Вы предполагаете, что предпочтение должно быть отдано сбалансированному решению; это может быть правдой, если то, что вы программируете, близко к реальному миру (например, алгоритм раздачи карт для симулированной карточной игры). В других случаях (например, при заполнении строк словами) лучше оставить строки как можно более полными. Так что я не могу предпочитать одно другому; они просто для разных случаев использования.
@ ChristopherBarrington-Leigh Хороший момент, для DataFrames вам, вероятно, следует использовать срезы, поскольку я считаю, что объекты DataFrame обычно не копируются при срезании, например import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
@AaronHall Ой. Я удалил свой комментарий, потому что я усомнился в своей критике, но вы быстро угадали. Спасибо! Фактически, мое утверждение, что это не работает для фреймов данных, верно. Если items является фреймом данных, просто используйте yield items [диапазон (x_i, item_count, корзинки)] в качестве последней строки. Я предложил отдельный (еще один) ответ, в котором вы указываете желаемый (минимальный) размер группы.
@ ChristopherBarrington-Leigh Спасибо, очень мило с вашей стороны. Однако я бы не стал использовать для этого код из своего ответа. Если вы перебираете DataFrame, вы можете использовать iterrows. Я бы не стал использовать диапазон для нарезки, он создает объект в памяти. Я бы предпочел объект среза, созданный с помощью синтаксиса среза, например. i::3 или эквивалентный slice(i, None, 3).
Мне это нравится, но я бы хотел, чтобы он работал с лямбдой произвольной длины, а не только с len ()
Не поддавайтесь соблазну использовать [[]]*maxbaskets, это не то же самое, что [[] for _ in range(maxbaskets)]. В первом случае действительно существует только один экземпляр корзины, на которую ссылаются несколько раз.
Во многих случаях «коротышка» на самом деле является более желательным вариантом. Поскольку в этом вопросе ничего не указано, должен ли отображаться в конце фрагмент меньшего размера или нет, возможно, ваш ответ был бы более уместным на соответствующий вопрос 2010 года Разделение списка на N частей примерно равной длины.
@wim, спасибо за критику, я ценю ваш вклад - я еще подумаю об этом и, возможно, обновлю свой ответ. «Куски одинакового размера» для меня означают, что все они имеют одинаковую длину или, за исключением этого, с минимальным отклонением. Например. В 5 корзинах по 21 элементу может быть >>> статистика импорта >>> statistics.variance ([5,5,5,5,1]) 3,2 >>> statistics.variance ([5,4,4,4,4] ) 0,19999999999999998
Дисперсия - это всего лишь один путь - другой разумной интерпретацией может быть «максимальное количество ведер с одинаковой длиной», когда лучшая стратегия - иметь коротышку. И во многих реальных случаях вы не можете знать количество элементов заранее (например, отправка пакетов по сети, когда вы не знаете размер данных, но у вас есть верхняя граница размера пакета, например потоковое сжатие). Думаю, я повторяю то, что уже сказал Альфе - ни один из подходов объективно не лучше, зависит от проблемы :)
Я удивлен, что никто не подумал об использовании iterформа с двумя аргументами:
from itertools import islice
def chunk(it, size):
it = iter(it)
return iter(lambda: tuple(islice(it, size)), ())
Демо:
>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
Это работает с любыми итерациями и лениво выводит результат. Он возвращает кортежи, а не итераторы, но, тем не менее, я думаю, что в нем есть определенная элегантность. Это также не подкладывает; если вы хотите заполнить отступы, достаточно будет простого варианта вышеперечисленного:
from itertools import islice, chain, repeat
def chunk_pad(it, size, padval=None):
it = chain(iter(it), repeat(padval))
return iter(lambda: tuple(islice(it, size)), (padval,) * size)
Демо:
>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]
Как и решения на основе izip_longest, вышеупомянутый всегда не работает. Насколько мне известно, не существует одно- или двухстрочного рецепта itertools для функции, которую выполняет необязательно. Комбинируя два вышеупомянутых подхода, этот подход довольно близок:
_no_padding = object()
def chunk(it, size, padval=_no_padding):
if padval == _no_padding:
it = iter(it)
sentinel = ()
else:
it = chain(iter(it), repeat(padval))
sentinel = (padval,) * size
return iter(lambda: tuple(islice(it, size)), sentinel)
Демо:
>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]
Я считаю, что это самый короткий из предложенных блоков, который предлагает дополнительное заполнение.
Как Томаш Гандор наблюдаемый, два фрагмента заполнения неожиданно остановятся, если они встретят длинную последовательность значений заполнения. Вот последний вариант, который разумным образом решает эту проблему:
_no_padding = object()
def chunk(it, size, padval=_no_padding):
it = iter(it)
chunker = iter(lambda: tuple(islice(it, size)), ())
if padval == _no_padding:
yield from chunker
else:
for ch in chunker:
yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))
Демо:
>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]
Замечательно, ваша простая версия - моя любимая. Другие тоже придумали базовое выражение islice(it, size) и встроили его (как это сделал я) в конструкцию цикла. Только вы думали о версии iter() с двумя аргументами (я совершенно не знал о ней), которая делает ее супер-элегантной (и, вероятно, наиболее эффективной с точки зрения производительности). Я понятия не имел, что первый аргумент iter изменится на функцию с 0 аргументами, когда будет задан часовой. Вы возвращаете итератор блоков (pot. Infinite), можете использовать итератор (pot. Infinite) в качестве входных данных, не имеете len() и срезов массива. Потрясающий!
Вот почему я зачитываю ответы, а не просматриваю только верхнюю пару. В моем случае обязательным требованием было необязательное заполнение, и я тоже узнал о форме iter с двумя аргументами.
Я поддержал это, но все же - давайте не будем переоценивать это! Во-первых, лямбда может быть плохой (медленное закрытие по итератору it. Во-вторых, что наиболее важно, вы закончите преждевременно, если кусок padval действительно существует в вашей итерации и должен быть обработан.
@TomaszGandor, я принимаю твое первое очко! Хотя я понимаю, что лямбда не медленнее, чем обычная функция, конечно, вы правы в том, что вызов функции и поиск закрытия замедлят это. Я не знаю, каков будет относительный удар по производительности, например, по сравнению с подходом izip_longest - я подозреваю, что это может быть сложный компромисс. Но ... разве проблема padval не разделяется всеми здесь ответами, которые предлагают параметр padval?
Не знаю, все ли (просто прочтите популярные ответы и обратите внимание на этот). Позвольте мне проиллюстрировать это: chunk_pad([1, 2, None, None, 5], 2) ДОЛЖЕН генерировать: (1, 2), (None, None), (5, None), вместо этого он просто генерирует (1, 2). То же самое для chunk([1, 2, (), (), 5], 2), но второй сгенерированный элемент должен быть ((), ()). Проблема не может быть устранена добавлением большего количества if, это характерно для использования iter с дозорным.
@TomaszGandor Понятно, я не понял, о чем ты говоришь. Вы правы, эта проблема в некотором роде уникальна для этого ответа. Я считаю, что есть способ обойти это - я немного подумаю.
Вам не нужен обходной путь, если вы знаете, что все фрагменты заполнения действительно недействительны. Это не всегда так, но для многих сценариев это нормально.
@TomaszGandor, достаточно честно! Но создать версию, которая это исправляет, было несложно. (Также обратите внимание, что самая первая версия, которая использует () в качестве дозорного, делает работает правильно. Это потому, что tuple(islice(it, size)) дает (), когда it пуст.)
Специально для этого я написал небольшую библиотеку, доступную здесь. Функция библиотеки chunked особенно эффективна, поскольку она реализована как генератор, поэтому в определенных ситуациях можно сэкономить значительный объем памяти. Он также не полагается на нотацию срезов, поэтому можно использовать любой произвольный итератор.
import iterlib
print list(iterlib.chunked(xrange(1, 1000), 10))
# prints [(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), (11, 12, 13, 14, 15, 16, 17, 18, 19, 20), ...]
Как @AaronHall, я пришел сюда в поисках кусков примерно одинакового размера. Есть разные толкования этого. В моем случае, если желаемый размер равен N, я бы хотел, чтобы каждая группа имела размер> = N. Таким образом, сирот, созданных в большинстве из перечисленных выше, следует перераспределить в другие группы.
Это можно сделать с помощью:
def nChunks(l, n):
""" Yield n successive chunks from l.
Works for lists, pandas dataframes, etc
"""
newn = int(1.0 * len(l) / n + 0.5)
for i in xrange(0, n-1):
yield l[i*newn:i*newn+newn]
yield l[n*newn-newn:]
(из Разделение списка на N частей примерно равной длины), просто вызвав его как nChunks (l, l / n) или nChunks (l, floor (l / n))
кажется, дает несколько пустых фрагментов (len = 26, 10) или последний очень несбалансированный фрагмент (len = 26, 11).
позволяя r быть размером блока, а L быть начальным списком, вы можете сделать это.
chunkL = [ [i for i in L[r*k:r*(k+1)] ] for k in range(len(L)/r)]
Используйте понимание списка:
l = [1,2,3,4,5,6,7,8,9,10,11,12]
k = 5 #chunk size
print [tuple(l[x:y]) for (x, y) in [(x, x+k) for x in range(0, len(l), k)]]
Еще одна более откровенная версия.
def chunkList(initialList, chunkSize):
"""
This function chunks a list into sub lists
that have a length equals to chunkSize.
Example:
lst = [3, 4, 9, 7, 1, 1, 2, 3]
print(chunkList(lst, 3))
returns
[[3, 4, 9], [7, 1, 1], [2, 3]]
"""
finalList = []
for i in range(0, len(initialList), chunkSize):
finalList.append(initialList[i:i+chunkSize])
return finalList
(12 сентября 2016 г.) Этот ответ не зависит от языка и его легче всего читать.
Я видел самый потрясающий ответ Python в дублировать этого вопроса:
from itertools import zip_longest
a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]
Вы можете создать n-кортеж для любого n. Если a = range(1, 15), то результат будет:
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]
Если список разделен поровну, то вы можете заменить zip_longest на zip, иначе тройка (13, 14, None) будет потеряна. Python 3 используется выше. Для Python 2 используйте izip_longest.
это хорошо, если ваш список и фрагменты короткие, но как вы могли бы адаптировать это, чтобы разделить свой список на куски по 1000? вы не собираетесь кодировать почтовый индекс (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
zip(i, i, i, ... i) с аргументами "chunk_size" для zip () может быть записан как zip(*[i]*chunk_size), конечно, спорный вопрос, хорошая это идея или нет.
Обратной стороной этого является то, что если вы не разделяете равномерно, вы отбрасываете элементы, так как zip останавливается на самом коротком итеративном значении - & izip_longest добавит элементы по умолчанию.
Следует использовать zip_longest, как это сделано в: stackoverflow.com/a/434411/1959808
В ответе с range(1, 15) элементы уже отсутствуют, потому что в range(1, 15) 14 элементов, а не 15.
В приведенном выше ответе (от koffein) есть небольшая проблема: список всегда разбивается на равное количество разделов, а не на равное количество элементов на раздел. Это моя версия. «// chs + 1» учитывает, что количество элементов не может быть разделено точно на размер раздела, поэтому последний раздел будет заполнен только частично.
# Given 'l' is your list
chs = 12 # Your chunksize
partitioned = [ l[i*chs:(i*chs)+chs] for i in range((len(l) // chs)+1) ]
Но если размер блока делает точно делит количество элементов, то это включает в себя список нулевой длины в конце.
код:
def split_list(the_list, chunk_size):
result_list = []
while the_list:
result_list.append(the_list[:chunk_size])
the_list = the_list[chunk_size:]
return result_list
a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print split_list(a_list, 3)
результат:
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
CHUNK = 4
[a[i*CHUNK:(i+1)*CHUNK] for i in xrange((len(a) + CHUNK - 1) / CHUNK )]
Не могли бы вы объяснить свой ответ поподробнее?
Работа в обратном направлении: (len (a) + CHUNK -1) / CHUNK Дает вам количество фрагментов, которые у вас останутся. Затем для каждого фрагмента с индексом i мы генерируем подмассив исходного массива следующим образом: a [i * CHUNK: (i + 1) * CHUNK], где i * CHUNK - это индекс первого элемента для помещается в подмассив, и (i + 1) * CHUNK - это 1 после последнего элемента, помещаемого в подмассив. Это решение использует понимание списков, поэтому оно может быть быстрее для больших массивов.
Я пришел к следующему решению без создания объекта временного списка, который должен работать с любым повторяемым объектом. Обратите внимание, что эта версия для Python 2.x:
def chunked(iterable, size):
stop = []
it = iter(iterable)
def _next_chunk():
try:
for _ in xrange(size):
yield next(it)
except StopIteration:
stop.append(True)
return
while not stop:
yield _next_chunk()
for it in chunked(xrange(16), 4):
print list(it)
Выход:
[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
[12, 13, 14, 15]
[]
Как видите, если len (iterable)% size == 0, то у нас есть дополнительный пустой объект-итератор. Но я не думаю, что это большая проблема.
Как вы думаете, что должен дать следующий код? я = 0
Попробуйте выполнять list (it) только на каждой второй итерации цикла, т.е. добавьте счетчик и проверьте, 0 mod 2. Ожидаемое поведение - печатать только каждую вторую строку вашего вывода. Фактическое поведение заключается в печати каждой строки.
Поскольку мне пришлось сделать что-то подобное, вот мое решение с генератором и размером партии:
def pop_n_elems_from_generator(g, n):
elems = []
try:
for idx in xrange(0, n):
elems.append(g.next())
return elems
except StopIteration:
return elems
На данный момент, я думаю, нам нужен рекурсивный генератор, на всякий случай ...
В Python 2:
def chunks(li, n):
if li == []:
return
yield li[:n]
for e in chunks(li[n:], n):
yield e
В Python 3:
def chunks(li, n):
if li == []:
return
yield li[:n]
yield from chunks(li[n:], n)
Кроме того, в случае массового вторжения инопланетян может пригодиться декорированный рекурсивный генератор:
def dec(gen):
def new_gen(li, n):
for e in gen(li, n):
if e == []:
return
yield e
return new_gen
@dec
def chunks(li, n):
yield li[:n]
for e in chunks(li[n:], n):
yield e
На данный момент, я думаю, нам нужна обязательная анонимно-рекурсивная функция.
Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
chunks = Y(lambda f: lambda n: [n[0][:n[1]]] + f((n[0][n[1]:], n[1])) if len(n[0]) > 0 else [])
лямбда-функция медленная. понимание списка будет быстрее
@SanjayPoongunran благодарит вас за отзыв, но это Python, мы здесь не для производительности (мы бы писали на C), а для удобства чтения.
@JulienPalard О да, этот ответ - это удобочитаемость.
[AA[i:i+SS] for i in range(len(AA))[::SS]]
Где AA - это массив, SS - размер блока. Например:
>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3
это самое лучшее и простое.
коротко и просто. простота важнее сложности.
Очень полезный! Спасибо!
Согласно этот ответ, ответ, получивший наибольшее количество голосов, оставляет в конце «коротышку». Вот мое решение, чтобы действительно получить куски одинакового размера, без каких-либо коротких замыканий. Он в основном пытается выбрать именно то дробное место, где он должен разделить список, но просто округляет его до ближайшего целого числа:
from __future__ import division # not needed in Python 3
def n_even_chunks(l, n):
"""Yield n as even chunks as possible from l."""
last = 0
for i in range(1, n+1):
cur = int(round(i * (len(l) / n)))
yield l[last:cur]
last = cur
Демонстрация:
>>> pprint.pprint(list(n_even_chunks(list(range(100)), 9)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]
>>> pprint.pprint(list(n_even_chunks(list(range(100)), 11)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8],
[9, 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]]
Сравните с ответом chunks, получившим наибольшее количество голосов:
>>> pprint.pprint(list(chunks(list(range(100)), 100//9)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]
>>> pprint.pprint(list(chunks(list(range(100)), 100//11)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8],
[9, 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]]
Это решение не работает в некоторых ситуациях: - когда n> len (l) - для l = [0,1,2,3,4] и n = 3 оно возвращает [[0], [1], [2] ] вместо [[0,1], [2,3], [4]]
@DragonTux: А, я написал функцию для Python 3 - она дает [[0, 1], [2], [3, 4]]. Я добавил будущий импорт, чтобы он работал и в Python 2
Большое спасибо. Я все время забываю о тонких различиях между Python 2 и 3.
Поскольку все здесь говорят об итераторах. boltons имеет для этого идеальный метод, называемый iterutils.chunked_iter.
from boltons import iterutils
list(iterutils.chunked_iter(list(range(50)), 11))
Выход:
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]
Но если вы не хотите жалеть память, вы можете использовать старый способ и сохранить полный list в первую очередь с помощью iterutils.chunked.
И этот действительно работает независимо от порядка просмотра подитераторов !!
Вы можете использовать функцию numpy array_split, например, np.array_split(np.array(data), 20), чтобы разделить ее на 20 частей примерно одинакового размера.
Чтобы убедиться, что куски точно равны по размеру, используйте np.split.
У меня есть одно решение ниже, которое действительно работает, но более важным, чем это решение, является несколько комментариев по другим подходам. Во-первых, хорошее решение не должно требовать одного цикла через суб-итераторы по порядку. Если я сбегу
g = paged_iter(list(range(50)), 11))
i0 = next(g)
i1 = next(g)
list(i1)
list(i0)
Соответствующий вывод для последней команды:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
нет
[]
Поскольку большинство решений на основе itertools здесь возвращаются. Это не просто обычное скучное ограничение на доступ к итераторам по порядку. Представьте, что потребитель пытается очистить неверно введенные данные, которые меняют соответствующий порядок блоков из 5, т.е. данные выглядят как [B5, A5, D5, C5] и должны выглядеть как [A5, B5, C5, D5] (где A5 - это всего лишь пять элементов, а не подсписок). Этот потребитель будет смотреть на заявленное поведение функции группировки и, не колеблясь, написать цикл вроде
i = 0
out = []
for it in paged_iter(data,5)
if (i % 2 == 0):
swapped = it
else:
out += list(it)
out += list(swapped)
i = i + 1
Это приведет к загадочно неправильным результатам, если вы втайне предположите, что под-итераторы всегда полностью используются по порядку. Еще хуже, если вы хотите чередовать элементы из кусков.
Во-вторых, приличное количество предлагаемых решений неявно полагается на тот факт, что итераторы имеют детерминированный порядок (они, например, не устанавливаются), и хотя некоторые из решений, использующих islice, могут быть в порядке, это меня беспокоит.
В-третьих, подход itertools grouper работает, но рецепт полагается на внутреннее поведение функций zip_longest (или zip), которое не является частью их опубликованного поведения. В частности, функция группировщика работает только потому, что в zip_longest (i0 ... in) следующая функция всегда вызывается в порядке next (i0), next (i1), ... next (in) перед тем, как начать заново. Когда группер передает n копий одного и того же объекта-итератора, он полагается на это поведение.
Наконец, хотя приведенное ниже решение может быть улучшено, если вы сделаете критикуемое выше предположение, что суб-итераторы доступны по порядку и полностью просматриваются без этого предположения, один ДОЛЖЕН неявно (через цепочку вызовов) или явно (через deques или другую структуру данных) хранить элементы для каждого подитератора где-то. Так что не тратьте время (как я) на предположение, что это можно обойти с помощью какого-нибудь хитрого трюка.
def paged_iter(iterat, n):
itr = iter(iterat)
deq = None
try:
while(True):
deq = collections.deque(maxlen=n)
for q in range(n):
deq.append(next(itr))
yield (i for i in deq)
except StopIteration:
yield (i for i in deq)
Вы также можете использовать функцию get_chunks библиотеки utilspie как:
>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]
Вы можете установить utilspie через pip:
sudo pip install utilspie
Отказ от ответственности: я создатель библиотеки утильспи.
Вот идея использования itertools.groupby:
def chunks(l, n):
c = itertools.count()
return (it for _, it in itertools.groupby(l, lambda x: next(c)//n))
Это возвращает генератор генераторов. Если вам нужен список списков, просто замените последнюю строку на
return [list(it) for _, it in itertools.groupby(l, lambda x: next(c)//n)]
Пример возврата списка списков:
>>> chunks('abcdefghij', 4)
[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'j']]
(Так что да, это страдает от «короткой проблемы», которая может быть, а может и не быть проблемой в данной ситуации.)
Опять же, это не удается, если суб-итераторы не оцениваются по порядку в случае генератора. Пусть c = chunks ('abcdefghij', 4) (как генератор). Затем установите i0 = next (c); i1 = следующий (c); list (i1) // ТОЧНО; list (i0) // Ухххх
@PeterGerdes, спасибо, что заметили это упущение; Я забыл, потому что всегда использовал генераторы groupby по порядку. В документации упоминается это ограничение: «Поскольку источник является общим, при расширении объекта groupby () предыдущая группа больше не видна».
@PeterGerdes Я думаю, что это можно решить, используя вместо этого enumerate, например: [[x for _, x in it] for _, it in itertools.groupby(enumerate(l), lambda x: x[0]//n)] (list (it) - это список пар (index, element) из-за enumerate)
Еще одно решение
def make_chunks(data, chunk_size):
while data:
chunk, data = data[:chunk_size], data[chunk_size:]
yield chunk
>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
... print chunk
...
[1, 2]
[3, 4]
[5, 6]
[7]
>>>
Это работает в v2 / v3, является встроенным, основанным на генераторе и использует только стандартную библиотеку:
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))
Просто сделайте (list(x) for x in split_groups('abcdefghij', 4)), а затем повторите их: в отличие от многих примеров здесь, это будет работать с группами любого размера.
Никакого волшебства, но просто и правильно:
def chunks(iterable, n):
"""Yield successive n-sized chunks from iterable."""
values = []
for i, item in enumerate(iterable, 1):
values.append(item)
if i % n == 0:
yield values
values = []
if values:
yield values
Я не думаю, что видел этот вариант, поэтому просто добавлю еще один :)):
def chunks(iterable, chunk_size):
i = 0;
while i < len(iterable):
yield iterable[i:i+chunk_size]
i += chunk_size
Мне было интересно узнать, как работают разные подходы, и вот они:
Протестировано на Python 3.5.1
import time
batch_size = 7
arr_len = 298937
#---------slice-------------
print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
if not arr:
break
tmp = arr[0:batch_size]
arr = arr[batch_size:-1]
print(time.time() - start)
#-----------index-----------
print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)
#----------batches 1------------
def batch(iterable, n=1):
l = len(iterable)
for ndx in range(0, l, n):
yield iterable[ndx:min(ndx + n, l)]
print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
tmp = x
print(time.time() - start)
#----------batches 2------------
from itertools import islice, chain
def batch(iterable, size):
sourceiter = iter(iterable)
while True:
batchiter = islice(sourceiter, size)
yield chain([next(batchiter)], batchiter)
print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
tmp = x
print(time.time() - start)
#---------chunks-------------
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
tmp = x
print(time.time() - start)
#-----------grouper-----------
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)
def grouper(iterable, n, padvalue=None):
"grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)
arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
tmp = x
print(time.time() - start)
Полученные результаты:
slice
31.18285083770752
index
0.02184295654296875
batches 1
0.03503894805908203
batches 2
0.22681021690368652
chunks
0.019841909408569336
grouper
0.006506919860839844
Тестирование с использованием библиотеки time - не лучшая идея, когда у нас есть модуль timeit
Мне не нравится идея разделения элементов по размеру блока, например скрипт может разделить от 101 до 3 частей как [50, 50, 1]. Для моих нужд мне нужно было пропорционально разделить и сохранить порядок. Сначала я написал свой собственный сценарий, который отлично работает и очень прост. Но я видел позже этот ответ, где скрипт лучше моего, рекомендую. Вот мой сценарий:
def proportional_dividing(N, n):
"""
N - length of array (bigger number)
n - number of chunks (smaller number)
output - arr, containing N numbers, diveded roundly to n chunks
"""
arr = []
if N == 0:
return arr
elif n == 0:
arr.append(N)
return arr
r = N // n
for i in range(n-1):
arr.append(r)
arr.append(N-r*(n-1))
last_n = arr[-1]
# last number always will be r <= last_n < 2*r
# when last_n == r it's ok, but when last_n > r ...
if last_n > r:
# ... and if difference too big (bigger than 1), then
if abs(r-last_n) > 1:
#[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7] # N=29, n=12
# we need to give unnecessary numbers to first elements back
diff = last_n - r
for k in range(diff):
arr[k] += 1
arr[-1] = r
# and we receive [3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2]
return arr
def split_items(items, chunks):
arr = proportional_dividing(len(items), chunks)
splitted = []
for chunk_size in arr:
splitted.append(items[:chunk_size])
items = items[chunk_size:]
print(splitted)
return splitted
items = [1,2,3,4,5,6,7,8,9,10,11]
chunks = 3
split_items(items, chunks)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm'], 3)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm', 'n'], 3)
split_items(range(100), 4)
split_items(range(99), 4)
split_items(range(101), 4)
и вывод:
[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'g', 'k', 'l', 'm']]
[['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'g'], ['k', 'l', 'm', 'n']]
[range(0, 25), range(25, 50), range(50, 75), range(75, 100)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 99)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 101)]
Вот список дополнительных подходов:
Дано
import itertools as it
import collections as ct
import more_itertools as mit
iterable = range(11)
n = 3
Код
Стандартная библиотека
list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
d = {}
for i, x in enumerate(iterable):
d.setdefault(i//n, []).append(x)
list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
dd[i//n].append(x)
list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]
list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]
использованная литература
zip_longest (связанный пост, связанный пост)setdefault (для упорядоченных результатов требуется Python 3.6+)collections.defaultdict (для упорядоченных результатов требуется Python 3.6+)more_itertools.chunked (связанные опубликованные)more_itertools.slicedmore_itertools.grouper (связанный пост)more_itertools.windowed (см. Также stagger, zip_offset)+ A third-party library that implements itertools recipes and more. > pip install more_itertools
import pprint pprint.pprint(list(chunks(range(10, 75), 10))) [range(10, 20), range(20, 30), range(30, 40), range(40, 50), range(50, 60), range(60, 70), range(70, 75)]Confer this implementation's result with the example usage result of the accepted answer.
Многие из вышеперечисленных функций предполагают, что длина всей итерации заранее известна или, по крайней мере, их легко вычислить.
Для некоторых потоковых объектов это означало бы сначала загрузку полных данных в память (например, чтобы загрузить весь файл), чтобы получить информацию о длине.
Однако, если вы еще не знаете полный размер, вы можете вместо этого использовать этот код:
def chunks(iterable, size):
"""
Yield successive chunks from iterable, being `size` long.
https://stackoverflow.com/a/55776536/3423324
:param iterable: The object you want to split into pieces.
:param size: The size each of the resulting pieces should have.
"""
i = 0
while True:
sliced = iterable[i:i + size]
if len(sliced) == 0:
# to suppress stuff like `range(max, max)`.
break
# end if
yield sliced
if len(sliced) < size:
# our slice is not the full length, so we must have passed the end of the iterator
break
# end if
i += size # so we start the next chunk at the right place.
# end while
# end def
Это работает, потому что команда slice вернет меньше / нет элементов, если вы передали конец итерации:
"abc"[0:2] == 'ab'
"abc"[2:4] == 'c'
"abc"[4:6] == ''
Теперь мы используем результат среза и вычисляем длину сгенерированного фрагмента. Если оно меньше ожидаемого, мы знаем, что можем завершить итерацию.
Таким образом, итератор не будет выполняться без доступа.
Если вас не волнует порядок:
> from itertools import groupby
> batch_no = 3
> data = 'abcdefgh'
> [
[x[1] for x in x[1]]
for x in
groupby(
sorted(
(x[0] % batch_no, x[1])
for x in
enumerate(data)
),
key=lambda x: x[0]
)
]
[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f']]
Это решение не генерирует наборы одинакового размера, но распределяет значения, чтобы партии были как можно больше, сохраняя при этом количество сгенерированных пакетов.
Пакет python pydash может быть хорошим выбором.
from pydash.arrays import chunk
ids = ['22', '89', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '1']
chunk_ids = chunk(ids,5)
print(chunk_ids)
# output: [['22', '89', '2', '3', '4'], ['5', '6', '7', '8', '9'], ['10', '11', '1']]
для получения дополнительной информации обратитесь к список фрагментов pydash
аккуратный! и это то, что на самом деле находится под капотом pydash.arrays.chunk: chunks = int (ceil (len (array) / float (size))) return [array [i * size: (i + 1) * size] для я в диапазоне (фрагменты)]
Этот вопрос напоминает мне метод .comb(n) Raku (ранее Perl 6). Он разбивает строки на куски размером с n. (Есть еще кое-что, но я опущу детали.)
Достаточно просто реализовать аналогичную функцию в Python3 в виде лямбда-выражения:
comb = lambda s,n: (s[i:i+n] for i in range(0,len(s),n))
Тогда вы можете назвать это так:
some_list = list(range(0, 20)) # creates a list of 20 elements
generator = comb(some_list, 4) # creates a generator that will generate lists of 4 elements
for sublist in generator:
print(sublist) # prints a sublist of four elements, as it's generated
Конечно, вам не нужно назначать генератор переменной; вы можете просто перебрать его прямо так:
for sublist in comb(some_list, 4):
print(sublist) # prints a sublist of four elements, as it's generated
В качестве бонуса эта функция comb() также работает со строками:
list( comb('catdogant', 3) ) # returns ['cat', 'dog', 'ant']
Подход старой школы, не требующий itertools, но работающий с произвольными генераторами:
def chunks(g, n):
"""divide a generator 'g' into small chunks
Yields:
a chunk that has 'n' or less items
"""
n = max(1, n)
buff = []
for item in g:
buff.append(item)
if len(buff) == n:
yield buff
buff = []
if buff:
yield buff
С Выражения присваивания в Python 3.8 становится довольно приятно:
import itertools
def batch(iterable, size):
it = iter(iterable)
while item := list(itertools.islice(it, size)):
yield item
Это работает с произвольной итерацией, а не только со списком.
>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[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]]
Теперь это достойный новый ответ на этот вопрос. На самом деле мне это очень нравится. Я скептически отношусь к выражениям назначения, но когда они работают, они работают.
def main():
print(chunkify([1,2,3,4,5,6],2))
def chunkify(list, n):
chunks = []
for i in range(0, len(list), n):
chunks.append(list[i:i+n])
return chunks
main()
Я думаю, что это просто и может дать вам кусок массива.
Общий фрагмент для любой итерации, который дает пользователю выбор, как обрабатывать частичный фрагмент в конце.
Протестировано на Python 3.
chunker.py
from enum import Enum
class PartialChunkOptions(Enum):
INCLUDE = 0
EXCLUDE = 1
PAD = 2
ERROR = 3
class PartialChunkException(Exception):
pass
def chunker(iterable, n, on_partial=PartialChunkOptions.INCLUDE, pad=None):
"""
A chunker yielding n-element lists from an iterable, with various options
about what to do about a partial chunk at the end.
on_partial=PartialChunkOptions.INCLUDE (the default):
include the partial chunk as a short (<n) element list
on_partial=PartialChunkOptions.EXCLUDE
do not include the partial chunk
on_partial=PartialChunkOptions.PAD
pad to an n-element list
(also pass pad=<pad_value>, default None)
on_partial=PartialChunkOptions.ERROR
raise a RuntimeError if a partial chunk is encountered
"""
on_partial = PartialChunkOptions(on_partial)
iterator = iter(iterable)
while True:
vals = []
for i in range(n):
try:
vals.append(next(iterator))
except StopIteration:
if vals:
if on_partial == PartialChunkOptions.INCLUDE:
yield vals
elif on_partial == PartialChunkOptions.EXCLUDE:
pass
elif on_partial == PartialChunkOptions.PAD:
yield vals + [pad] * (n - len(vals))
elif on_partial == PartialChunkOptions.ERROR:
raise PartialChunkException
return
return
yield vals
test.py
import chunker
chunk_size = 3
for it in (range(100, 107),
range(100, 109)):
print("\nITERABLE TO CHUNK: {}".format(it))
print("CHUNK SIZE: {}".format(chunk_size))
for option in chunker.PartialChunkOptions.__members__.values():
print("\noption {} used".format(option))
try:
for chunk in chunker.chunker(it, chunk_size, on_partial=option):
print(chunk)
except chunker.PartialChunkException:
print("PartialChunkException was raised")
print("")
выход test.py
ITERABLE TO CHUNK: range(100, 107)
CHUNK SIZE: 3
option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106]
option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]
option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, None, None]
option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
PartialChunkException was raised
ITERABLE TO CHUNK: range(100, 109)
CHUNK SIZE: 3
option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]
option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]
option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]
option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]
Абстракция была бы
l = [1,2,3,4,5,6,7,8,9]
n = 3
outList = []
for i in range(n, len(l) + n, n):
outList.append(l[i-n:i])
print(outList)
Это напечатает:
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Вот небольшой код, написанный на Python3, который делает то же самое, что и np.array_split.
list(map(list, map(functools.partial(filter, None), itertools.zip_longest(*iter(lambda: tuple(itertools.islice(a, n)), ())))))
Это довольно длинный однострочный список, но он равномерно распределяет элементы между результирующими подсписками.
Я создал эти две причудливые однострочники, которые эффективны и ленивы, и ввод, и вывод являются повторяющимися, и они не зависят от какого-либо модуля:
Первый однострочный является полностью ленивым, что означает, что он возвращает итератор, производящий итераторы (т.е. каждый произведенный блок представляет собой итератор, повторяющий элементы блока), эта версия хороша для случая, если блоки очень большие или элементы создаются медленно один за другим и должны стать доступны сразу после производства:
chunk_iters = lambda it, n: ((e for i, g in enumerate(((f,), cit)) for j, e in zip(range((1, n - 1)[i]), g)) for cit in (iter(it),) for f in cit)
Второй однострочный возвращает итератор, который создает списки. Каждый список создается, как только элементы целого фрагмента становятся доступными через итератор ввода или если достигается самый последний элемент последнего фрагмента. Эту версию следует использовать, если элементы ввода создаются быстро или все доступны сразу. В противном случае следует использовать первую более ленивую однострочную версию.
chunk_lists = lambda it, n: (l for l in ([],) for i, g in enumerate((it, ((),))) for e in g for l in (l[:len(l) % n] + [e][:1 - i],) if (len(l) % n == 0) != i)
Также я предоставляю многострочную версию первого однострочника chunk_iters, который возвращает итератор, создающий другие итераторы (проходящие через элементы каждого фрагмента):
def chunk_iters(it, n):
cit = iter(it)
def one_chunk(f):
yield f
for i, e in zip(range(n - 1), cit):
yield e
for f in cit:
yield one_chunk(f)
Хотя ответов много, у меня есть очень простой способ:
x = list(range(10, 75))
indices = x[0::10]
print("indices: ", indices)
xx = [x[i-10:i] for i in indices ]
print("x= ", x)
print ("xx= ",xx)
результат будет:
indices: [10, 20, 30, 40, 50, 60, 70] x= [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]
xx = [[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]]
Прежде чем опубликовать новый ответ, подумайте, что на этот вопрос уже есть более 60 ответов. Пожалуйста, убедитесь, что ваш ответ содержит информацию, которой нет среди существующих ответов.