Как вы разбиваете список на куски с равномерным размером?
У меня есть список произвольной длины, и мне нужно разбить его на равные куски размера и работать с ним. Есть несколько очевидных способов сделать это, например, сохранить счетчик и два списка, а когда второй список заполнится, добавьте его в первый список и очистите второй список для следующего раунда данных, но это потенциально чрезвычайно дорого.
Мне было интересно, есть ли у кого-нибудь хорошее решение для списков любой длины, например. используя генераторы.
Я искал что-то полезное в itertools
, но я не мог найти ничего явно полезного. Возможно, он пропустил это.
Связанный вопрос: Каков самый "питонический" способ перебора списка в кусках?
Ответы
Ответ 1
Вот генератор, который выдает нужные вам куски:
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)]
Ответ 2
Если вы хотите что-то супер простое:
def chunks(l, n):
n = max(1, n)
return (l[i:i+n] for i in xrange(0, len(l), n))
Используйте range()
вместо xrange()
в случае Python 3.x
Ответ 3
Непосредственно из (старой) документации 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)
Я думаю, что работающая машина Guido работает - будет работать - будет работать - снова работала.
Эти решения работают, потому что [iter(iterable)]*n
(или эквивалент в более ранней версии) создает один итератор, повторяя n
раз в списке. izip_longest
затем эффективно выполняет циклический "каждый" итератор; потому что это тот же самый итератор, он продвигается каждым таким вызовом, в результате чего каждый такой zip-roundrobin генерирует один кортеж элементов n
.
Ответ 4
Я знаю, что это довольно старо, но никто еще не упомянул 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])]
Ответ 5
Я удивлен, что никто не думал об использовании 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
-based, вышеприведенные всегда дополнения. Насколько я знаю, нет никакого one- или двухстрочного рецепта 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)]
Ответ 6
Вот генератор, который работает с произвольными итерами:
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]]
Ответ 7
def chunk(input, size):
return map(None, *([iter(input)] * size))
Ответ 8
Простой, но элегантный
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)
Ответ 9
Я видел самый удивительный ответ Python-ish в дубликате этого вопроса:
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
.
Ответ 10
Критика других ответов здесь:
Ни один из этих ответов не является кусочками равномерного размера, все они оставляют кусок в конце, поэтому они не полностью сбалансированы. Если вы используете эти функции, чтобы распределить работу, вы построили в перспективе один, вероятно, задолго до окончания остальных, поэтому было бы сидеть и ничего не делать, а остальные продолжали работать трудно.
Например, текущий верхний ответ заканчивается на:
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]
Я просто ненавижу этот блин в конце!
Другие, такие как list(grouper(3, xrange(7)))
и chunk(xrange(7), 3)
, возвращаются: [(0, 1, 2), (3, 4, 5), (6, None, None)]
. None
являются просто дополнением, и, по моему мнению, довольно неэлегантными. Они НЕ равномерно разбивают итерации.
Почему мы не можем делить их лучше?
Мое решение (ы)
Здесь сбалансированное решение, адаптированное из функции, которую я использовал в производстве (примечание в Python 3 для замены xrange
на range
):
def baskets_from(items, maxbaskets=25):
baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
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 xrange(baskets):
yield [items[y_i] for y_i in xrange(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 xrange(baskets):
length = ceiling if x_i < stepdown else floor
yield [items.next() for _ in xrange(length)]
Выход
Чтобы проверить их:
print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(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]]
Обратите внимание, что непрерывный генератор предоставляет куски в тех же шаблонах длины, что и два других, но все элементы упорядочены, и они равномерно разделены, так как можно разделить список дискретных элементов.
Ответ 11
Если вы знаете размер списка:
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
В последнем случае его можно перефразировать более красивым способом, если вы можете быть уверены, что последовательность всегда содержит целое количество фрагментов заданного размера (т.е. не существует неполного последнего фрагмента).
Ответ 12
more-itertools имеет итератор кусков.
Он также имеет намного больше вещей, включая все рецепты документации itertools.
Ответ 13
Если у вас был размер блока 3, например, вы могли бы сделать:
zip(*[iterable[i::3] for i in range(3)])
Источник:
http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/
Я бы использовал это, когда размер моего блока - это фиксированное число, которое я могу напечатать, например. '3' и никогда не изменится.
Ответ 14
Выражение генератора:
def chunks(seq, n):
return (seq[i:i+n] for i in xrange(0, len(seq), n))
например.
print list(chunks(range(1, 1000), 10))
Ответ 15
В библиотеке toolz есть функция partition
для этого:
from toolz.itertoolz.core import partition
list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
Ответ 16
Мне нравится версия документа Python, предложенная tzot и J.F.Sebastian,
но имеет два недостатка:
- он не очень явный
- Обычно я не хочу значения заполнения в последнем фрагменте
Я использую этот код в своем коде:
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))
Ответ 17
В этот момент мне кажется, нужен рекурсивный генератор, на всякий случай...
В 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
Ответ 18
Мне было интересно узнать о различных подходах, и вот он:
Протестировано на 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
Ответ 19
код:
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]]
Ответ 20
Вы также можете использовать 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
Отказ от ответственности: я являюсь создателем библиотеки utilspie.
Ответ 21
[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
Ответ 22
heh, одна строка версии
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]]
Ответ 23
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
Ответ 24
Без вызова 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))
Ответ 25
Еще одна явная версия.
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
Ответ 26
Еще одно решение
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]
>>>
Ответ 27
Рассмотрите возможность использования материалов matplotlib.cbook
например:
import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
print s
Ответ 28
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]]'
Ответ 29
См. эта ссылка
>>> 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
Ответ 30
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 )]