У меня есть список произвольной длины, и мне нужно разделить его на куски одинакового размера и обработать его. Есть несколько очевидных способов сделать это, например, сохранить счетчик и два списка, и, когда второй список заполнится, добавить его в первый список и очистить второй список для следующего раунда данных, но это потенциально чрезвычайно дорого.
Мне было интересно, есть ли у кого-нибудь хорошее решение для списков любой длины, например, используя генераторы.
Я искал что-то полезное в itertools
, но не смог найти ничего явно полезного. Возможно, я пропустил это.
Смежный вопрос: Какой самый «питонный» способ перебрать список по частям?
Вот генератор, который выдает нужные вам куски:
def chunks(l, n):
"""Yield successive n-sized chunks from l."""
for i in range(0, len(l), n):
yield l[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(l, n):
"""Yield successive n-sized chunks from l."""
for i in xrange(0, len(l), n):
yield l[i:i + n]
Также вы можете просто использовать списочное понимание вместо написания функции, хотя это хорошая идея для инкапсуляции таких операций в именованные функции, чтобы ваш код было легче понять. Python 3:
[l[i:i + n] for i in range(0, len(l), n)]
Версия Python 2:
[l[i:i + n] for i in xrange(0, len(l), n)]
Вы можете использовать функцию array_split для numpy, например, np.array_split(np.array(data), 20)
, чтобы разбить на 20 почти одинаковых по размеру кусков.
Чтобы убедиться, что куски в точности равны по размеру, используйте np.split
.
>>> f = lambda x, n, acc=[]: f(x[n:], n, acc+[(x[:n])]) if x else acc
>>> f("Hallo Welt", 3)
['Hal', 'lo ', 'Wel', 't']
>>>
Если вы в скобках - я взял книгу об Эрланге :)
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
Я специально для этой цели написал небольшую библиотеку, доступную здесь . Функция библиотеки 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), ...]
Позволяя r будет размером чанка, а L - начальным списком, вы можете это сделать.
chunkL = [ [i for i in L[r*k:r*(k+1)] ] for k in range(len(L)/r)]
Вот идея использования 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']]
(Так что да, это страдает от «проблемы с прогибом» , которая может или не может быть проблемой в данной ситуации.)
Как @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 (л, пол (л / н))
Ответ выше (от 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) ]
Это работает в 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))
Мне не нравится идея разделения элементов по размеру куска, например, Скрипт может разделить от 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)]
В соответствии с этим ответом , голос с наибольшим количеством голосов оставляет «прогиб» в конце. Вот мое решение по-настоящему получить куски одинакового размера, насколько это возможно, без сучков. Он в основном пытается выбрать именно дробную точку, где он должен разбить список, но просто округляет его до ближайшего целого числа:
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]]
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]]
Пакет 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
Не думаю, что видел эту опцию, поэтому просто добавлю еще одну :)):
def chunks(iterable, chunk_size):
i = 0;
while i < len(iterable):
yield iterable[i:i+chunk_size]
i += chunk_size
Этот вопрос напоминает мне метод Perl 6 .comb(n)
. Он разбивает строки на куски размером 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']
Непосредственно из (старой) документации 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)
Текущая версия, предложенная JFSebastian:
#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
элементов.
Так как я должен был сделать что-то вроде этого, вот мое решение, учитывая генератор и размер партии:
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
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]]'
Использовать списки:
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)]]
Я удивлен, что никто не думал об использовании формы 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)]
Простой, но элегантный
l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]
или, если вы предпочитаете:
chunks = lambda l, n: [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
Никто не использует функцию tee () под itertools?
http://docs.python.org/2/library/itertools.html#itertools.tee
>>> import itertools
>>> itertools.tee([1,2,3,4,5,6],3)
(<itertools.tee object at 0x02932DF0>, <itertools.tee object at 0x02932EB8>, <itertools.tee object at 0x02932EE0>)
Это разделит список на 3 итератора, цикл итератор получит подсписок равной длины
Я пришел к следующему решению без создания объекта временного списка, который должен работать с любым повторяемым объектом. Обратите внимание, что эта версия для 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 (итеративный)% size == 0, тогда у нас есть дополнительный пустой объект итератор. Но я не думаю, что это большая проблема.
def chunked(iterable, size):
chunk = ()
for item in iterable:
chunk += (item,)
if len(chunk) % size == 0:
yield chunk
chunk = ()
if chunk:
yield chunk
Я понимаю, что этот вопрос старый (наткнулся на него в 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 ...
Если вы не заботитесь о порядке:
> 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']]
Это решение не генерирует наборы одинакового размера, но распределяет значения таким образом, чтобы пакеты были как можно большими при сохранении количества сгенерированных пакетов. .
Вот генератор, который работает с произвольными итерациями:
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]]
def chunk(input, size):
return map(None, *([iter(input)] * size))
Библиотека toolz имеет функцию partition
для этого:
from toolz.itertoolz.core import partition
list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]