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

2633

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

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

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

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

2
  • 12
    Прежде чем опубликовать новый ответ, подумайте, что на этот вопрос уже есть более 60 ответов. Пожалуйста, убедитесь, что ваш ответ содержит информацию, которой нет среди существующих ответов. janniks 3 фев '20 в 12:17
  • простой на stackoverflow.com/a/66967457/687896Brandt 6 апр в 11:13
3790
+100

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

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)]
14
  • 82
    Что произойдет, если мы не сможем определить длину списка? Попробуйте это на itertools.repeat ([1, 2, 3]), напримерjespern 23 нояб.
  • 51
    Это интересное расширение вопроса, но исходный вопрос явно задан о работе со списком. Ned Batchelder 23 нояб.
  • 76
    эти функции должны быть в чертовой стандартной библиотекеdgan 4 фев '18 в 14:19
  • 8
    @Calimo: что ты посоветуешь? Я передаю вам список из 47 элементов. Как бы вы хотели разделить его на «куски одинакового размера»? OP принял ответ, поэтому они явно в порядке с последним фрагментом другого размера. Возможно, английская фраза неточна? Ned Batchelder 14 июня '18 в 18: 292018-06-14 15:29
  • 7
    Большинство людей будут смотреть на это для пакетной обработки и ограничения скорости, поэтому обычно не имеет значения, меньше ли последний кусокAlvaro 04 июл.
607

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

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

2
  • 6
    Или (если мы делаем разные представления этой конкретной функции) вы можете определить лямбда-функцию с помощью: lambda x, y: [x [i: i + y] для i в диапазоне (0, len (x), y) ]. Мне нравится этот метод понимания списков! J-P 20 авг.
  • Использование короткого замыкания len(l) or 1для работы с пустыми списками. keepAlive 12 августа в 15:24
322

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

from itertools import izip, chain, repeat

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

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

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

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

Думаю, машина времени Гвидо работает - работает - будет работать - будет работать - снова заработала.

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

3
  • 19
    поддержал это, потому что он работает с генераторами (без len) и использует в целом более быстрый модуль itertools. Michael Dillon 30 янв.
  • 99
    Классический пример причудливого itertoolsфункционального подхода, в результате которого получается нечитаемый осадок по сравнению с простой и наивной реализацией чистого Pythonwim 12 апр '13 в 5: 402013-04-12 05:40
  • 16
    @wim Учитывая, что этот ответ начался как фрагмент из документации Python, я предлагаю вам открыть проблему на bugs.python.org . tzot 12 апр '13 в 11: 362013-04-12 11:36
319

Я знаю, что это старовато, но никто еще не упомянул 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])]
1
  • 18
    Это позволяет вам установить общее количество фрагментов, а не количество элементов в каждом фрагменте. FizxMike 9 сен.
223

Я удивлен, что никто не подумал об использовании формы 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решения на основе -base, вышеперечисленное всегда является подкладкой. Насколько мне известно, не существует одно- или двухстрочного рецепта 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)]
1
  • 10
    Замечательно, ваша простая версия - моя любимая. Другие тоже придумали базовое islice(it, size)выражение и встроили его (как это сделал я) в конструкцию цикла. Только вы подумали о версии с двумя аргументами iter()(я совершенно не подозревал о ней), которая делает ее супер-элегантной (и, вероятно, наиболее эффективной с точки зрения производительности). Я понятия не имел, что первый аргумент iterизменится на функцию с 0 аргументами, когда будет указан часовой. Вы возвращаете итератор блоков (pot. Infinite), можете использовать итератор (pot. Infinite) в качестве входных данных, не иметь len()и не иметь срезов массива. Потрясающие! ThomasH 15 сен '16 в 19:58
106

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

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]]
0
66

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

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)
3
  • 23
    Не дублируй переменную как арабское число. В некоторых шрифтах они 1и lнеотличимы. Как есть 0и O. А иногда даже Iи 1. Alfe 14 авг.
  • 22
    @Alfe Неисправные шрифты. Люди не должны использовать такие шрифты. Ни для программирования, ни для чего . Jerry B 5 окт.
  • 18
    Лямбды предназначены для использования в качестве безымянных функций. Нет смысла их так использовать. Кроме того, это затрудняет отладку, поскольку в случае ошибки трассировка будет сообщать «в <лямбда>», а не «по частям». Желаю вам удачи в поиске проблемы, если у вас их целая куча :)Chris Koston 26 ноя '13 в 22: 452013-11-26 19:45
60
def chunk(input, size):
    return map(None, *([iter(input)] * size))
0
49

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

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

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

Ломтики

Еще одно элегантное решение - использовать срезы - в частности, менее часто используемый аргумент step для срезов. то есть:

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

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

11
  • Вы говорите, что ни одно из вышеперечисленного не обеспечивает куски одинакового размера. Но этот делает, как и этот . senderle 26 фев '14 в 15:00
  • 1
    @senderle, первый один, list(grouper(3, xrange(7)))и второй, chunk(xrange(7), 3)и возвращение: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Это Noneпросто набивка и, на мой взгляд, довольно неэлегантная. Они НЕ равномерно разбивают итерируемые объекты. Спасибо за голос! Aaron Hall 26 фев '14 в 16: 072014-02-26 16:07
  • 6
    Вы поднимаете вопрос (не делая этого явно, поэтому я делаю это сейчас здесь), будут ли блоки одинакового размера (кроме последнего, если это невозможно) или сбалансированный (как можно более хороший) результат чаще всего будет необходим. Вы предполагаете, что предпочтение должно быть отдано сбалансированному решению; это может быть правдой, если то, что вы программируете, близко к реальному миру (например, алгоритм раздачи карт для симулированной карточной игры). В других случаях (например, при заполнении строк словами) лучше оставить строки как можно более полными. Так что я не могу предпочитать одно другому; они просто для разных случаев использования. Alfe 02 авг.
  • @ ChristopherBarrington-Leigh Хороший момент, для DataFrames вам, вероятно, следует использовать срезы, поскольку я считаю, что объекты DataFrame обычно не копируются при срезании, напримерimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]Aaron Hall 3 сен '14 в 17:10
  • 1
    @AaronHall Ой. Я удалил свой комментарий, потому что я усомнился в своей критике, но вы быстро сыграли вничью. Спасибо! Фактически, мое утверждение, что это не работает для фреймов данных, верно. Если items является фреймом данных, просто используйте yield items [диапазон (x_i, item_count, корзинки)] в качестве последней строки. Я предложил отдельный (еще один) ответ, в котором вы указываете желаемый (минимальный) размер группы. CPBL 3 сен '14 в 17:47
44

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

5
  • это хорошо, если ваш список и фрагменты короткие, но как вы могли бы адаптировать это, чтобы разделить свой список на куски по 1000? вы не собираетесь кодировать почтовый индекс (i, i, i, i, i, i, i, i, i, i ..... i = 1000)Tom Smith 18 мая '15 в 14:21
  • 11
    zip(i, i, i, ... i)с "chunk_size" аргументы для zip () могут быть записаны как zip(*[i]*chunk_size)"Хорошая это идея или нет, конечно, спорный". Wilson F 28 июня '15 в 4: 522015-06-28 04:52
  • 1
    Обратной стороной этого является то, что если вы не делите равномерно, вы отбрасываете элементы, так как zip останавливается на самом коротком итеративном значении - & izip_longest добавит элементы по умолчанию. Aaron Hall 08 июл.
  • zip_longestследует использовать, как это сделано в: stackoverflow.com/a/434411/1959808Ioannis Filippidis 21 июня '17 в 13:28
  • В ответе range(1, 15)уже отсутствуют элементы, потому что в них 14 элементов range(1, 15), а не 15.Ioannis Filippidis 21 июня '17 в 13:34
44

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

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

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

0
23

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

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

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

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

1
  • 13
    Это не работает, если len (iterable)% 3! = 0. Последняя (короткая) группа чисел не будет возвращена. sherbang 03 июл.
22
[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
0
21

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

from toolz.itertoolz.core import partition

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

С выражениями присваивания в 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]]
0
17

Мне очень нравится версия документа 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))
0
17

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

Протестировано на 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
0
14

код:

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]]
0
14

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

0
12

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

В 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
0
12

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

Данный

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

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

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

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]]
3
  • 37
    Пожалуйста, используйте def chunk вместо chunk = lambda. Работает так же. Одна линия. Те же функции. НАМНОГО легче читать и понимать n00bz. S.Lott 23 нояб.
  • 4
    @ S.Lott: нет, если n00bz исходит от схемы: P, это не настоящая проблема. есть даже ключевое слово для Google! Какие еще функции показывают, чего мы избегаем ради n00bz? Я думаю, yield не является обязательным / c-подобным достаточно, чтобы быть дружелюбным к n00b. Janus Troelsen 11 мая '12 в 21:10
  • 18
    Функциональный объект, полученный из, def chunkвместо chunk=lambdaимеет атрибут .__ name__ 'chunk' вместо '<lambda>'. Конкретное имя более полезно при трассировке. Terry Jan Reedy 27 июн.
9

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

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
1
  • (12 сентября 2016 г.) Этот ответ не зависит от языка и его легче всего читать. D Adams 14 сен '16 в 0:36
9

Без вызова 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))
1
  • 16
    Нет причин избегать len()больших списков; это операция с постоянным временем. Thomas Wouters 30 мая '11 в 10:03
8

См. Эту ссылку

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

2
  • 4
    Хорошо, но отбрасывает элементы в конце, если размер не соответствует целому количеству фрагментов, например, zip(*[iter(range(7))]*3)только возвращает [(0, 1, 2), (3, 4, 5)]и забывает 6из ввода. Alfe 14 авг.
  • OP написал: «У меня есть список произвольной длины, и мне нужно разбить его на части равного размера и работать с ним». Возможно, я что-то упускаю, но как получить «куски равного размера» из списка произвольной длины, не отбрасывая куски, которые короче «равного размера»Aivar Paalberg 27 сен.
7
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
  • 1
    Хотя это может показаться не таким коротким или красивым, как многие ответы на основе itertools, этот на самом деле работает, если вы хотите распечатать второй подсписок перед доступом к первому, т.е. вы можете установить i0 = next (g2); i1 = следующий (g2); и используйте i1 перед использованием i0, и он не сломается !! Peter Gerdes 19 дек.
7

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

Например:

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

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

1
  • И этот действительно работает независимо от порядка просмотра подитераторов !! Peter Gerdes 19 дек.
6
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 )]
2
  • Не могли бы вы объяснить свой ответ поподробнее? Zulu 16 июл.
  • Работа в обратном направлении: (len (a) + CHUNK -1) / CHUNK Дает вам количество фрагментов, которые у вас останутся. Затем для каждого фрагмента с индексом i мы генерируем подмассив исходного массива следующим образом: a [i * CHUNK: (i + 1) * CHUNK], где i * CHUNK - это индекс первого элемента для помещается в подмассив, и (i + 1) * CHUNK - это 1 после последнего элемента, помещаемого в подмассив. Это решение использует понимание списков, поэтому оно может быть быстрее для больших массивов. AdvilUser 29 июля '15 в 0:29