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

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

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

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

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

23.11.2008 12:15:52
Прежде чем опубликовать новый ответ, учтите, что на этот вопрос уже есть более 60 ответов. Пожалуйста, убедитесь, что ваш ответ содержит информацию, которой нет среди существующих ответов.
janniks 3.02.2020 12:17:33
Для пользователей, которые хотят избежать сколь угодно маленького финального фрагмента, посмотрите разделение списка на N частей примерно одинаковой длины
wim 20.02.2020 21:15:09
30 ОТВЕТОВ
РЕШЕНИЕ

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

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)]
3065
28.11.2019 01:43:27
Что произойдет, если мы не сможем определить длину списка? Попробуйте это на itertools.repeat ([1, 2, 3]), например,
jespern 23.11.2008 12:51:10
Это интересное продолжение вопроса, но первоначальный вопрос четко задан относительно работы со списком.
Ned Batchelder 23.11.2008 13:53:37
эта функция должна быть в
dgan 4.02.2018 14:19:55
@Calimo: что ты предлагаешь? Я передаю вам список из 47 элементов. Как бы вы хотели разделить его на «куски одинакового размера»? ОП приняла ответ, поэтому они явно в порядке с последним чанком разного размера. Возможно английская фраза неточна?
Ned Batchelder 14.06.2018 15:29:23
Пожалуйста, не называйте свои переменные l, они выглядят как 1 и сбивают с толку. Люди копируют ваш код и думают, что это нормально.
Yasen 27.11.2019 12:33: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

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

33
18.04.2019 11:18:24
Мне грустно, что это похоронено так далеко внизу. IterChunks работает для всего и является общим решением и не имеет никаких предостережений, о которых я знаю.
Jason Dunkelberger 7.08.2015 23:31:15

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

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]]
90
17.09.2012 21:22:03

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

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]]
7
23.11.2008 12:51:16
Пожалуйста, используйте "def chunk" вместо "chunk = lambda". Это работает так же. Одна линия. Те же функции Гораздо проще для n00bz читать и понимать.
S.Lott 23.11.2008 13:45:51
@ S.Lott: нет, если n00bz происходит от схемы: P, это не реальная проблема. есть даже ключевое слово для Google! какие еще функции мы избегаем ради n00bz? я думаю, что доходность не является обязательной / с-подобной, чтобы быть дружелюбной к n00b.
Janus Troelsen 11.05.2012 21:10:56
Объект функции, полученный def chunkвместо вместо chunk=lambda.__ name__ атрибут 'chunk' вместо '<lambda>'. Конкретное имя более полезно в трассировках.
Terry Jan Reedy 27.06.2012 04:20:59
@Alfe: Я не уверен, можно ли назвать главное семантическое различие, но есть ли полезное имя в трассировке вместо <lamba>или нет, по крайней мере, заметная разница.
martineau 11.01.2015 20:33:46
После тестирования нескольких из них на производительность, это здорово!
Sunny Patel 5.10.2018 16:36:10

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

288
21.09.2017 09:47:29
@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 13:09:44
проголосовал за это, потому что он работает на генераторах (без len) и использует, как правило, более быстрый модуль itertools.
Michael Dillon 30.01.2012 23:47:08
Классический пример причудливого itertoolsфункционального подхода, превращающего некоторый нечитаемый шлам по сравнению с простой и наивной чистой реализацией Python
wim 12.04.2013 05:40:11
@wim Учитывая, что этот ответ начинался как фрагмент из документации по Python, я бы посоветовал вам открыть проблему на bugs.python.org .
tzot 12.04.2013 11:36:07
@pedrosaurio, если l==[1, 2, 3]тогда f(*l)эквивалентно f(1, 2, 3). Смотрите этот вопрос и официальную документацию .
tzot 21.08.2019 08:02:56
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
24.11.2008 16:56:57
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
3
23.06.2012 15:19:32
>>> f = lambda x, n, acc=[]: f(x[n:], n, acc+[(x[:n])]) if x else acc
>>> f("Hallo Welt", 3)
['Hal', 'lo ', 'Wel', 't']
>>> 

Если вы в скобках - я взял книгу об Эрланге :)

3
3.11.2009 17:10:08

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

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

546
6.01.2020 10:56:07
Или (если мы делаем разные представления этой конкретной функции), вы можете определить лямбда-функцию через: лямбда х, у: [x [i: i + y] для i в диапазоне (0, len (x), y) ]. Я люблю этот метод понимания списка!
J-P 20.08.2011 13:54:34
после возвращения должно быть [, а не (
alwbtc 1.06.2017 06:45:56
«Супер просто» означает отсутствие необходимости отлаживать бесконечные циклы - слава для max().
Bob Stein 15.05.2018 17:49:13
в этом решении нет ничего простого
mit 19.10.2018 12:36:59
Обратите внимание, что результат с входным списком ['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 08:59:37

Без вызова 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))
7
16.05.2016 06:29:12
Нет причин избегать len()больших списков; это операция с постоянным временем.
Thomas Wouters 30.05.2011 10:03:12
def chunk(input, size):
    return map(None, *([iter(input)] * size))
51
17.09.2012 21:22:25
map(None, iter)равно izip_longest(iter).
Thomas Ahle 29.01.2012 15:18:29
@TomaszWysocki Можете ли вы объяснить *перед вами кортеж итератора? Возможно, в вашем тексте ответа, но я заметил, что *раньше это использовалось в Python. Спасибо!
theJollySin 7.10.2013 18:58:13
@theJollySin В этом контексте это называется оператором сплат. Его использование объясняется здесь - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms 15.11.2013 21:14:54
Закрыть, но в последнем блоке нет ни одного элемента для его заполнения. Это может быть или не быть дефектом. Действительно крутой образец, хотя.
user1969453 25.04.2014 01:49:12

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

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

или если вы предпочитаете:

chunks = lambda l, n: [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)
47
10.02.2015 10:27:39
Не дублируй переменную в подобии арабского числа. В некоторых шрифты 1и lнеразличимы. Как есть 0и O. А иногда даже Iи 1.
Alfe 14.08.2013 23:02:09
@Alfe Неисправные шрифты. Люди не должны использовать такие шрифты. Не для программирования, ни для чего .
Jerry B 5.10.2013 08:14:31
Лямбды предназначены для использования в качестве неназванных функций. Нет смысла их так использовать. Кроме того, это усложняет отладку, так как в случае ошибки трассировка будет сообщать «в <lambda>», а не «в чанках». Я желаю вам удачи в поиске проблемы, если у вас есть целая куча из них :)
Chris Koston 26.11.2013 19:45:40
это должно быть 0, а не 1 внутри xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta 28.12.2013 19:11:35
ПРИМЕЧАНИЕ. Для пользователей Python 3 range.
Christian Dean 31.08.2017 12:32:54

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

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

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

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

17
19.04.2011 05:27:19
Это не работает, если len (итеративный)% 3! = 0. Последняя (короткая) группа чисел не будет возвращена.
sherbang 3.07.2012 19:28:27

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

например:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
5
8.03.2012 18:27:15
Похоже, вы случайно создали две учетные записи. Вы можете связаться с командой, чтобы объединить их, что позволит вам восстановить права прямого редактирования ваших вкладов.
Georgy 15.05.2019 15:15:26
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]]'
5
13.02.2012 04:50:38
Хотя это может выглядеть не так коротко и не так красиво, как многие ответы на основе itertools, на самом деле это работает, если вы хотите распечатать второй подсписок перед доступом к первому, то есть вы можете установить i0 = next (g2); i1 = следующая (g2); и используйте i1 перед использованием i0, и он не сломается !!
Peter Gerdes 19.12.2017 10:25:55

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

Никто не использует функцию tee () под itertools?

http://docs.python.org/2/library/itertools.html#itertools.tee

>>> import itertools
>>> itertools.tee([1,2,3,4,5,6],3)
(<itertools.tee object at 0x02932DF0>, <itertools.tee object at 0x02932EB8>, <itertools.tee object at 0x02932EE0>)

Это разделит список на 3 итератора, цикл итератор получит подсписок равной длины

-5
7.12.2012 03:16:51
Я не думаю, что это делает то, что вы думаете, что делает. У каждого из итераторов в tee (по крайней мере для меня) есть полный список: >>> map (list, itertools.tee ([1,2,3,4,5,6], 3)) [[1 , 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]]
Christopher Schmidt 8.06.2013 19:04:51
Да, teeаналогично созданию итератора.
pylang 26.08.2018 01:43:35

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

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

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

используя список понимания 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]]

перейдите по этой ссылке, чтобы узнать о списках

-1
14.11.2014 09:47:28
Как бы вы применили свой подход к существующему списку, который поступает в качестве входных данных?
Alfe 14.08.2013 23:18:38
@Alfefor chunk in [some_list[i:i + 10] for i in range(0, len(some_list), 10)]: print chunk
flexd 2.08.2014 21:34:36
Таким образом, это очень похоже на принятый топ-ответ ;-)
Alfe 2.08.2014 23:17:27

Я знаю, что это старое, но никто еще не упомянул 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])]
217
14.10.2019 08:16:28
Это позволяет вам установить общее количество блоков, а не количество элементов на блок.
FizxMike 9.09.2015 03:03:50
Вы можете сделать математику самостоятельно. если у вас есть 10 элементов, вы можете сгруппировать их по 2, 5 элементам или пяти 2 элементам
Moj 9.09.2015 07:27:58
+1 Это мое любимое решение, поскольку оно разбивает массив на массивы равномерного размера, в то время как другие решения этого не делают (во всех других решениях, на которые я смотрел, последний массив может быть сколь угодно малым).
MiniQuark 28.06.2016 17:26:29
@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 15:31:50
чистейшее решение. Спасибо.
Robert 31.10.2019 18:43:58
  • Работает с любыми повторяемыми
  • Внутренние данные - объект генератора (не список)
  • Один лайнер
В [259]: get_in_chunks = лямбда-itr, n: ((v для _, v в g) для _, g в itertools.groupby (перечислить (itr), лямбда (ind, _): ind / n))

В [260]: список (список (х) для х в get_in_chunks (диапазон (30), 7))
Из [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]]
2
13.09.2013 19:17:08
g = get_in_chunks (диапазон (30), 7); i0 = следующая (г); i1 = следующая (г); список (i1); список (I0); Последняя оценка пуста. Скрытое требование о доступе ко всем подспискам по порядку кажется мне очень плохим, потому что цель таких утилит часто состоит в том, чтобы перемешивать данные различными способами.
Peter Gerdes 19.12.2017 10:30:10
def chunked(iterable, size):
    chunk = ()

    for item in iterable:
        chunk += (item,)
        if len(chunk) % size == 0:
            yield chunk
            chunk = ()

    if chunk:
        yield chunk
-2
23.09.2013 23:57:43

Мне очень нравится версия документа 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))
16
9.11.2013 08:21:23
Каково условие разрыва while Trueцикла?
wjandrea 6.09.2019 13:40:03
@wjandrea: Повышенный, StopIterationкогда tupleпустой и iterable.next()выполняется. Не работает должным образом в современном Python, где выход из генератора должен быть сделан return, а не повышение StopIteration. try/except StopIteration: returnВокруг всего контура (и изменение iterable.next()в next(iterable)кросс-версии Compat) фиксирует это с минимальными затратами , по крайней мере.
ShadowRanger 22.01.2020 06:10:47

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

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
18
20.11.2013 20:55:22
Это выглядит как самый простой из всех предложений. Мне просто интересно, действительно ли это может быть правдой, что нужно использовать стороннюю библиотеку, чтобы получить такую ​​функцию разделения. Я ожидал бы, что что-то эквивалентное с этой функцией разделения будет существовать как встроенный язык.
kasperd 29.03.2015 14:45:45
Вы можете сделать раздел с помощью itertools. но мне нравится библиотека инструментов. это вдохновленная библиотека для работы над коллекциями в функциональном стиле. вы не получаете неизменности, но вы получаете небольшой словарный запас для работы с простыми коллекциями. Плюс, cytoolz написан на cython и получает хороший прирост производительности. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach 30.03.2015 15:28:09
Ссылка из комментария Зака ​​работает, если вы пропустите косую черту: matthewrocklin.com/blog/work/2014/05/01/Introduction-CyToolz
mit 19.10.2018 12:46:10

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

>>> 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]]
-1
26.11.2013 21:58:03

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

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

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

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

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

39
26.02.2014 16:11:00
Вы говорите, что ничего из вышеперечисленного не дает куски одинакового размера. Но этот делает, как и этот .
senderle 26.02.2014 15:00:56
@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 16:07:24
Вы поднимаете вопрос (не делая этого явно, поэтому я делаю это сейчас здесь), является ли куски одинакового размера (кроме последнего, если это невозможно) или сбалансированным (настолько хорошим, насколько это возможно) результатом, который будет необходим чаще всего. Вы предполагаете, что сбалансированное решение - это предпочтение; это может быть правдой, если то, что вы программируете, близко к реальному миру (например, алгоритм раздачи карт для симулированной карточной игры). В других случаях (например, заполнение строк словами) желательно, чтобы строки были максимально полными. Поэтому я не могу отдать предпочтение одному другому; они просто для разных вариантов использования.
Alfe 2.08.2014 23:14:52
@ ChristopherBarrington-Leigh Хорошая мысль: для DataFrames вам, вероятно, следует использовать срезы, поскольку я считаю, что объекты DataFrame обычно не копируются при нарезке, напримерimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall♦ 3.09.2014 17:10:35
@AaronHall Ой. Я удалил свой комментарий, потому что я угадал мою критику, но вы были быстры на ничью. Спасибо! На самом деле, мое утверждение о том, что он не работает для фреймов данных, верно. Если items - это фрейм данных, просто используйте в качестве последней строки yield items [range (x_i, item_count, baskets)]. Я предложил отдельный (еще один) ответ, в котором вы указываете желаемый (минимальный) размер группы.
CPBL 3.09.2014 17:47:40

Я удивлен, что никто не думал об использовании формы 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)]
139
17.11.2018 01:16:27
Замечательно, твоя простая версия - моя любимая. Другие тоже придумали базовое islice(it, size)выражение и встроили его (как я сделал) в конструкцию цикла. Только вы подумали о версии с двумя аргументами iter()(я совершенно не знал), что делает ее супер-элегантной (и, вероятно, наиболее эффективной с точки зрения производительности). Я понятия не имел, что первый аргумент для iterизменения функции с 0 аргументами при задании дозорного. Вы возвращаете (пот. Бесконечный) итератор чанков, можете использовать (пот. Бесконечный) итератор в качестве входных данных, не иметь len()и не иметь срезов массива. Потрясающие!
ThomasH 15.09.2016 19:58:02
Вот почему я перечитываю ответы, а не сканирую только верхнюю пару. В моем случае требовалось дополнительное заполнение, и я тоже узнал о форме iter с двумя аргументами.
Kerr 16.08.2017 14:30:02
Я проголосовал за это, но все же - давайте не будем преувеличивать это! Во-первых, лямбда может быть плохой (медленное закрытие через itитератор. Во-вторых, и самое важное - вы преждевременно прекратите работу, если padvalв вашей итерируемой части действительно существует кусок, который должен быть обработан.
Tomasz Gandor 16.11.2018 11:34:49
@TomaszGandor, я понимаю твою первую мысль! Хотя я понимаю, что лямбда не медленнее, чем обычная функция, конечно, вы правы, что поиск вызова и закрытия функции замедлит это. Я не знаю, каким будет относительный удар по производительности по сравнению с izip_longestподходом, например - я подозреваю, что это может быть сложным компромиссом. Но ... разве padvalпроблема не разделяется каждым ответом здесь, который предлагает padvalпараметр?
senderle 16.11.2018 12:38:39
@ TomaszGandor, достаточно справедливо! Но не было слишком сложно создать версию, которая исправит это. (Кроме того , обратите внимание , что первая версия, которая использует в ()качестве дозорных, делает работу правильно Это происходит потому, tuple(islice(it, size))урожаи , ()когда itпусто.)
senderle 17.11.2018 01:19:42

Я специально для этой цели написал небольшую библиотеку, доступную здесь . Функция библиотеки 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), ...]
3
3.03.2014 04:30:24

Как @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))

2
23.05.2017 12:02:59
кажется, дает несколько пустых кусков (len = 26, 10), или последний очень несбалансированный кусок (len = 26, 11).
idij 27.11.2014 13:11:55

допустим, что r - это размер чанка, а L - начальный список.

chunkL = [ [i for i in L[r*k:r*(k+1)] ] for k in range(len(L)/r)] 
4
9.12.2014 03:54:49