Как разделить список на части одинакового размера?

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

Мне было интересно, есть ли у кого-нибудь хорошее решение для списков любой длины, например с помощью генераторов.

Я искал что-нибудь полезное в itertools, но ничего явно полезного не нашел. Хотя, возможно, пропустил.

Связанный вопрос: Каков наиболее «питонический» способ перебора списка по частям?

Прежде чем опубликовать новый ответ, подумайте, что на этот вопрос уже есть более 60 ответов. Пожалуйста, убедитесь, что ваш ответ содержит информацию, которой нет среди существующих ответов.

janniks 03.02.2020 15:17
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2 507
1
1 132 040
64
Перейти к ответу Данный вопрос помечен как решенный

Ответы 64

Ответ принят как подходящий

Вот генератор, который выдает нужные вам фрагменты:

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 23.11.2008 15:51

Это интересное расширение вопроса, но исходный вопрос явно задан о работе со списком.

Ned Batchelder 23.11.2008 16:53

@jespern Я думаю, что с бесконечным или неопределенным списком вы переходите к связанный вопрос, который связан с Дж. Ф. Себастьян: Каков наиболее «питонический» способ перебора списка по частям?

n611x007 24.04.2014 13:19

эти функции должны быть в чертовой стандартной библиотеке

dgan 04.02.2018 17:19

Я бы добавил пример выражения генератора в дополнение к спискам понимания. (Просто используйте (), а не [])

SomethingSomething 03.04.2018 14:33

-1. Вопрос конкретно касается «кусков одинакового размера». Это неверный ответ на вопрос, так как последний кусок будет произвольно маленьким.

Calimo 14.06.2018 14:51

@Calimo: что вы предлагаете? Я передаю вам список из 47 элементов. Как бы вы хотели разделить его на «куски одинакового размера»? OP принял ответ, поэтому они явно в порядке с последним фрагментом другого размера. Возможно, английская фраза неточна?

Ned Batchelder 14.06.2018 18:29

@NedBatchelder Я согласен, что вопрос довольно нечеткий, но вы можете разделить список из 47 элементов на 5 частей по 9, 9, 9, 10 и 10 элементов вместо 7, 10, 10, 10 и 10. Это не совсем даже, но это то, что я имел в виду, когда гуглил ключевые слова "блоки одинакового размера". Это означает, что вам нужно n определять количество фрагментов, а не их размер. Другой ответ ниже предлагает способ сделать это на самом деле. Ваш ответ в основном такой же, как и в связанном «связанном вопросе».

Calimo 14.06.2018 18:46

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

Alvaro 04.07.2019 15:46

В моей системе я получаю не тот же результат, а список объектов диапазона. Чтобы получить тот же результат, что и вы, я использовал это: [list(s) for s in chunks(range(10, 75), 10)]

marsipan 26.10.2019 18:41

@Alvaro вы обнаружите, что если ваша последняя партия содержит только один элемент, у вас есть потраченное впустую ядро, которому нечего делать, в то время как у других может остаться довольно много, что легко можно было бы сделать в другом месте.

user2589273 12.05.2020 22:52

Я предпочитаю заполнять последний фрагмент, чтобы все куски были одинакового размера. Итак, def ichunks(it, n, pad=None): и т. д. И да, +1 для итератора в (возможно, бесконечном) потоке. Гораздо более общее, чем предположение о коллекции конечного размера.

Pierre D 20.05.2020 05:11

Я удивлен, что в стандартной библиотеке уже нет функции для этого.

Mr. Lance E Sloan 17.08.2020 06:17

почему это принятый ответ, он не распространяет их наилучшим образом.

Bob Bobster 01.03.2021 00:32

Если вы знаете размер списка:

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 работает для всего и является общим решением и не имеет никаких оговорок, о которых я знаю.

Jason Dunkelberger 08.08.2015 02:31

Вот генератор, который работает с произвольными итерациями:

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 23.11.2008 16:45

@ S.Lott: нет, если n00bz исходит из схемы: P, это не настоящая проблема. есть даже ключевое слово для Google! Какие еще функции показывают, чего мы избегаем ради n00bz? Я думаю, yield не является обязательным / c-подобным достаточно, чтобы быть дружелюбным к n00b.

Janus Troelsen 12.05.2012 01:10

Функциональный объект, полученный из def chunk вместо chunk=lambda, имеет атрибут .__ name__ 'chunk' вместо '<lambda>'. Конкретное имя более полезно при трассировке.

Terry Jan Reedy 27.06.2012 08:20

@Alfe: Я не уверен, можно ли назвать это основным семантическим различием, но есть ли полезное имя в трассировке вместо <lamba> или нет, это, по крайней мере, заметное различие.

martineau 11.01.2015 23:33

После тестирования нескольких из них на производительность, ЭТО отлично!

Sunny Patel 05.10.2018 19:36

Непосредственно из (старой) документации 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»? Заранее спасибо.

tzot 19.04.2011 17:09

Если код пользователя имеет кортеж с None - это неправильное поведение, им необходимо явно выдать ошибку, если len('0123456789')%3 != 0. Это неплохая вещь, но вещь, которая может быть задокументирована. Ой, подождите, мои извинения ... это неявно задокументировано аргументом padvalue = None. (Также под «3» я имел в виду «n») Хороший код.

ninjagecko 19.04.2011 19:51

поддержал это, потому что он работает с генераторами (без len) и использует в целом более быстрый модуль itertools.

Michael Dillon 31.01.2012 03:47

Классический пример причудливого функционального подхода itertools, в результате которого получается нечитаемый осадок по сравнению с простой и наивной реализацией на чистом питоне.

wim 12.04.2013 09:40

@wim Учитывая, что этот ответ начался как фрагмент из документации Python, я предлагаю вам открыть проблему на bugs.python.org.

tzot 12.04.2013 15:36

Для справки, решение является частью документации, в разделе рецептов itertools: docs.python.org/3/library/itertools.html#itertools-recipes

Juan Carlos Ramirez 14.03.2019 19:11

Может ли кто-нибудь объяснить или указать мне правильную концепцию того, почему существует * до [iter(iterable)]*n?

pedrosaurio 20.08.2019 09:37

@pedrosaurio, если l==[1, 2, 3], то f(*l) эквивалентен f(1, 2, 3). См. этот вопрос и официальная документация.

tzot 21.08.2019 11:02

Моя проблема с этим решением заключается в том, что результатом является не только разделение содержимого итератора ввода, но и добавление дополнительных элементов к последнему фрагменту, чтобы сделать его «правильным» размером.

Graham Lea 23.07.2020 05:05

@GrahamLea Я не уверен, что вы имеете в виду под «разделением»; ты имеешь в виду «потреблен»? Что касается заполнения последней группы, я вернусь с версией без заполнения. Обновлено: он уже предоставлен отправителем.

tzot 23.07.2020 19:04
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) ]. Мне нравится этот метод понимания списков!

J-P 20.08.2011 17:54

после возврата должно быть [, а не (

alwbtc 01.06.2017 09:45

«Супер простой» означает отсутствие необходимости отлаживать бесконечные циклы - спасибо max().

Bob Stein 15.05.2018 20:49

в этом решении нет ничего простого

mit 19.10.2018 15:36

Обратите внимание, что результатом со списком ввода ['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']].

np8 14.08.2019 11:59

@BobStein Я новичок, как работает max ()?

GoNK 27.04.2020 05:55

@Nhoj_Gonk К сожалению, это не бесконечный цикл, но фрагменты (L, 0) вызовут ValueError без max (). Вместо этого max () превращает все, что меньше 1, в 1.

Bob Stein 27.04.2020 12:58

Без вызова 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() в больших списках; это операция с постоянным временем.

Thomas Wouters 30.05.2011 14:03
def chunk(input, size):
    return map(None, *([iter(input)] * size))
map(None, iter) равен izip_longest(iter).
Thomas Ahle 29.01.2012 19:18

@TomaszWysocki Не могли бы вы объяснить * перед вашим кортежем итератора? Возможно, в тексте вашего ответа, но я заметил, что * раньше использовал этот способ в Python. Спасибо!

theJollySin 07.10.2013 22:58

@theJollySin В этом контексте он называется оператором splat. Его использование объясняется здесь - stackoverflow.com/questions/5917522/unzipping-and-the-operat‌ или.

rlms 16.11.2013 01:14

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

user1969453 25.04.2014 05:49

Простой, но элегантный

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 15.08.2013 03:02

@Alfe Неисправные шрифты. Люди не должны использовать такие шрифты. Не для программирования, не для что-нибудь.

Jerry B 05.10.2013 12:14

Лямбды предназначены для использования в качестве безымянных функций. Нет смысла их так использовать. Вдобавок это усложняет отладку, поскольку в случае ошибки трассировка будет сообщать «в <лямбда>», а не «по частям». Желаю вам удачи в поиске проблемы, если у вас их целая куча :)

Chris Koston 26.11.2013 23:45

внутри xrange в print [l[x:x+10] for x in xrange(1, len(l), 10)] должно быть 0, а не 1

scottydelta 28.12.2013 23:11
ПРИМЕЧАНИЕ: Для пользователей Python 3 используйте range.
Christian Dean 31.08.2017 15:32

Если у вас, например, размер блока 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. Последняя (короткая) группа чисел не будет возвращена.

sherbang 03.07.2012 23:28

Рассмотрите возможность использования частей matplotlib.cbook

Например:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

Похоже, вы случайно создали две учетные записи. Вы можете связаться с командой объединить их, что позволит вам восстановить права прямого редактирования ваших вкладов.

Georgy 15.05.2019 18:15
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, и он не сломается !!

Peter Gerdes 19.12.2017 13:25

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

Alfe 15.08.2013 03:17

OP написал: «У меня есть список произвольной длины, и мне нужно разбить его на части равного размера и работать с ним». Может быть, я что-то упускаю, но как получить «куски равного размера» из списка произвольной длины, не отбрасывая куски, которые короче «равного размера»

Aivar Paalberg 27.09.2020 12:57

использование списков 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 15.08.2013 03:18

@Alfe for chunk in [some_list[i:i + 10] for i in range(0, len(some_list), 10)]: print chunk

flexd 03.08.2014 01:34

Таким образом, это очень похоже на принятый верхний ответ ;-)

Alfe 03.08.2014 03:17

Я знаю, что это старовато, но 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])]

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

FizxMike 09.09.2015 06:03

вы можете посчитать сами. если у вас есть 10 элементов, вы можете сгруппировать их в блоки по 2, 5 элементов или пять блоков по 2 элемента

Moj 09.09.2015 10:27

+1 Это мое любимое решение, поскольку оно разбивает массив на массивы размером равномерно, в то время как другие решения этого не делают (во всех других рассмотренных мною решениях последний массив может быть сколь угодно маленьким).

MiniQuark 28.06.2016 20:26

@MiniQuark, но что это делать, если количество блоков не является фактором исходного размера массива?

Baldrickk 18.05.2018 14:12

@Baldrickk Если вы разделите N элементов на K фрагментов, то первые N% K фрагментов будут содержать N // K + 1 элементов, а остальные будут иметь N // K элементов. Например, если вы разделите массив, содержащий 108 элементов, на 5 фрагментов, то первые 108% 5 = 3 фрагментов будут содержать 108 // 5 + 1 = 22 элемента, а остальные фрагменты будут иметь 108 // 5 = 21 элементы.

MiniQuark 18.05.2018 18:31

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

schrödingercöder 30.01.2020 21:52

Как разбить его на массив равного размера? В качестве примера, скажем, я хочу разделить массив np.arange (14) на равный размер массива размером 5. Окончательный результат, который я ищу, - это три массива, два размера - 5 и один - 4.

sushmit 20.10.2020 00:40
  • Работает с любыми итерациями
  • Внутренние данные - это объект-генератор (не список)
  • Один лайнер
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); Последняя оценка пуста. Скрытое требование о доступе ко всем подспискам по порядку кажется мне здесь действительно плохим, потому что цель таких утилит часто состоит в том, чтобы перетасовать данные различными способами.

Peter Gerdes 19.12.2017 13:30

Мне очень нравится версия документа 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 06.09.2019 16:40

@wjandrea: StopIteration возникает, когда tuple пуст и iterable.next() запускается. Однако не работает должным образом в современном Python, где выход из генератора должен выполняться с помощью return, а не с помощью StopIteration. try/except StopIteration: return вокруг всего цикла (и изменение iterable.next() на next(iterable) для совместимости между версиями) исправляет это, по крайней мере, с минимальными накладными расходами.

ShadowRanger 22.01.2020 09:10

В библиотеке Toolz есть функция partition для этого:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

Это кажется самым простым из всех предложений. Мне просто интересно, действительно ли может быть правдой, что для получения такой функции разделения нужно использовать стороннюю библиотеку. Я ожидал, что что-то эквивалентное с этой функцией разделения будет существовать как встроенный язык.

kasperd 29.03.2015 17:45

вы можете сделать раздел с помощью itertools. но мне нравится библиотека toolz. это библиотека, вдохновленная Clojure, для работы с коллекциями в функциональном стиле. вы не получаете неизменяемости, но получаете небольшой словарный запас для работы с простыми коллекциями. В качестве плюса, cytoolz написан на cython и имеет хороший прирост производительности. github.com/pytoolz/cytoolzmatthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz

zach 30.03.2015 18:28

Ссылка из комментария Зака ​​работает, если вы опустите косую черту в конце: matthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz

mit 19.10.2018 15:46

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

>>> 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 26.02.2014 19:00

@senderle, и первый, list(grouper(3, xrange(7))), и второй, chunk(xrange(7), 3), оба возвращают: [(0, 1, 2), (3, 4, 5), (6, None, None)]. None - это просто набивка и, на мой взгляд, довольно неэлегантная. Они НЕ разделяют итерации равномерно. Спасибо за голос!

Aaron Hall 26.02.2014 20:07

Вы поднимаете вопрос (не делая этого явно, поэтому я делаю это сейчас здесь), будут ли блоки одинакового размера (кроме последнего, если это невозможно) или сбалансированный (как можно более хороший) результат чаще всего будет необходим. Вы предполагаете, что предпочтение должно быть отдано сбалансированному решению; это может быть правдой, если то, что вы программируете, близко к реальному миру (например, алгоритм раздачи карт для симулированной карточной игры). В других случаях (например, при заполнении строк словами) лучше оставить строки как можно более полными. Так что я не могу предпочитать одно другому; они просто для разных случаев использования.

Alfe 03.08.2014 03:14

@ ChristopherBarrington-Leigh Хороший момент, для DataFrames вам, вероятно, следует использовать срезы, поскольку я считаю, что объекты DataFrame обычно не копируются при срезании, например import pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]

Aaron Hall 03.09.2014 21:10

@AaronHall Ой. Я удалил свой комментарий, потому что я усомнился в своей критике, но вы быстро угадали. Спасибо! Фактически, мое утверждение, что это не работает для фреймов данных, верно. Если items является фреймом данных, просто используйте yield items [диапазон (x_i, item_count, корзинки)] в качестве последней строки. Я предложил отдельный (еще один) ответ, в котором вы указываете желаемый (минимальный) размер группы.

CPBL 03.09.2014 21:47

@ ChristopherBarrington-Leigh Спасибо, очень мило с вашей стороны. Однако я бы не стал использовать для этого код из своего ответа. Если вы перебираете DataFrame, вы можете использовать iterrows. Я бы не стал использовать диапазон для нарезки, он создает объект в памяти. Я бы предпочел объект среза, созданный с помощью синтаксиса среза, например. i::3 или эквивалентный slice(i, None, 3).

Aaron Hall 03.09.2014 22:06

Мне это нравится, но я бы хотел, чтобы он работал с лямбдой произвольной длины, а не только с len ()

Steve Yeago 24.09.2016 01:04

Не поддавайтесь соблазну использовать [[]]*maxbaskets, это не то же самое, что [[] for _ in range(maxbaskets)]. В первом случае действительно существует только один экземпляр корзины, на которую ссылаются несколько раз.

qbolec 14.11.2016 13:18

Во многих случаях «коротышка» на самом деле является более желательным вариантом. Поскольку в этом вопросе ничего не указано, должен ли отображаться в конце фрагмент меньшего размера или нет, возможно, ваш ответ был бы более уместным на соответствующий вопрос 2010 года Разделение списка на N частей примерно равной длины.

wim 19.08.2020 20:09

@wim, спасибо за критику, я ценю ваш вклад - я еще подумаю об этом и, возможно, обновлю свой ответ. «Куски одинакового размера» для меня означают, что все они имеют одинаковую длину или, за исключением этого, с минимальным отклонением. Например. В 5 корзинах по 21 элементу может быть >>> статистика импорта >>> statistics.variance ([5,5,5,5,1]) 3,2 >>> statistics.variance ([5,4,4,4,4] ) 0,19999999999999998

Aaron Hall 19.08.2020 20:49

Дисперсия - это всего лишь один путь - другой разумной интерпретацией может быть «максимальное количество ведер с одинаковой длиной», когда лучшая стратегия - иметь коротышку. И во многих реальных случаях вы не можете знать количество элементов заранее (например, отправка пакетов по сети, когда вы не знаете размер данных, но у вас есть верхняя граница размера пакета, например потоковое сжатие). Думаю, я повторяю то, что уже сказал Альфе - ни один из подходов объективно не лучше, зависит от проблемы :)

wim 19.08.2020 21:27

Я удивлен, что никто не подумал об использовании 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() и срезов массива. Потрясающий!

ThomasH 15.09.2016 22:58

Вот почему я зачитываю ответы, а не просматриваю только верхнюю пару. В моем случае обязательным требованием было необязательное заполнение, и я тоже узнал о форме iter с двумя аргументами.

Kerr 16.08.2017 17:30

Я поддержал это, но все же - давайте не будем переоценивать это! Во-первых, лямбда может быть плохой (медленное закрытие по итератору it. Во-вторых, что наиболее важно, вы закончите преждевременно, если кусок padval действительно существует в вашей итерации и должен быть обработан.

Tomasz Gandor 16.11.2018 14:34

@TomaszGandor, я принимаю твое первое очко! Хотя я понимаю, что лямбда не медленнее, чем обычная функция, конечно, вы правы в том, что вызов функции и поиск закрытия замедлят это. Я не знаю, каков будет относительный удар по производительности, например, по сравнению с подходом izip_longest - я подозреваю, что это может быть сложный компромисс. Но ... разве проблема padval не разделяется всеми здесь ответами, которые предлагают параметр padval?

senderle 16.11.2018 15:38

Не знаю, все ли (просто прочтите популярные ответы и обратите внимание на этот). Позвольте мне проиллюстрировать это: chunk_pad([1, 2, None, None, 5], 2) ДОЛЖЕН генерировать: (1, 2), (None, None), (5, None), вместо этого он просто генерирует (1, 2). То же самое для chunk([1, 2, (), (), 5], 2), но второй сгенерированный элемент должен быть ((), ()). Проблема не может быть устранена добавлением большего количества if, это характерно для использования iter с дозорным.

Tomasz Gandor 16.11.2018 21:48

@TomaszGandor Понятно, я не понял, о чем ты говоришь. Вы правы, эта проблема в некотором роде уникальна для этого ответа. Я считаю, что есть способ обойти это - я немного подумаю.

senderle 17.11.2018 01:09

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

Tomasz Gandor 17.11.2018 01:14

@TomaszGandor, достаточно честно! Но создать версию, которая это исправляет, было несложно. (Также обратите внимание, что самая первая версия, которая использует () в качестве дозорного, делает работает правильно. Это потому, что tuple(islice(it, size)) дает (), когда it пуст.)

senderle 17.11.2018 04:19

Специально для этого я написал небольшую библиотеку, доступную здесь. Функция библиотеки 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).

idij 27.11.2014 16: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 г.) Этот ответ не зависит от языка и его легче всего читать.

D Adams 14.09.2016 03:36

Я видел самый потрясающий ответ 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)

Tom Smith 18.05.2015 17:21
zip(i, i, i, ... i) с аргументами "chunk_size" для zip () может быть записан как zip(*[i]*chunk_size), конечно, спорный вопрос, хорошая это идея или нет.
Wilson F 28.06.2015 07:52

Обратной стороной этого является то, что если вы не разделяете равномерно, вы отбрасываете элементы, так как zip останавливается на самом коротком итеративном значении - & izip_longest добавит элементы по умолчанию.

Aaron Hall 08.07.2016 06:37

Следует использовать zip_longest, как это сделано в: stackoverflow.com/a/434411/1959808

Ioannis Filippidis 21.06.2017 16:28

В ответе с range(1, 15) элементы уже отсутствуют, потому что в range(1, 15) 14 элементов, а не 15.

Ioannis Filippidis 21.06.2017 16:34

В приведенном выше ответе (от 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) ]

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

Arthur Tacca 03.12.2018 18:34

код:

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

Не могли бы вы объяснить свой ответ поподробнее?

Zulu 16.07.2015 03:06

Работа в обратном направлении: (len (a) + CHUNK -1) / CHUNK Дает вам количество фрагментов, которые у вас останутся. Затем для каждого фрагмента с индексом i мы генерируем подмассив исходного массива следующим образом: a [i * CHUNK: (i + 1) * CHUNK], где i * CHUNK - это индекс первого элемента для помещается в подмассив, и (i + 1) * CHUNK - это 1 после последнего элемента, помещаемого в подмассив. Это решение использует понимание списков, поэтому оно может быть быстрее для больших массивов.

AdvilUser 29.07.2015 03:29

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

Peter Gerdes 19.12.2017 13:03

Попробуйте выполнять list (it) только на каждой второй итерации цикла, т.е. добавьте счетчик и проверьте, 0 mod 2. Ожидаемое поведение - печатать только каждую вторую строку вашего вывода. Фактическое поведение заключается в печати каждой строки.

Peter Gerdes 19.12.2017 13:10

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

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

лямбда-функция медленная. понимание списка будет быстрее

Sanjay Poongunran 24.07.2018 22:47

@SanjayPoongunran благодарит вас за отзыв, но это Python, мы здесь не для производительности (мы бы писали на C), а для удобства чтения.

Julien Palard 25.07.2018 01:29

@JulienPalard О да, этот ответ - это удобочитаемость.

Ibolit 16.10.2018 10:44
[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

это самое лучшее и простое.

F.Tamy 26.10.2019 13:12

коротко и просто. простота важнее сложности.

darkman 28.01.2020 11:41

Очень полезный! Спасибо!

Mateus da Silva Teixeira 29.10.2020 18:17

Согласно этот ответ, ответ, получивший наибольшее количество голосов, оставляет в конце «коротышку». Вот мое решение, чтобы действительно получить куски одинакового размера, без каких-либо коротких замыканий. Он в основном пытается выбрать именно то дробное место, где он должен разделить список, но просто округляет его до ближайшего целого числа:

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 05.09.2016 13:45

@DragonTux: А, я написал функцию для Python 3 - она ​​дает [[0, 1], [2], [3, 4]]. Я добавил будущий импорт, чтобы он работал и в Python 2

Claudiu 05.09.2016 20:24

Большое спасибо. Я все время забываю о тонких различиях между Python 2 и 3.

DragonTux 09.09.2016 18:18

Поскольку все здесь говорят об итераторах. 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.

И этот действительно работает независимо от порядка просмотра подитераторов !!

Peter Gerdes 19.12.2017 13:32

Вы можете использовать функцию 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) // Ухххх

Peter Gerdes 19.12.2017 13:19

@PeterGerdes, спасибо, что заметили это упущение; Я забыл, потому что всегда использовал генераторы groupby по порядку. В документации упоминается это ограничение: «Поскольку источник является общим, при расширении объекта groupby () предыдущая группа больше не видна».

itub 19.12.2017 19:12

@PeterGerdes Я думаю, что это можно решить, используя вместо этого enumerate, например: [[x for _, x in it] for _, it in itertools.groupby(enumerate(l), lambda x: x[0]//n)] (list (it) - это список пар (index, element) из-за enumerate)

Yuri Feldman 13.05.2019 21:18

Еще одно решение

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)), а затем повторите их: в отличие от многих примеров здесь, это будет работать с группами любого размера.

Andrey Cizov 25.02.2018 00:55

Никакого волшебства, но просто и правильно:

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

Azat Ibrakov 06.10.2018 12:24

Мне не нравится идея разделения элементов по размеру блока, например скрипт может разделить от 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]]

more_itertools+

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

использованная литература

+ 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] для я в диапазоне (фрагменты)]

darkman 27.03.2020 17:47

Этот вопрос напоминает мне метод .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]]

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

juanpa.arrivillaga 02.05.2020 09:08
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]]

Другие вопросы по теме