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

avatar
jespern
23 ноября 2008 в 12:15
1218550
70
2620

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

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

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

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

Источник
janniks
3 февраля 2020 в 12:17
12

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

Brandt
6 апреля 2021 в 11:13
0

простой на coderhelper.com/a/66967457/687896

Ответы (70)

avatar
Ned Batchelder
23 ноября 2008 в 12:33
3770

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

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)]
jespern
23 ноября 2008 в 12:51
81

Что произойдет, если мы не сможем определить длину списка? Попробуйте это на itertools.repeat ([1, 2, 3]), например

Ned Batchelder
23 ноября 2008 в 13:53
50

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

n611x007
24 апреля 2014 в 09:19
2

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

dgan
4 февраля 2018 в 14:19
74

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

SomethingSomething
3 апреля 2018 в 11:33
2

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

Calimo
14 июня 2018 в 11:51
1

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

Ned Batchelder
14 июня 2018 в 15:29
7

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

Calimo
14 июня 2018 в 15:46
3

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

Alvaro
4 июля 2019 в 12:46
6

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

marsipan
26 октября 2019 в 15:41
1

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

user2589273
12 мая 2020 в 19:52
0

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

Pierre D
20 мая 2020 в 02:11
0

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

Mr. Lance E Sloan
17 августа 2020 в 03:17
1

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

Bob Bobster
28 февраля 2021 в 21:32
0

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

avatar
Francesco Frassinelli
22 марта 2022 в 13:32
0

Python 3.8 и выше, с использованием оператора Walrus:

from itertools import islice

def chunk(it, size):
    it = iter(it)
    while slice := tuple(islice(it, size)):
        yield slice

Демо:

>>> list(chunk(range(10), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)]
avatar
shanu khera
31 января 2022 в 14:53
0

Допустим, список следующий: lst

import math

# length of the list len(lst) is ln
# size of a chunk is size

for num in range ( math.ceil(ln/size) ):
    start, end = num*size, min((num+1)*size, ln)
    print(lst[start:end])
avatar
user1022684
2 января 2022 в 11:14
0

Однострочная версия ответа senderle:

from itertools import islice
from functools import partial

seq = [1,2,3,4,5,6,7]
size = 3
result = list(iter(partial(lambda it: tuple(islice(it, size)), iter(seq)), ()))
assert result == [(1, 2, 3), (4, 5, 6), (7,)]
avatar
Amin Rezaei
16 декабря 2021 в 15:29
0

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

class ChunksIterator(object):
    def __init__(self, data, n):
        self._data = data
        self._l = len(data)
        self._n = n

    def __iter__(self):
        for i in range(0, self._l, self._n):
            yield self._data[i:i + self._n]

    def __len__(self):
        rem = 1 if self._l % self._n != 0 else 0
        return self._l // self._n + rem

Использование:

it = ChunksIterator([1,2,3,4,5,6,7,8,9], 2)
print(len(it))
for i in it:
  print(i)
avatar
Bapan Biswas
20 апреля 2021 в 14:46
0
from itertools import islice
l=[1,2,3,4,5,6]
chuncksize=input("Enter chunk size")
m=[]
obj=iter(l)
m.append(list(islice(l,3)))
m.append(list(islice(l,3)))
print(m)
DaveL17
20 апреля 2021 в 22:44
1

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

Chris
21 апреля 2021 в 00:19
0

Добро пожаловать в Stack Overflow. Дампы кода без каких-либо объяснений редко бывают полезными. Stack Overflow - это обучение, а не предоставление фрагментов для слепого копирования и вставки. Отредактируйте свой вопрос и объясните, как он отвечает на заданный конкретный вопрос. См. Как ответить на. Это особенно важно при ответе на старые вопросы (этому почти 12,5 лет) существующими ответами (уже есть 81 другой ответ ). Как этот ответ улучшает то, что уже здесь?

avatar
Brandt
6 апреля 2021 в 11:06
-1

простое решение

OP запросил «кусок равного размера». Я понимаю «равные» как «сбалансированные» размеры: мы ищем группы элементов примерно одинаковых размеров , если равные размеры невозможны (например, 23/5).

Входные данные здесь:

  • список элементов: input_list (например, список из 23 номеров)
  • количество групп для разделения этих элементов: n_groups (5, например)

Ввод:

input_list = list(range(23))
n_groups = 5

Группы смежных элементов:

approx_sizes = len(input_list)/n_groups 

groups_cont = [input_list[int(i*approx_sizes):int((i+1)*approx_sizes)] 
               for i in range(n_groups)]

Группы элементов "каждый N":

groups_leap = [input_list[i::n_groups] 
               for i in range(n_groups)]

Результаты

print(len(input_list))

print('Contiguous elements lists:')
print(groups_cont)

print('Leap every "N" items lists:')
print(groups_leap)

Будет выведено:

23

Contiguous elements lists:
[[0, 1, 2, 3], [4, 5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16, 17], [18, 19, 20, 21, 22]]

Leap every "N" items lists:
[[0, 5, 10, 15, 20], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18], [4, 9, 14, 19]]
avatar
Ариж Аль Адел
21 марта 2021 в 16:27
0

Хотя ответов много, у меня есть очень простой способ:


x = list(range(10, 75))
indices = x[0::10]
print("indices: ", indices)
xx = [x[i-10:i] for i in indices ]
print("x= ", x)
print ("xx= ",xx)

результат будет:

индексы: [10, 20, 30, 40, 50, 60, 70] x = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74]

xx ​​= [[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

avatar
Arty
24 сентября 2020 в 07:01
0

Я создал эти две причудливые однострочники, которые эффективны и ленивы, и ввод, и вывод являются повторяемыми, и они не зависят от какого-либо модуля:

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

Попробуйте онлайн!

chunk_iters = lambda it, n: ((e for i, g in enumerate(((f,), cit)) for j, e in zip(range((1, n - 1)[i]), g)) for cit in (iter(it),) for f in cit)

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

Попробуйте онлайн!

chunk_lists = lambda it, n: (l for l in ([],) for i, g in enumerate((it, ((),))) for e in g for l in (l[:len(l) % n] + [e][:1 - i],) if (len(l) % n == 0) != i)

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

Попробуйте онлайн!

def chunk_iters(it, n):
    cit = iter(it)
    def one_chunk(f):
        yield f
        for i, e in zip(range(n - 1), cit):
            yield e
    for f in cit:
        yield one_chunk(f)
avatar
Ryan Codrai
28 июля 2020 в 14:07
-1

Вот небольшой код, написанный на Python3, который делает то же самое, что и np.array_split.

list(map(list, map(functools.partial(filter, None), itertools.zip_longest(*iter(lambda: tuple(itertools.islice(a, n)), ())))))

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

avatar
Kandarp
29 июня 2020 в 17:54
3

Абстракция будет иметь вид

l = [1,2,3,4,5,6,7,8,9]
n = 3
outList = []
for i in range(n, len(l) + n, n):
    outList.append(l[i-n:i])

print(outList)

Будет напечатано:

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]

avatar
alani
5 июня 2020 в 13:34
1

Общий фрагмент для любой итерации, который дает пользователю выбор, как обрабатывать частичный фрагмент в конце.

Протестировано на Python 3.

chunker.py

from enum import Enum

class PartialChunkOptions(Enum):
    INCLUDE = 0
    EXCLUDE = 1
    PAD = 2
    ERROR = 3

class PartialChunkException(Exception):
    pass

def chunker(iterable, n, on_partial=PartialChunkOptions.INCLUDE, pad=None):
    """
    A chunker yielding n-element lists from an iterable, with various options
    about what to do about a partial chunk at the end.

    on_partial=PartialChunkOptions.INCLUDE (the default):
                     include the partial chunk as a short (<n) element list

    on_partial=PartialChunkOptions.EXCLUDE
                     do not include the partial chunk

    on_partial=PartialChunkOptions.PAD
                     pad to an n-element list 
                     (also pass pad=<pad_value>  default None)

    on_partial=PartialChunkOptions.ERROR
                     raise a RuntimeError if a partial chunk is encountered
    """

    on_partial = PartialChunkOptions(on_partial)        

    iterator = iter(iterable)
    while True:
        vals = []
        for i in range(n):
            try:
                vals.append(next(iterator))
            except StopIteration:
                if vals:
                    if on_partial == PartialChunkOptions.INCLUDE:
                        yield vals
                    elif on_partial == PartialChunkOptions.EXCLUDE:
                        pass
                    elif on_partial == PartialChunkOptions.PAD:
                        yield vals + [pad] * (n - len(vals))
                    elif on_partial == PartialChunkOptions.ERROR:
                        raise PartialChunkException
                    return
                return
        yield vals

test.py

import chunker

chunk_size = 3

for it in (range(100, 107),
          range(100, 109)):

    print("\nITERABLE TO CHUNK: {}".format(it))
    print("CHUNK SIZE: {}".format(chunk_size))

    for option in chunker.PartialChunkOptions.__members__.values():
        print("\noption {} used".format(option))
        try:
            for chunk in chunker.chunker(it, chunk_size, on_partial=option):
                print(chunk)
        except chunker.PartialChunkException:
            print("PartialChunkException was raised")
    print("")

вывод test.py


ITERABLE TO CHUNK: range(100, 107)
CHUNK SIZE: 3

option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106]

option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]

option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, None, None]

option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
PartialChunkException was raised


ITERABLE TO CHUNK: range(100, 109)
CHUNK SIZE: 3

option PartialChunkOptions.INCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.EXCLUDE used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.PAD used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

option PartialChunkOptions.ERROR used
[100, 101, 102]
[103, 104, 105]
[106, 107, 108]

avatar
Matheus Vinícius de Andrade
14 апреля 2020 в 16:18
-1
def main():
  print(chunkify([1,2,3,4,5,6],2))

def chunkify(list, n):
  chunks = []
  for i in range(0, len(list), n):
    chunks.append(list[i:i+n])
  return chunks

main()

Я думаю, что это просто и может дать вам кусок массива.

avatar
nirvana-msu
10 декабря 2019 в 11:59
20

С Выражениями присвоения в 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]]
avatar
Realfun
3 декабря 2019 в 18:34
-1

Старый подход, не требующий itertools, но работающий с произвольными генераторами:

def chunks(g, n):
  """divide a generator 'g' into small chunks
  Yields:
    a chunk that has 'n' or less items
  """
  n = max(1, n)
  buff = []
  for item in g:
    buff.append(item)
    if len(buff) == n:
      yield buff
      buff = []
  if buff:
    yield buff
avatar
J-L
15 июля 2019 в 15:27
0

Этот вопрос напоминает мне метод Raku (ранее Perl 6) .comb(n). Он разбивает строки на блоки размером n. (Есть еще кое-что, но я опущу детали.)

Достаточно просто реализовать аналогичную функцию в Python3 в виде лямбда-выражения:

comb = lambda s,n: (s[i:i+n] for i in range(0,len(s),n))

Тогда вы можете называть это так:

some_list = list(range(0, 20))  # creates a list of 20 elements
generator = comb(some_list, 4)  # creates a generator that will generate lists of 4 elements
for sublist in generator:
    print(sublist)  # prints a sublist of four elements, as it's generated

Конечно, вам не нужно назначать генератор переменной; вы можете просто перебрать его прямо так:

for sublist in comb(some_list, 4):
    print(sublist)  # prints a sublist of four elements, as it's generated

В качестве бонуса эта функция comb() также работает со строками:

list( comb('catdogant', 3) )  # returns ['cat', 'dog', 'ant']
avatar
Ravi Anand
9 июля 2019 в 14:04
5

пакет python pydash может быть хорошим выбором.

from pydash.arrays import chunk
ids = ['22', '89', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '1']
chunk_ids = chunk(ids,5)
print(chunk_ids)
# output: [['22', '89', '2', '3', '4'], ['5', '6', '7', '8', '9'], ['10', '11', '1']]

для дополнительной проверки список фрагментов pydash

darkman
27 марта 2020 в 14:47
0

аккуратный! и это то, что на самом деле находится под капотом pydash.arrays.chunk: chunks = int (ceil (len (array) / float (size))) return [array [i * size: (i + 1) * size] для я в диапазоне (фрагменты)]

avatar
ajaest
23 мая 2019 в 10:35
0

Если порядок вас не волнует:

> from itertools import groupby
> batch_no = 3
> data = 'abcdefgh'

> [
    [x[1] for x in x[1]] 
    for x in 
    groupby(
      sorted(
        (x[0] % batch_no, x[1]) 
        for x in 
        enumerate(data)
      ),
      key=lambda x: x[0]
    )
  ]

[['a', 'd', 'g'], ['b', 'e', 'h'], ['c', 'f']]

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

avatar
luckydonald
20 апреля 2019 в 18:28
-1

Версия с отложенной загрузкой

import pprint
pprint.pprint(list(chunks(range(10, 75), 10)))
[range(10, 20),
 range(20, 30),
 range(30, 40),
 range(40, 50),
 range(50, 60),
 range(60, 70),
 range(70, 75)]

Сопоставьте результат этой реализации с примером результата использования принятого ответа.

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

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

Если вы, однако, еще не знаете полный размер, вы можете вместо этого использовать этот код:

def chunks(iterable, size):
    """
    Yield successive chunks from iterable, being `size` long.

    https://coderhelper.com/a/55776536/3423324
    :param iterable: The object you want to split into pieces.
    :param size: The size each of the resulting pieces should have.
    """
    i = 0
    while True:
        sliced = iterable[i:i + size]
        if len(sliced) == 0:
            # to suppress stuff like `range(max, max)`.
            break
        # end if
        yield sliced
        if len(sliced) < size:
            # our slice is not the full length, so we must have passed the end of the iterator
            break
        # end if
        i += size  # so we start the next chunk at the right place.
    # end while
# end def

Это работает, потому что команда slice вернет меньше / без элементов, если вы передали конец итерации:

"abc"[0:2] == 'ab'
"abc"[2:4] == 'c'
"abc"[4:6] == ''

Теперь мы используем результат среза и вычисляем длину сгенерированного фрагмента. Если оно меньше ожидаемого, мы знаем, что можем завершить итерацию.

Таким образом, итератор не будет выполняться без доступа.

avatar
pylang
26 августа 2018 в 01:40
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

avatar
Arthur Sult
23 марта 2018 в 18:27
0

Мне не нравится идея разделения элементов по размеру блока, например скрипт может разделить от 101 до 3 частей как [50, 50, 1]. Для моих нужд мне нужно было пропорционально разделить и сохранить порядок. Сначала я написал свой собственный сценарий, который отлично работает и очень прост. Но позже я видел этот ответ, где сценарий лучше, чем мой, я рекомендую его. Вот мой сценарий:

def proportional_dividing(N, n):
    """
    N - length of array (bigger number)
    n - number of chunks (smaller number)
    output - arr, containing N numbers, diveded roundly to n chunks
    """
    arr = []
    if N == 0:
        return arr
    elif n == 0:
        arr.append(N)
        return arr
    r = N // n
    for i in range(n-1):
        arr.append(r)
    arr.append(N-r*(n-1))

    last_n = arr[-1]
    # last number always will be r <= last_n < 2*r
    # when last_n == r it's ok, but when last_n > r ...
    if last_n > r:
        # ... and if difference too big (bigger than 1), then
        if abs(r-last_n) > 1:
            #[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 7] # N=29, n=12
            # we need to give unnecessary numbers to first elements back
            diff = last_n - r
            for k in range(diff):
                arr[k] += 1
            arr[-1] = r
            # and we receive [3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2]
    return arr

def split_items(items, chunks):
    arr = proportional_dividing(len(items), chunks)
    splitted = []
    for chunk_size in arr:
        splitted.append(items[:chunk_size])
        items = items[chunk_size:]
    print(splitted)
    return splitted

items = [1,2,3,4,5,6,7,8,9,10,11]
chunks = 3
split_items(items, chunks)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm'], 3)
split_items(['a','b','c','d','e','f','g','h','i','g','k','l', 'm', 'n'], 3)
split_items(range(100), 4)
split_items(range(99), 4)
split_items(range(101), 4)

и вывод:

[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'g', 'k', 'l', 'm']]
[['a', 'b', 'c', 'd', 'e'], ['f', 'g', 'h', 'i', 'g'], ['k', 'l', 'm', 'n']]
[range(0, 25), range(25, 50), range(50, 75), range(75, 100)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 99)]
[range(0, 25), range(25, 50), range(50, 75), range(75, 101)]
avatar
Alex T
7 января 2018 в 08:58
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
avatar
George B
3 ноября 2017 в 12:38
4

Я не думаю, что видел эту опцию, поэтому просто добавлю еще одну :)):

def chunks(iterable, chunk_size):
  i = 0;
  while i < len(iterable):
    yield iterable[i:i+chunk_size]
    i += chunk_size
avatar
guyskk
30 октября 2017 в 08:13
3

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

def chunks(iterable, n):
    """Yield successive n-sized chunks from iterable."""
    values = []
    for i, item in enumerate(iterable, 1):
        values.append(item)
        if i % n == 0:
            yield values
            values = []
    if values:
        yield values
avatar
Andrey Cizov
6 июля 2017 в 22:24
2

Это работает в v2 / v3, является встроенным, основанным на генераторе и использует только стандартную библиотеку:

import itertools
def split_groups(iter_in, group_size):
    return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))
Andrey Cizov
24 февраля 2018 в 21:55
0

Просто выполните (list(x) for x in split_groups('abcdefghij', 4)), затем перебирайте их: в отличие от многих примеров здесь это будет работать с группами любого размера.

avatar
Анатолий Панин
17 апреля 2017 в 15:38
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]
>>> 
avatar
itub
8 марта 2017 в 17:03
4

Вот идея использования itertools.groupby:

def chunks(l, n):
    c = itertools.count()
    return (it for _, it in itertools.groupby(l, lambda x: next(c)//n))

Возвращает генератор генераторов. Если вам нужен список списков, просто замените последнюю строку на

    return [list(it) for _, it in itertools.groupby(l, lambda x: next(c)//n)]

Пример возврата списка списков:

>>> chunks('abcdefghij', 4)
[['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'j']]

(Так что да, это страдает от "проблемы с коротышкой", которая может быть или не быть проблемой в данной ситуации.)

Peter Gerdes
19 декабря 2017 в 10:19
0

Опять же, это не удается, если суб-итераторы не оцениваются по порядку в случае генератора. Пусть c = chunks ('abcdefghij', 4) (как генератор). Затем установите i0 = next (c); i1 = следующий (c); list (i1) // ТОЧНО; list (i0) // Ухххх

itub
19 декабря 2017 в 16:12
0

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

Yuri Feldman
13 мая 2019 в 18:18
0

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

avatar
Anonymous
27 января 2017 в 23:12
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 .

avatar
Peter Gerdes
11 января 2017 в 09:18
4

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

g = paged_iter(list(range(50)), 11))
i0 = next(g)
i1 = next(g)
list(i1)
list(i0)

Соответствующий вывод для последней команды:

 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

не

 []

Поскольку большинство решений на основе itertools здесь возвращаются. Это не просто обычное скучное ограничение на доступ к итераторам по порядку. Представьте, что потребитель пытается очистить неправильно введенные данные, которые меняют соответствующий порядок блоков из 5, т.е. данные выглядят как [B5, A5, D5, C5] и должны выглядеть как [A5, B5, C5, D5] (где A5 это всего лишь пять элементов, а не подсписок). Этот потребитель будет смотреть на заявленное поведение функции группирования и, не колеблясь, написать цикл вроде

i = 0
out = []
for it in paged_iter(data,5)
    if (i % 2 == 0):
         swapped = it
    else: 
         out += list(it)
         out += list(swapped)
    i = i + 1

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

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

В-третьих, подход itertools grouper работает, но рецепт полагается на внутреннее поведение функций zip_longest (или zip), которое не является частью их опубликованного поведения. В частности, функция группировщика работает только потому, что в zip_longest (i0 ... in) следующая функция всегда вызывается в порядке next (i0), next (i1), ... next (in) перед тем, как начать заново. Когда группировщик передает n копий одного и того же объекта-итератора, он полагается на это поведение.

Наконец, хотя приведенное ниже решение может быть улучшено, если вы сделаете критикуемое выше предположение о том, что суб-итераторы доступны по порядку и полностью просматриваются без этого предположения, одно ДОЛЖНО неявно (через цепочку вызовов) или явно (через deques или другую структуру данных ) где-то хранят элементы для каждого подитератора. Так что не тратьте время (как это сделал я), полагая, что это можно обойти с помощью какого-нибудь хитроумного трюка.

def paged_iter(iterat, n):
    itr = iter(iterat)
    deq = None
    try:
        while(True):
            deq = collections.deque(maxlen=n)
            for q in range(n):
                deq.append(next(itr))
            yield (i for i in deq)
    except StopIteration:
        yield (i for i in deq)
avatar
AlexG
20 ноября 2016 в 04:32
4

Вы можете использовать функцию numpy array_split, например. np.array_split(np.array(data), 20) для разделения на 20 частей примерно одинакового размера.

Чтобы убедиться, что чанки точно равны по размеру, используйте np.split.

avatar
vishes_shell
3 ноября 2016 в 19:10
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.

Peter Gerdes
19 декабря 2017 в 10:32
0

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

avatar
Claudiu
6 августа 2016 в 20:44
5

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

from __future__ import division  # not needed in Python 3
def n_even_chunks(l, n):
    """Yield n as even chunks as possible from l."""
    last = 0
    for i in range(1, n+1):
        cur = int(round(i * (len(l) / n)))
        yield l[last:cur]
        last = cur

Демонстрация:

>>> pprint.pprint(list(n_even_chunks(list(range(100)), 9)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
 [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
 [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
 [44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55],
 [56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66],
 [67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77],
 [78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88],
 [89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]]
>>> pprint.pprint(list(n_even_chunks(list(range(100)), 11)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8],
 [9, 10, 11, 12, 13, 14, 15, 16, 17],
 [18, 19, 20, 21, 22, 23, 24, 25, 26],
 [27, 28, 29, 30, 31, 32, 33, 34, 35],
 [36, 37, 38, 39, 40, 41, 42, 43, 44],
 [45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
 [55, 56, 57, 58, 59, 60, 61, 62, 63],
 [64, 65, 66, 67, 68, 69, 70, 71, 72],
 [73, 74, 75, 76, 77, 78, 79, 80, 81],
 [82, 83, 84, 85, 86, 87, 88, 89, 90],
 [91, 92, 93, 94, 95, 96, 97, 98, 99]]

Сравните с ответом chunks, получившим наибольшее количество голосов:

>>> pprint.pprint(list(chunks(list(range(100)), 100//9)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21],
 [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32],
 [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
 [44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54],
 [55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65],
 [66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76],
 [77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87],
 [88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98],
 [99]]
>>> pprint.pprint(list(chunks(list(range(100)), 100//11)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8],
 [9, 10, 11, 12, 13, 14, 15, 16, 17],
 [18, 19, 20, 21, 22, 23, 24, 25, 26],
 [27, 28, 29, 30, 31, 32, 33, 34, 35],
 [36, 37, 38, 39, 40, 41, 42, 43, 44],
 [45, 46, 47, 48, 49, 50, 51, 52, 53],
 [54, 55, 56, 57, 58, 59, 60, 61, 62],
 [63, 64, 65, 66, 67, 68, 69, 70, 71],
 [72, 73, 74, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89],
 [90, 91, 92, 93, 94, 95, 96, 97, 98],
 [99]]
DragonTux
5 сентября 2016 в 10:45
1

Это решение, похоже, не работает в некоторых ситуациях: - когда n> len (l) - для l = [0,1,2,3,4] и n = 3 оно возвращает [[0], [1], [2] ] вместо [[0,1], [2,3], [4]]

Claudiu
5 сентября 2016 в 17:24
0

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

DragonTux
9 сентября 2016 в 15:18
1

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

avatar
Riaz Rizvi
16 декабря 2015 в 21:42
21
[AA[i:i+SS] for i in range(len(AA))[::SS]]

Где AA - массив, SS - размер блока. Например:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3
avatar
Julien Palard
4 ноября 2015 в 09:12
3

На данный момент, я думаю, нам нужна обязательная анонимно-рекурсивная функция.

Y = lambda f: (lambda x: x(x))(lambda y: f(lambda *args: y(y)(*args)))
chunks = Y(lambda f: lambda n: [n[0][:n[1]]] + f((n[0][n[1]:], n[1])) if len(n[0]) > 0 else [])
Sanjay Poongunran
24 июля 2018 в 19:47
0

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

Julien Palard
24 июля 2018 в 22:29
0

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

Ibolit
16 октября 2018 в 07:44
5

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

avatar
mazieres
3 ноября 2015 в 23:10
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
avatar
Evan Zamir
16 октября 2015 в 22:09
1

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

def pop_n_elems_from_generator(g, n):
    elems = []
    try:
        for idx in xrange(0, n):
            elems.append(g.next())
        return elems
    except StopIteration:
        return elems
avatar
Mikhail Lyundin
18 сентября 2015 в 17:54
2

Я пришел к следующему решению без создания объекта временного списка, который должен работать с любым повторяемым объектом. Обратите внимание, что эта версия для Python 2.x:

def chunked(iterable, size):
    stop = []
    it = iter(iterable)
    def _next_chunk():
        try:
            for _ in xrange(size):
                yield next(it)
        except StopIteration:
            stop.append(True)
            return

    while not stop:
        yield _next_chunk()

for it in chunked(xrange(16), 4):
   print list(it)

Вывод:

[0, 1, 2, 3]
[4, 5, 6, 7]
[8, 9, 10, 11]
[12, 13, 14, 15] 
[]

Как видите, если len (iterable)% size == 0, то у нас есть дополнительный пустой объект итератора. Но не думаю, что это большая проблема.

Peter Gerdes
19 декабря 2017 в 10:03
0

Как вы думаете, что должен дать следующий код? я = 0

Peter Gerdes
19 декабря 2017 в 10:10
0

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

avatar
AdvilUser
15 июля 2015 в 23:27
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 )]
Zulu
16 июля 2015 в 00:06
0

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

AdvilUser
29 июля 2015 в 00:29
0

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

avatar
Art B
2 июля 2015 в 07:32
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]]
avatar
Flo
17 апреля 2015 в 18:48
3

В приведенном выше ответе (от koffein) есть небольшая проблема: список всегда разбивается на равное количество разделов, а не на равное количество элементов на раздел. Это моя версия. «// chs + 1» учитывает, что количество элементов не может быть разделено точно на размер раздела, поэтому последний раздел будет заполнен только частично.

# Given 'l' is your list

chs = 12 # Your chunksize
partitioned = [ l[i*chs:(i*chs)+chs] for i in range((len(l) // chs)+1) ]
Arthur Tacca
3 декабря 2018 в 15:34
0

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

avatar
Noich
12 марта 2015 в 12:36
43

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

Tom Smith
18 мая 2015 в 14:21
0

это хорошо, если ваш список и фрагменты короткие, но как вы могли бы адаптировать это, чтобы разделить свой список на куски по 1000? вы не собираетесь кодировать почтовый индекс (i, i, i, i, i, i, i, i, i, i ..... i = 1000)

Wilson F
28 июня 2015 в 04:52
11

zip(i, i, i, ... i) с аргументами "chunk_size" для zip () можно записать как zip(*[i]*chunk_size) Вопрос о том, хорошая это идея или нет, конечно, спорный.

Aaron Hall♦
8 июля 2016 в 03:37
1

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

Ioannis Filippidis
21 июня 2017 в 13:28
0

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

Ioannis Filippidis
21 июня 2017 в 13:34
0

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

avatar
Ranaivo
28 февраля 2015 в 20:05
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
D Adams
14 сентября 2016 в 00:36
0

(2016, 12 сентября) Этот ответ не зависит от языка и его легче всего читать.

avatar
Saksham Varma
27 февраля 2015 в 02:33
4

Использовать понимание списка:

l = [1,2,3,4,5,6,7,8,9,10,11,12]
k = 5 #chunk size
print [tuple(l[x:y]) for (x, y) in [(x, x+k) for x in range(0, len(l), k)]]
avatar
Be Wake Pandey
9 декабря 2014 в 03:54
4

позволяя r быть размером блока, а L быть начальным списком, вы можете сделать это.

chunkL = [ [i for i in L[r*k:r*(k+1)] ] for k in range(len(L)/r)] 
avatar
CPBL
3 сентября 2014 в 17:43
2

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

idij
27 ноября 2014 в 13:11
0

кажется, дает несколько пустых фрагментов (len = 26, 10) или последний очень несбалансированный фрагмент (len = 26, 11).

avatar
rectangletangle
3 марта 2014 в 04:30
3

Специально для этой цели я написал небольшую библиотеку, доступную здесь. Функция библиотеки 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), ...]
avatar
senderle
26 февраля 2014 в 15:02
219

Я удивлен, что никто не подумал об использовании 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)]
ThomasH
15 сентября 2016 в 19:58
9

Замечательно, ваша простая версия - моя любимая. Другие тоже придумали базовое выражение islice(it, size) и встроили его (как это сделал я) в конструкцию цикла. Только вы подумали о версии iter() с двумя аргументами (я совершенно не знал о ней), которая делает ее супер-элегантной (и, вероятно, наиболее эффективной с точки зрения производительности). Я понятия не имел, что первый аргумент iter изменится на функцию с 0 аргументами, когда будет задан часовой. Вы возвращаете итератор (pot. Infinite) блоков, можете использовать итератор (pot. Infinite) в качестве входных данных, не иметь len() и срезов массива. Потрясающий!

avatar
Aaron Hall
13 февраля 2014 в 23:07
47

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

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

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

senderle
26 февраля 2014 в 15:00
0

Вы говорите, что ни одно из вышеперечисленного не обеспечивает куски одинакового размера. Но этот делает, как и этот.

Aaron Hall♦
26 февраля 2014 в 16:07
1

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

Alfe
2 августа 2014 в 23:14
6

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

Aaron Hall♦
3 сентября 2014 в 17:10
0

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

CPBL
3 сентября 2014 в 17:47
1

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

Aaron Hall♦
3 сентября 2014 в 18:06
0

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

Steve Yeago
23 сентября 2016 в 22:04
0

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

qbolec
14 ноября 2016 в 10:18
0

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

wim
19 августа 2020 в 17:09
0

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

Aaron Hall♦
19 августа 2020 в 17:49
0

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

wim
19 августа 2020 в 18:27
0

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

avatar
koffein
26 ноября 2013 в 21:58
-2

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

>>> 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]]
avatar
zach
20 ноября 2013 в 20:55
21

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

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]
avatar
nikipore
9 октября 2013 в 06:17
17

Мне очень нравится версия документа Python, предложенная Цотом и Дж. Ф. Себастьяном, но у него есть два недостатка:

  • не очень явный
  • Обычно мне не нужно значение заполнения в последнем чанке

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

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

ОБНОВЛЕНИЕ: Версия ленивых фрагментов:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))
avatar
balki
13 сентября 2013 в 19:11
2
  • Работает с любыми итерациями
  • Внутренние данные - это объект-генератор (не список)
  • Один лайнер
In [259]: get_in_chunks = lambda itr,n: ( (v for _,v in g) for _,g in itertools.groupby(enumerate(itr),lambda (ind,_): ind/n))

In [260]: list(list(x) for x in get_in_chunks(range(30),7))
Out[260]:
[[0, 1, 2, 3, 4, 5, 6],
 [7, 8, 9, 10, 11, 12, 13],
 [14, 15, 16, 17, 18, 19, 20],
 [21, 22, 23, 24, 25, 26, 27],
 [28, 29]]
Peter Gerdes
19 декабря 2017 в 10:30
0

g = get_in_chunks (диапазон (30), 7); i0 = следующий (g); i1 = следующий (g); список (i1); список (i0); Последняя оценка пуста. Скрытое требование о доступе ко всем подспискам по порядку кажется мне здесь действительно плохим, потому что цель таких утилит часто состоит в том, чтобы перетасовать данные различными способами.

avatar
Moj
5 июня 2013 в 08:54
315

Я знаю, что это старовато, но никто еще не упомянул numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]
FizxMike
9 сентября 2015 в 03:03
18

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

avatar
Uday Kumar
26 мая 2013 в 15:18
-2

с использованием списковых представлений python

[range(t,t+10) for t in range(1,1000,10)]

[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
 [21, 22, 23, 24, 25, 26, 27, 28, 29, 30],....
 ....[981, 982, 983, 984, 985, 986, 987, 988, 989, 990],
 [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]

посетите эту ссылку, чтобы узнать о составлении списка

Alfe
14 августа 2013 в 23:18
2

Как бы вы применили свой подход к существующему списку, который приходит на вход?

flexd
2 августа 2014 в 21:34
1

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

Alfe
2 августа 2014 в 23:17
1

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

avatar
macm
18 февраля 2013 в 13:31
7

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

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

Alfe
14 августа 2013 в 23:17
4

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

Aivar Paalberg
27 сентября 2020 в 09:57
0

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

avatar
user1628890
27 августа 2012 в 22:58
4

Я понимаю, что это старый вопрос (наткнулся на него в 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 ...
avatar
robert king
13 февраля 2012 в 04:50
6
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]]'
Peter Gerdes
19 декабря 2017 в 10:25
1

Хотя это может показаться не таким коротким или красивым, как многие ответы на основе itertools, этот на самом деле работает, если вы хотите распечатать второй подсписок перед доступом к первому, т.е. вы можете установить i0 = next (g2); i1 = следующий (g2); и используйте i1 перед использованием i0, и он не сломается !!

avatar
schwater
3 мая 2011 в 16:27
6

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

например:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s
Georgy
15 мая 2019 в 15:15
0

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

avatar
ninjagecko
19 апреля 2011 в 05:27
22

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

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

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

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

sherbang
3 июля 2012 в 19:28
13

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

avatar
lebenf
12 июля 2010 в 07:58
65

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

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)
Alfe
14 августа 2013 в 23:02
23

Не дублируй переменную как арабское число. В некоторых шрифтах 1 и l неотличимы. Как и 0 и O. А иногда даже I и 1.

Jerry B
5 октября 2013 в 08:14
21

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

Chris Koston
26 ноября 2013 в 19:45
18

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

avatar
Tomasz Wysocki
26 июня 2010 в 19:10
58
def chunk(input, size):
    return map(None, *([iter(input)] * size))
avatar
Mars
16 февраля 2010 в 05:49
8

Без вызова 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))
Thomas Wouters
30 мая 2011 в 10:03
16

Нет причин избегать использования len() в больших списках; это операция с постоянным временем.

avatar
oremj
17 ноября 2009 в 20:17
604

Если вам нужно что-то очень простое:

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

J-P
20 августа 2011 в 13:54
6

Или (если мы делаем разные представления этой конкретной функции) вы можете определить лямбда-функцию с помощью: lambda x, y: [x [i: i + y] для i в диапазоне (0, len (x), y) ]. Мне нравится этот метод понимания списков!

keepAlive
12 августа 2021 в 15:24
0

Использование короткого замыкания len(l) or 1 для работы с пустыми списками.

avatar
hcvst
3 ноября 2009 в 16:45
4
>>> def f(x, n, acc=[]): return f(x[n:], n, acc+[(x[:n])]) if x else acc
>>> f("Hallo Welt", 3)
['Hal', 'lo ', 'Wel', 't']
>>> 

Если вы в скобки - я взял книгу по Erlang :)

avatar
J.T. Hurley
26 ноября 2008 в 07:24
3
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
avatar
Corey Goldberg
24 ноября 2008 в 16:56
9
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
avatar
tzot
23 ноября 2008 в 15:48
320

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

from itertools import izip, chain, repeat

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

Текущая версия, предложенная Дж. Ф. Себастьяном:

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

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

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

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

Michael Dillon
30 января 2012 в 23:47
19

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

wim
12 апреля 2013 в 05:40
99

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

tzot
12 апреля 2013 в 11:36
16

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

avatar
slav0nic
23 ноября 2008 в 12:51
8

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

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]]
S.Lott
23 ноября 2008 в 13:45
37

Пожалуйста, используйте def chunk вместо chunk = lambda. Работает так же. Одна линия. Те же функции. НАМНОГО легче читать и понимать n00bz.

Janus Troelsen
11 мая 2012 в 21:10
4

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

Terry Jan Reedy
27 июня 2012 в 04:20
18

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

avatar
Markus Jarderot
23 ноября 2008 в 12:41
104

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

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]]
avatar
atzz
23 ноября 2008 в 12:40
42

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

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

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