Разбиение списка на N частей примерно равной длины - PullRequest
119 голосов
/ 25 января 2010

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

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

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

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

Ответы [ 23 ]

149 голосов
/ 26 января 2010

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

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 xrange(n))

Пример:

>>> list(split(range(11), 3))
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10]]
102 голосов
/ 26 января 2010

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

>>> 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]]
87 голосов
/ 30 марта 2017

Это смысл существования для numpy.array_split*:

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

* кредит Ноль Пирей в номере 6

60 голосов
/ 25 января 2010

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

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]]
15 голосов
/ 25 января 2010

Изменение кода для получения 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]
7 голосов
/ 24 мая 2016

Если вы разделите 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)]

3 голосов
/ 16 июля 2018

См. more_itertools.divide:

n = 2

[list(x) for x in mit.divide(n, range(5, 11))]
# [[5, 6, 7], [8, 9, 10]]

[list(x) for x in mit.divide(n, range(5, 12))]
# [[5, 6, 7, 8], [9, 10, 11]]

Установка через > pip install more_itertools.

3 голосов
/ 25 января 2010

Вот тот, который добавляет 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)]
2 голосов
/ 24 сентября 2014

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

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]]
2 голосов
/ 10 февраля 2010

Взгляните на numpy.split :

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