Розбиття списку на N частин приблизно однакової довжини


149

Який найкращий спосіб розділити список на приблизно рівні частини? Наприклад, якщо список містить 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 шматки. Я міг би просто перенести (повторити це і взяти перший елемент кожного стовпця, назвати цю частину першу, потім взяти другу та помістити її у другу частину тощо), але це руйнує впорядкованість елементів.

Відповіді:


65

Цей код порушений через помилки округлення. Не використовуйте його !!!

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
Цей потік коментаря дійсно заплутаний, оскільки відповідь було відредаговано кілька разів. Це хороша відповідь? Недобра відповідь?
conchoecia

6
@conchoecia Недобра відповідь, продовжуйте прокручувати вниз. Це було щойно відредаговано один раз, і це було лише тривіальне редагування (2 пробіли в космосі змінено на 4). На жаль, ОП "user248237dfsf" не було помічено на сайті більше 3 років, тому мало сподівань змінити прийняту відповідь.
Вім

182

Ви можете записати його досить просто як генератор списку:

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

Вставте n = min(n, len(a)) # don't create empty bucketsв рядок 1, щоб уникнути створення порожніх відра в таких сценаріях, як list(split(range(X, Y)))деX < Y
abanana

Вважаючи, що я не можу редагувати свій коментар - я повинен додати, що моя попередня поправка могла призвести до поділу на нульову помилку, якщо список порожній, тому його потрібно або контролювати зовні, або додати до рішення.
абанана

4
Із N відповіді на ТА, це єдиний, хто пройшов усі мої тести. гж!
avishayp

2
stackoverflow.com/a/37414115/210971 використовує той самий метод, але працює також для порожнього списку та 0 лічильника розділення.
LookAheadAtYourTypes

Гарний! Крім того, n можна змусити працювати як batch_size, замінивши k і n у зворотній заяві :)
haraprasadj

161

Це є причиною для 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


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
Я б не сказав, що безперервні шматки дурні. Можливо, ви б хотіли, наприклад, відрегулювати шматки (наприклад, шматок [0] <шматок [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]

це дає мені дивний результат. для p in chunks (діапазон (54), 3): print len ​​(p) повертається 18, 18, 51 ...

Зафіксовано, що це був кінцевий вихід.
paxdiablo

дивіться також виступ за посиланням
Jakob Kroeker

Це найкорисніша відповідь з практичних міркувань. Дякую!
mVChr

Коли я це використовую, роблячи for x in chunks(mylist,num): print x, я отримую бажані шматки, але між ними я отримую порожній список. Будь-яка ідея чому? Тобто, я отримую багато [], по одному після кожного шматка.
синаптик

12

Якщо розділити nелементи приблизно на kшматки, ви можете зробити елементи на n % k1 елемент більшими за інші, щоб розподілити зайві елементи.

Наступний код надасть вам довжину шматочків:

[(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+1th шматок як кордон, ми отримуємо, що ith фрагмент списку 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)]


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 частин. Розмір деталей відрізняється не більше ніж одним елементом.



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

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

3

Подивіться на numpy.split :

>>> a = numpy.array([1,2,3,4])
>>> numpy.split(a, 2)
[array([1, 2]), array([3, 4])]

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

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

FYI: Ваш однолаймер зламаний, дає неправильні результати. Інший працює чудово.
Пауло Фрейтас

2

Використання розуміння списку:

def divide_list_to_chunks(list_, n):
    return [list_[start::n] for start in range(n)]

Це не вирішує питання про те, щоб зробити всі шматки рівними.
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)]
>>> 

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]

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

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)

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

Округлення області лінза та використання його як індексу - це простіше рішення, ніж те, що пропонує 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

-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

Вибраний із цього посилання , і саме це мені допомогло. У мене був заздалегідь визначений список.


-1

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

p1, p2, p3, p4, p5 = np.split(df, 5)

4
Це не дає відповіді на питання, наприклад, як би ви його написали, якщо не знаєте заздалегідь, що ви хочете розділити його на п’ять частин. Крім того, ви (гадаю) припускаєте numpy і, можливо, фрейм даних панди. ОП запитує про загальний список.
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

ділі_спорт (1, 10, 9) повернеться

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

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

-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.