Разбиение списка на N частей примерно равной длины

150

Каков наилучший способ разделить список на примерно равные части? Например, если список состоит из 7 элементов и разделен на 2 части, мы хотим получить 3 элемента в одной части, а другая должна содержать 4 элемента.

Я ищу что-то подобное even_split(L, n), разбивается Lна nчасти.

def chunks(L, n):
    """ Yield successive n-sized chunks from L.
    """
    for i in range(0, len(L), n):
        yield L[i:i+n]

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

Wim
источник

Ответы:

64

Этот код нарушен из-за ошибок округления. Не используйте его !!!

assert len(chunkIt([1,2,3], 10)) == 10  # fails

Вот тот, который может работать:

def chunkIt(seq, num):
    avg = len(seq) / float(num)
    out = []
    last = 0.0

    while last < len(seq):
        out.append(seq[int(last):int(last + avg)])
        last += avg

    return out

Тестирование:

>>> chunkIt(range(10), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]
>>> chunkIt(range(11), 3)
[[0, 1, 2], [3, 4, 5, 6], [7, 8, 9, 10]]
>>> chunkIt(range(12), 3)
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]
Макс Шавабке
источник
9
Ваш пример не сработает >>> chunkIt(range(8), 6)=>[[0], [1], [2, 3], [4], [5], [6], [7]]
nopper
1
@nopper, я добавил условное «if num == 1:» для обработки этого крайнего случая.
paulie4
24
Новые посетители: пожалуйста, не используйте и не ставьте этот код голосом , он не работает. например, chunkIt(range(10), 9)должен вернуть 9 частей, но это не так.
Вим
3
Эта ветка комментариев действительно сбивает с толку, так как ответ был отредактирован несколько раз. Это хороший ответ? Не очень хороший ответ?
Конхоэсия
6
@conchoecia Не очень хороший ответ, продолжайте прокручивать вниз. Это было только что отредактировано один раз, и это было только тривиальное редактирование (отступ 2 пробела изменен на 4). К сожалению, ОП «user248237dfsf» не было на сайте более 3 лет, поэтому надежды на изменение принятого ответа практически нет.
Вим
183

Вы можете написать это довольно просто как генератор списков:

def split(a, n):
    k, m = divmod(len(a), n)
    return (a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))

Пример:

>>> list(split(range(11), 3))
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10]]
tixxit
источник
Вставьте n = min(n, len(a)) # don't create empty bucketsв строку 1, чтобы избежать создания пустых сегментов в таких сценариях, как list(split(range(X, Y)))гдеX < Y
abanana
Видя, что я не могу редактировать свой комментарий - я должен добавить, что моя предыдущая поправка могла бы вызвать ошибку деления на ноль, если список пуст, поэтому его необходимо либо контролировать извне, либо добавлять в решение.
Абанана
4
Из N ответов на SO, это единственный, который прошел все мои тесты. GJ!
Авишайп
2
stackoverflow.com/a/37414115/210971 использует тот же метод, но работает также для пустого списка и счетчика 0 разделений.
LookAheadAtYourTypes
Прекрасный! Кроме того, n можно заставить работать как batch_size, поменяв местами k и n в выражении return :)
haraprasadj
162

Это смысл для numpy.array_split*:

>>> import numpy as np
>>> print(*np.array_split(range(10), 3))
[0 1 2 3] [4 5 6] [7 8 9]
>>> print(*np.array_split(range(10), 4))
[0 1 2] [3 4 5] [6 7] [8 9]
>>> print(*np.array_split(range(10), 5))
[0 1] [2 3] [4 5] [6 7] [8 9]

* кредит Зеро Пирей в комнате 6

Wim
источник
1
Что *в printтечение?
yuqli
2
Привет @yuqli, он преобразует список чего-то в отдельные аргументы функции. попробуйте print(L)и `напечатайте (* L). Также см. Stackoverflow.com/a/36908/2184122 или поиск "использование звездочкой Python".
Роберт Лугг
121

Пока вы не хотите ничего глупого, как непрерывные куски:

>>> def chunkify(lst,n):
...     return [lst[i::n] for i in xrange(n)]
... 
>>> chunkify(range(13), 3)
[[0, 3, 6, 9, 12], [1, 4, 7, 10], [2, 5, 8, 11]]
работа
источник
14
Я бы не сказал, что непрерывные куски глупы. Возможно, вы хотите сохранить чанки отсортированными (например, chunk [0] <chunk [1]).
tixxit
1
Я пошутил. Но если вам действительно все равно, этот способ понимания списков хорош и лаконичен.
работа
3
Это подписка с шагом n
smci
8
отправив этот вывод в 'zip', вы получите упорядоченный список: zip(*chunkify(range(13), 3))результаты[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]
gens
2
Это решение прекрасно работает, пока вам не нужно, чтобы список оставался таким же.
s7anley
18

Изменение кода для получения nкусков, а не кусков n:

def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    newn = int(len(l) / n)
    for i in xrange(0, n-1):
        yield l[i*newn:i*newn+newn]
    yield l[n*newn-newn:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

который дает:

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

Это назначит дополнительные элементы для последней группы, которая не является идеальной, но вполне соответствует вашей спецификации «примерно N равных частей» :-) Под этим я подразумеваю, что 56 элементов будут лучше, чем (19,19,18), тогда как это дает (18,18,20).

Вы можете получить более сбалансированный вывод с помощью следующего кода:

#!/usr/bin/python
def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    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:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

какие выводы:

[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]
paxdiablo
источник
это дает мне странный результат. для p в чанках (диапазон (54), 3): print len ​​(p) возвращает 18, 18, 51 ...
Исправлено, что это был окончательный доход.
paxdiablo
см. также решение по ссылке
Якоб Крукер
Это самый полезный ответ для практических соображений. Спасибо!
mVChr
Когда я использую это, делая for x in chunks(mylist,num): print x, я получаю нужные куски, но между ними я получаю пустой список. Есть идеи почему? То есть я получаю много [], по одному за каждый кусок.
синаптик
12

Если вы разделите nэлементы примерно на kкуски, вы можете сделать n % kблок на 1 элемент больше, чем другие куски, чтобы распределить дополнительные элементы.

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

[(n // k) + (1 if i < (n % k) else 0) for i in range(k)]

Пример: n=11, k=3результаты в[4, 4, 3]

Затем вы можете легко рассчитать начальные индексы для кусков:

[i * (n // k) + min(i, n % k) for i in range(k)]

Пример: n=11, k=3результаты в[0, 4, 8]

Используя i+1й кусок в качестве границы мы получаем , что iй кусок списка lс Len nявляется

l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)]

В качестве последнего шага создайте список из всех кусков, используя понимание списка:

[l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)] for i in range(k)]

Пример: n=11, k=3, l=range(n)результаты в[range(0, 4), range(4, 8), range(8, 11)]

MaPePeR
источник
6

Это сделает разделение одним выражением:

>>> myList = range(18)
>>> parts = 5
>>> [myList[(i*len(myList))//parts:((i+1)*len(myList))//parts] for i in range(parts)]
[[0, 1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13], [14, 15, 16, 17]]

Список в этом примере имеет размер 18 и разделен на 5 частей. Размер деталей отличается не более чем одним элементом.

bitagoras
источник
4

Вот тот, который добавляет, Noneчтобы сделать списки равной длины

>>> from itertools import izip_longest
>>> def chunks(l, n):
    """ Yield n successive chunks from l. Pads extra spaces with None
    """
    return list(zip(*izip_longest(*[iter(l)]*n)))

>>> l=range(54)

>>> chunks(l,3)
[(0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51), (1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49, 52), (2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53)]

>>> chunks(l,4)
[(0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52), (1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53), (2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, None), (3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, None)]

>>> chunks(l,5)
[(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50), (1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51), (2, 7, 12, 17, 22, 27, 32, 37, 42, 47, 52), (3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53), (4, 9, 14, 19, 24, 29, 34, 39, 44, 49, None)]
Джон Ла Рой
источник
4

Вот мое решение:

def chunks(l, amount):
    if amount < 1:
        raise ValueError('amount must be positive integer')
    chunk_len = len(l) // amount
    leap_parts = len(l) % amount
    remainder = amount // 2  # make it symmetrical
    i = 0
    while i < len(l):
        remainder += leap_parts
        end_index = i + chunk_len
        if remainder >= amount:
            remainder -= amount
            end_index += 1
        yield l[i:end_index]
        i = end_index

Производит

    >>> list(chunks([1, 2, 3, 4, 5, 6, 7], 3))
    [[1, 2], [3, 4, 5], [6, 7]]
leotrubach
источник
4

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

Я также включил некоторый код для тестирования ragged_chunksфункции.

''' Split a list into "ragged" chunks

    The size of each chunk is either the floor or ceiling of len(seq) / chunks

    chunks can be > len(seq), in which case there will be empty chunks

    Written by PM 2Ring 2017.03.30
'''

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(1, chunks + 1):
        stop = i * size // chunks
        yield seq[start:stop]
        start = stop

# test

def test_ragged_chunks(maxsize):
    for size in range(0, maxsize):
        seq = list(range(size))
        for chunks in range(1, size + 1):
            minwidth = size // chunks
            #ceiling division
            maxwidth = -(-size // chunks)
            a = list(ragged_chunks(seq, chunks))
            sizes = [len(u) for u in a]
            deltas = all(minwidth <= u <= maxwidth for u in sizes)
            assert all((sum(a, []) == seq, sum(sizes) == size, deltas))
    return True

if test_ragged_chunks(100):
    print('ok')

Мы можем сделать это немного более эффективным, экспортируя умножение в rangeвызов, но я думаю, что предыдущая версия более читабельна (и DRYer).

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(size, size * chunks + 1, size):
        stop = i // chunks
        yield seq[start:stop]
        start = stop
PM 2Ring
источник
3

Посмотрите на numpy.split :

>>> a = numpy.array([1,2,3,4])
>>> numpy.split(a, 2)
[array([1, 2]), array([3, 4])]
dalloliogm
источник
5
И numpy.array_split () еще более адекватен, потому что он грубо разделяется.
Ярив
11
Это не работает, если размер массива не делится на количество разбиений.
Дан
1
Это неправильный ответ, ваше решение возвращает список ndarrays, а не список списков
Chłop Z Lasu
3

Реализация с использованием метода numpy.linspace.

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

Пример :

import numpy as np   
a=np.arange(10)
print "Input array:",a 
parts=3
i=np.linspace(np.min(a),np.max(a)+1,parts+1)
i=np.array(i,dtype='uint16') # Indices should be floats
split_arr=[]
for ind in range(i.size-1):
    split_arr.append(a[i[ind]:i[ind+1]]
print "Array split in to %d parts : "%(parts),split_arr

Дает:

Input array: [0 1 2 3 4 5 6 7 8 9]
Array split in to 3 parts :  [array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8, 9])]
amit12690
источник
3

Мое решение, простое для понимания

def split_list(lst, n):
    splitted = []
    for i in reversed(range(1, n + 1)):
        split_point = len(lst)//i
        splitted.append(lst[:split_point])
        lst = lst[split_point:]
    return splitted

И самый короткий однострочник на этой странице (написано моей девушкой)

def split(l, n):
    return [l[int(i*len(l)/n):int((i+1)*len(l)/n-1)] for i in range(n)]
Хлоп З Ласу
источник
К вашему сведению: ваша строка не работает, дает неверные результаты. Другой работает прекрасно.
Пауло Фрейтас
2

Используя понимание списка:

def divide_list_to_chunks(list_, n):
    return [list_[start::n] for start in range(n)]
liscju
источник
Это не решает проблему создания всех кусков.
SuperBiasedMan
0

Другим способом было бы что-то вроде этого, идея здесь в том, чтобы использовать групер, но избавиться от него None. В этом случае у нас будут все 'small_parts', сформированные из элементов в первой части списка, и 'large_parts' из более поздней части списка. Длина «больших частей» равна len (small_parts) + 1. Нам нужно рассмотреть x как две разные части.

from itertools import izip_longest

import numpy as np

def grouper(n, iterable, fillvalue=None): # This is grouper from itertools
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

def another_chunk(x,num):
    extra_ele = len(x)%num #gives number of parts that will have an extra element 
    small_part = int(np.floor(len(x)/num)) #gives number of elements in a small part

    new_x = list(grouper(small_part,x[:small_part*(num-extra_ele)]))
    new_x.extend(list(grouper(small_part+1,x[small_part*(num-extra_ele):])))

    return new_x

То, как я его настроил, возвращает список кортежей:

>>> x = range(14)
>>> another_chunk(x,3)
[(0, 1, 2, 3), (4, 5, 6, 7, 8), (9, 10, 11, 12, 13)]
>>> another_chunk(x,4)
[(0, 1, 2), (3, 4, 5), (6, 7, 8, 9), (10, 11, 12, 13)]
>>> another_chunk(x,5)
[(0, 1), (2, 3, 4), (5, 6, 7), (8, 9, 10), (11, 12, 13)]
>>> 
Akavall
источник
0

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

def chunks(l, k):
  """ Yield k successive chunks from l."""
  if k < 1:
    yield []
    raise StopIteration
  n = len(l)
  avg = n/k
  remainders = n % k
  start, end = 0, avg
  while start < n:
    if remainders > 0:
      end = end + 1
      remainders = remainders - 1
    yield l[start:end]
    start, end = end, end+avg

Например, сгенерируйте 4 фрагмента из списка из 14 элементов:

>>> list(chunks(range(14), 4))
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10], [11, 12, 13]]
>>> map(len, list(chunks(range(14), 4)))
[4, 4, 3, 3]
jreyes
источник
0

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

def chunkify(lst,n):
    [ lst[i::n] for i in xrange(n if n < len(lst) else len(lst)) ]

если n (количество чанков) равно 7, а lst (список для разделения) равен [1, 2, 3], чанки будут [[0], [1], [2]] вместо [[0], [1 ], [2], [], [], [], []]

Илья Тувщев
источник
0

Вы также можете использовать:

split=lambda x,n: x if not x else [x[:n]]+[split([] if not -(len(x)-n) else x[-(len(x)-n):],n)][0]

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

[[1, 2], [3, 4], [5, 6], [7, 8], [9]]
Карлос дель Охо
источник
0
def evenly(l, n):
    len_ = len(l)
    split_size = len_ // n
    split_size = n if not split_size else split_size
    offsets = [i for i in range(0, len_, split_size)]
    return [l[offset:offset + split_size] for offset in offsets]

Пример:

l = [a for a in range(97)] должен состоять из 10 частей, каждая из которых имеет 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]]
emremrah
источник
0

Допустим, вы хотите разделить список [1, 2, 3, 4, 5, 6, 7, 8] на 3 списка элементов

как [[1,2,3], [4, 5, 6], [7, 8]] , где, если последние оставшиеся элементы меньше 3, они группируются вместе.

my_list = [1, 2, 3, 4, 5, 6, 7, 8]
my_list2 = [my_list[i:i+3] for i in range(0, len(my_list), 3)]
print(my_list2)

Выход: [[1,2,3], [4, 5, 6], [7, 8]]

Там, где длина одной части равна 3. Замените 3 собственным размером куска.

Али Саджад
источник
0

1>

import numpy as np

data # your array

total_length = len(data)
separate = 10
sub_array_size = total_length // separate
safe_separate = sub_array_size * separate

splited_lists = np.split(np.array(data[:safe_separate]), separate)
splited_lists[separate - 1] = np.concatenate(splited_lists[separate - 1], 
np.array(data[safe_separate:total_length]))

splited_lists # your output

2>

splited_lists = np.array_split(np.array(data), separate)
Bbake Waikhom
источник
0
def chunk_array(array : List, n: int) -> List[List]:
    chunk_size = len(array) // n 
    chunks = []
    i = 0
    while i < len(array):
        # if less than chunk_size left add the remainder to last element
        if len(array) - (i + chunk_size + 1) < 0:
            chunks[-1].append(*array[i:i + chunk_size])
            break
        else:
            chunks.append(array[i:i + chunk_size])
            i += chunk_size
    return chunks

вот моя версия (по мотивам Макса)

Солал Бодуан
источник
-1

Округление linspace и использование его в качестве индекса - более простое решение, чем то, что предлагает amit12690.

function chunks=chunkit(array,num)

index = round(linspace(0,size(array,2),num+1));

chunks = cell(1,num);

for x = 1:num
chunks{x} = array(:,index(x)+1:index(x+1));
end
end
galliwuzz
источник
-1
#!/usr/bin/python


first_names = ['Steve', 'Jane', 'Sara', 'Mary','Jack','Bob', 'Bily', 'Boni', 'Chris','Sori', 'Will', 'Won','Li']

def chunks(l, n):
for i in range(0, len(l), n):
    # Create an index range for l of n items:
    yield l[i:i+n]

result = list(chunks(first_names, 5))
print result

Выбрал по этой ссылке , и это то, что мне помогло. У меня был заранее определенный список.

swateek
источник
-1

скажем, вы хотите разделить на 5 частей:

p1, p2, p3, p4, p5 = np.split(df, 5)
Данил
источник
4
Это не дает ответа на вопрос, например, как бы вы написали его, если не знаете заранее, что хотите разбить его на пять частей. Кроме того, вы (я догадываюсь) предполагаете, что NumPy и, возможно, Pandas DataFrame. ОП спрашивает об общем списке.
NickD
-1

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

def chunk_ports(port_start, port_end, portions):
    if port_end < port_start:
        return None

    total = port_end - port_start + 1

    fractions = int(math.floor(float(total) / portions))

    results = []

    # No enough to chuck.
    if fractions < 1:
        return None

    # Reverse, so any additional items would be in the first range.
    _e = port_end
    for i in range(portions, 0, -1):
        print "i", i

        if i == 1:
            _s = port_start
        else:
            _s = _e - fractions + 1

        results.append((_s, _e))

        _e = _s - 1

    results.reverse()

    return results

div_ports (1, 10, 9) вернется

[(1, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9), (10, 10)]
Cipher.Chen
источник
-1

этот код работает для меня (Python3-совместимый):

def chunkify(tab, num):
    return [tab[i*num: i*num+num] for i in range(len(tab)//num+(1 if len(tab)%num else 0))]

пример (для типа bytearray , но он работает и для списка s):

b = bytearray(b'\x01\x02\x03\x04\x05\x06\x07\x08')
>>> chunkify(b,3)
[bytearray(b'\x01\x02\x03'), bytearray(b'\x04\x05\x06'), bytearray(b'\x07\x08')]
>>> chunkify(b,4)
[bytearray(b'\x01\x02\x03\x04'), bytearray(b'\x05\x06\x07\x08')]
grafi71
источник
-1

Этот предоставляет куски длины <= n,> = 0

Защита

 chunkify(lst, n):
    num_chunks = int(math.ceil(len(lst) / float(n))) if n < len(lst) else 1
    return [lst[n*i:n*(i+1)] for i in range(num_chunks)]

например

>>> chunkify(range(11), 3)
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
>>> chunkify(range(11), 8)
[[0, 1, 2, 3, 4, 5, 6, 7], [8, 9, 10]]
Энтони Мэннинг-Франклин
источник
-1

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

import math

def chunkIt(seq, num):
    seqLen = len(seq)
    total_chunks = math.ceil(seqLen / num)
    items_per_chunk = num
    out = []
    last = 0

    while last < seqLen:
        out.append(seq[last:(last + items_per_chunk)])
        last += items_per_chunk

    return out
Анджело Полотто
источник