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

2270

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

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

Я искал что-то полезное в, itertoolsно я не мог найти ничего явно полезного. Возможно, я пропустил это.

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

jespern
источник
1
Прежде чем опубликовать новый ответ, учтите, что на этот вопрос уже есть более 60 ответов. Пожалуйста, убедитесь, что ваш ответ содержит информацию, которой нет среди существующих ответов.
Янник
Для пользователей, которые хотят избежать сколь угодно маленького финального блока, посмотрите разделение списка на N частей примерно равной длины
wim

Ответы:

3155

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

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)]
Нед Бэтчелдер
источник
72
Что произойдет, если мы не сможем определить длину списка? Попробуйте это на itertools.repeat ([1, 2, 3]), например,
jespern
47
Это интересное продолжение вопроса, но первоначальный вопрос четко задан относительно работы со списком.
Нед Бэтчелдер
33
эта функция должна быть в
чертовой
6
@Calimo: что ты предлагаешь? Я передаю вам список из 47 элементов. Как бы вы хотели разделить его на «куски одинакового размера»? ОП приняла ответ, поэтому они явно согласны с последним чанком разного размера. Возможно английская фраза неточна?
Нед Бэтчелдер
8
Пожалуйста, не называйте свои переменные l, они выглядят как 1 и сбивают с толку. Люди копируют ваш код и думают, что это нормально.
Ясен
555

Если вы хотите что-то супер простое:

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

oremj
источник
6
Или (если мы делаем разные представления этой конкретной функции), вы можете определить лямбда-функцию через: лямбда х, у: [x [i: i + y] для i в диапазоне (0, len (x), y) ]. Я люблю этот метод понимания списка!
JP
4
после возвращения должно быть [, а не (
alwbtc
2
«Супер просто» означает отсутствие необходимости отлаживать бесконечные циклы - слава для max().
Боб Стейн
в этом решении нет ничего простого
мит
1
@Nhoj_Gonk Ой, это не бесконечный цикл, но чанки (L, 0) вызовут ValueError без max (). Вместо этого max () превращает все, что меньше 1, в 1.
Боб Стейн
295

Непосредственно из (старой) документации Python (рецепты для itertools):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

Текущая версия, предложенная JFSebastian:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Я предполагаю, что машина времени Гвидо работает - работала - будет работать - будет работать - снова работала.

Эти решения работают, потому что [iter(iterable)]*n(или эквивалент в более ранней версии) создает один итератор, повторяющийся nраз в списке. izip_longestзатем эффективно выполняет циклический перебор «каждого» итератора; поскольку это один и тот же итератор, он продвигается при каждом таком вызове, в результате чего каждый такой zip-циклический цикл генерирует один кортеж nэлементов.

tzot
источник
@ninjagecko: list(grouper(3, range(10)))возвращается [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], и все кортежи имеют длину 3. Пожалуйста, опишите ваш комментарий, потому что я не могу его понять; что вы называете вещью, и как вы определяете, что она кратна 3 в «ожидании, что ваша вещь будет кратна 3»? Заранее спасибо.
tzot
14
проголосовал за это, потому что он работает на генераторах (без len) и использует, как правило, более быстрый модуль itertools.
Майкл Диллон
88
Классический пример причудливого itertoolsфункционального подхода, превращающего некоторый нечитаемый шлам по сравнению с простой и наивной чистой реализацией Python
wim
15
@wim Учитывая, что этот ответ начинался как фрагмент из документации по Python, я бы посоветовал вам открыть проблему на bugs.python.org .
tzot
1
@pedrosaurio, если l==[1, 2, 3]тогда f(*l)эквивалентно f(1, 2, 3). Смотрите этот вопрос и официальную документацию .
tzot
227

Я знаю, что это старое, но никто еще не упомянул 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])]
Moj
источник
12
Это позволяет вам установить общее количество блоков, а не количество элементов на блок.
FizxMike
6
Вы можете сделать математику самостоятельно. если у вас есть 10 элементов, вы можете сгруппировать их по 2, 5 элементам или пяти 2 элементам
Moj
24
+1 Это мое любимое решение, поскольку оно разбивает массив на массивы равномерного размера, в то время как другие решения этого не делают (во всех других решениях, на которые я смотрел, последний массив может быть сколь угодно малым).
MiniQuark
@MiniQuark, но что это делает, если количество блоков не зависит от размера исходного массива?
Балдрикк
1
@Baldrickk Если вы разделите N элементов на K блоков, то первые N% K блоков будут иметь N // K + 1 элементов, а остальные будут иметь N // K элементов. Например, если разбить массив, содержащий 108 элементов, на 5 блоков, то первые 108% 5 = 3 блоков будут содержать 108 // 5 + 1 = 22 элементов, а остальные фрагменты будут иметь 108 // 5 = 21 элементы.
MiniQuark
147

Я удивлен, что никто не думал об использовании формы 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)]
senderle
источник
7
Замечательно, твоя простая версия - моя любимая. Другие тоже придумали базовое islice(it, size)выражение и встроили его (как я сделал) в конструкцию цикла. Только вы подумали о версии с двумя аргументами iter()(я совершенно не знал), что делает ее супер-элегантной (и, вероятно, наиболее эффективной с точки зрения производительности). Я понятия не имел, что первый аргумент для iterизменения функции с 0 аргументами при задании дозорного. Вы возвращаете (пот. Бесконечный) итератор чанков, можете использовать (пот. Бесконечный) итератор в качестве входных данных, не иметь len()и не иметь срезов массива. Потрясающие!
ThomasH
1
Вот почему я перечитываю ответы, а не сканирую только верхнюю пару. В моем случае требовалось дополнительное заполнение, и я тоже узнал о форме iter с двумя аргументами.
Керр
Я проголосовал за это, но все же - давайте не будем преувеличивать это! Во-первых, лямбда может быть плохой (медленное закрытие по itитератору. Во-вторых, и самое важное - вы преждевременно прекратите работу, если padvalв вашей итерируемой части действительно существует кусок, который должен быть обработан.
Томаш Гандор
@TomaszGandor, я понимаю твою первую мысль! Хотя я понимаю, что лямбда не медленнее, чем обычная функция, конечно, вы правы, что поиск вызова и закрытия функции замедлит это. Я не знаю, каким будет относительный удар по производительности по сравнению с izip_longestподходом, например - я подозреваю, что это может быть сложным компромиссом. Но ... разве padvalпроблема не разделена каждым ответом здесь, который предлагает padvalпараметр?
senderle
1
@ TomaszGandor, достаточно справедливо! Но не было слишком сложно создать версию, которая исправит это. (Кроме того , обратите внимание , что первая версия, которая использует в ()качестве дозорных, делает работу правильно Это происходит потому, tuple(islice(it, size))урожаи , ()когда itпусто.)
senderle
93

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

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]]
Маркус Жардеро
источник
52
def chunk(input, size):
    return map(None, *([iter(input)] * size))
Томаш Высоцкий
источник
map(None, iter)равно izip_longest(iter).
Томас Але
1
@TomaszWysocki Можете ли вы объяснить *перед вами кортеж итератора? Возможно, в вашем тексте ответа, но я заметил, что *раньше это использовалось в Python. Спасибо!
theJollySin
1
@theJollySin В этом контексте это называется оператором сплат. Его использование объясняется здесь - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
РМЭЗ
2
Закрыть, но у последнего блока нет элементов None для его заполнения. Это может быть или не быть дефектом. Действительно крутой образец, хотя.
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)
lebenf
источник
18
Ты не должен дублировать переменную в подобии арабского числа. В некоторых шрифты 1и lнеразличимы. Как есть 0и O. А иногда даже Iи 1.
Alfe
14
@Alfe Неисправные шрифты. Люди не должны использовать такие шрифты. Ни для программирования, ни для чего .
Джерри Б
17
Лямбды предназначены для использования в качестве неназванных функций. Нет смысла их так использовать. Кроме того, это усложняет отладку, так как в случае ошибки трассировка будет сообщать «в <lambda>», а не «в чанках». Я желаю вам удачи в поиске проблемы, если у вас есть целая куча из них :)
Крис Костон
1
это должно быть 0, а не 1 внутри xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta
ПРИМЕЧАНИЕ. Для пользователей Python 3 range.
Кристиан Дин
40

Критика других ответов здесь:

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

Например, текущий верхний ответ заканчивается:

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

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

Аарон Холл
источник
Вы говорите, что ничего из вышеперечисленного не дает куски одинакового размера. Но этот делает, как и этот .
senderle
1
@senderle, первый один, list(grouper(3, xrange(7)))и второй, chunk(xrange(7), 3)и возвращение: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Они Noneпросто набивают, и, на мой взгляд, довольно не элегантны. Они НЕ равномерно разбивают фрагменты. Спасибо за ваш голос!
Аарон Холл
4
Вы поднимаете вопрос (не делая этого явно, поэтому я делаю это сейчас здесь), является ли куски одинакового размера (кроме последнего, если это невозможно) или сбалансированным (настолько хорошим, насколько это возможно) результатом, который будет необходим чаще всего. Вы предполагаете, что сбалансированное решение - это предпочтение; это может быть правдой, если то, что вы программируете, близко к реальному миру (например, алгоритм раздачи карт для симулированной карточной игры). В других случаях (например, заполнение строк словами) желательно, чтобы строки были максимально полными. Так что я не могу отдать предпочтение одному другому; они просто для разных вариантов использования.
Alfe
@ ChristopherBarrington-Leigh Хорошая мысль: для DataFrames вам, вероятно, следует использовать слайсы, поскольку я считаю, что объекты DataFrame обычно не копируются при нарезке, напримерimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Аарон Холл
1
@AaronHall Ой. Я удалил свой комментарий, потому что я угадал мою критику, но вы были быстры на ничью. Спасибо! На самом деле, мое утверждение о том, что он не работает для фреймов данных, верно. Если items - это фрейм данных, просто используйте в качестве последней строки yield items [range (x_i, item_count, baskets)]. Я предложил отдельный (еще один) ответ, в котором вы указываете желаемый (минимальный) размер группы.
CPBL
38

Я видел самый удивительный ответ 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.

Noich
источник
это хорошо, если ваш список и чанки короткие, как вы могли бы приспособить это, чтобы разделить ваш список на чанки по 1000? Вы не собираетесь кодировать zip (я, я, я, я, я, я, я, я, я ..... я = 1000)
Том Смит
9
zip(i, i, i, ... i)с «chunk_size» аргументы молнии () можно записать в виде Является zip(*[i]*chunk_size)ли это хорошая идея или нет , это вопрос спорный, конечно.
Уилсон Ф.
1
Недостатком этого является то, что если вы не делите равномерно, вы будете отбрасывать элементы, поскольку zip останавливается на самом коротком итерируемом элементе - & izip_longest добавит элементы по умолчанию.
Аарон Холл
zip_longestследует использовать, как это сделано в: stackoverflow.com/a/434411/1959808
Иоаннис Филиппидис
Ответ с range(1, 15)уже пропущенными элементами, потому что есть 14 элементов range(1, 15), а не 15.
Иоаннис Филиппидис
35

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

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

В последнем случае его можно перефразировать более красивым способом, если вы можете быть уверены, что последовательность всегда содержит целое количество фрагментов заданного размера (т.е. не существует неполного последнего фрагмента).

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

Библиотека инструментов имеет partitionфункцию для этого:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
Zach
источник
Это выглядит как самый простой из всех предложений. Мне просто интересно, действительно ли это может быть правдой, что нужно использовать стороннюю библиотеку, чтобы получить такую ​​функцию разделения. Я ожидал бы, что что-то эквивалентное с этой функцией разделения будет существовать как встроенный язык.
Касперд
1
Вы можете сделать раздел с помощью itertools. но мне нравится библиотека инструментов. это вдохновленная библиотека для работы над коллекциями в функциональном стиле. вы не получаете неизменности, но вы получаете небольшой словарный запас для работы с простыми коллекциями. Как плюс, cytoolz написан на cython и получает хороший прирост производительности. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
Zach
Ссылка из комментария Зака ​​работает, если вы пропустите косую черту: matthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz
mit
17

Например, если у вас размер куска 3, вы можете сделать:

zip(*[iterable[i::3] for i in range(3)]) 

источник: http://code.activestate.com/recipes/303060-group-a-list-into-sequential-n-tuples/

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

ninjagecko
источник
11
Это не работает, если len (итеративный)% 3! = 0. Последняя (короткая) группа чисел не будет возвращена.
щербанг
16

Мне очень нравится версия документа Python, предложенная tzot и JFSebastian, но у нее есть два недостатка:

  • это не очень явно
  • Я обычно не хочу, чтобы значение заполнения в последнем чанке

Я использую это много в моем коде:

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))
nikipore
источник
Каково условие разрыва while Trueцикла?
wjandrea
@wjandrea: Повышенный, StopIterationкогда tupleпустой и iterable.next()выполняется. Не работает должным образом в современном Python, где выход из генератора должен быть сделан return, а не повышение StopIteration. try/except StopIteration: returnВокруг всего контура (и изменение iterable.next()в next(iterable)кросс-версии Compat) фиксирует это с минимальными затратами , по крайней мере.
ShadowRanger
15
[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
Риаз Ризви
источник
2
это самое лучшее и простое.
Ф.Тами
2
коротко и просто. простота над сложностью.
dkrynicki
15

Мне было интересно узнать о производительности различных подходов, и вот оно:

Протестировано на 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
Алекс Т
источник
3
тестирование с использованием timeбиблиотеки не очень timeit
хорошая
13

код:

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]]
Арт Б
источник
12

Вы также можете использовать 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 .

Мойнуддин Квадри
источник
11

На данный момент, я думаю, нам нужен рекурсивный генератор , на всякий случай ...

В питоне 2:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

В питоне 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
Mazieres
источник
9

С помощью Assignment Expressions в 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]]
нирвана-MSU
источник
1
Теперь это достойный новый ответ на этот вопрос. Мне на самом деле очень нравится это. Я скептически отношусь к выражениям присваивания, но когда они работают, они работают.
juanpa.arrivillaga
7

хех, однострочная версия

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]]
slav0nic
источник
36
Пожалуйста, используйте "def chunk" вместо "chunk = lambda". Это работает так же. Одна линия. Те же функции Гораздо проще для n00bz читать и понимать.
S.Lott
4
@ S.Lott: нет, если n00bz происходит от схемы: P, это не реальная проблема. есть даже ключевое слово для Google! какие еще функции мы не используем ради n00bz? Я полагаю, что доходность не является обязательной / c-подобной, чтобы быть дружественной к n00b.
Янус Троелсен
16
Функциональный объект, полученный def chunkвместо вместо chunk=lambda.__ name__ атрибут 'chunk' вместо '<lambda>'. Конкретное имя более полезно в трассировках.
Терри Ян Риди
1
@Alfe: Я не уверен, можно ли назвать главное семантическое различие, но есть ли полезное имя в трассировке вместо <lamba>или нет, по крайней мере, заметная разница.
Мартино
1
После тестирования нескольких из них на производительность, это здорово!
Солнечный Патель
7
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
Кори Голдберг
источник
7

Еще одна более явная версия.

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
Ranaivo
источник
(2016 Sep 12) Этот ответ является наиболее независимым от языка и легким для чтения.
D Adams
7

Без вызова 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))
Марс
источник
16
Нет причин избегать len()больших списков; это операция с постоянным временем.
Томас Воутерс
7

Вот список дополнительных подходов:

Данный

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

Ссылки

+ Сторонняя библиотека, которая реализует рецепты itertools и многое другое.> pip install more_itertools

pylang
источник
6

Смотрите эту ссылку

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

MACM
источник
3
Хорошо, но удаляет элементы в конце, если размер не совпадает с целым числом кусков, например, zip(*[iter(range(7))]*3)только возвращает [(0, 1, 2), (3, 4, 5)]и забывает 6входные данные.
Alfe
6

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

Но если вы не хотите пощадить память, вы можете использовать old-way и хранить все listв первую очередь с помощью iterutils.chunked.

vishes_shell
источник
И этот на самом деле работает независимо от того, кто смотрит на субитераторов!
Питер Гердес
6

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

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]
>>> 
Анатолий Панин
источник
5
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]]'
Роберт Кинг
источник
1
Хотя это может выглядеть не так коротко и не так красиво, как многие ответы на основе itertools, на самом деле это работает, если вы хотите распечатать второй подсписок перед доступом к первому, то есть вы можете установить i0 = next (g2); i1 = следующая (g2); и используйте i1 перед использованием i0, и он не сломается!
Питер Гердес
5

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

например:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
schwater
источник
Похоже, вы случайно создали две учетные записи. Вы можете связаться с командой, чтобы объединить их, что позволит вам восстановить права прямого редактирования ваших вкладов.
Георгий