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

Я ищу быстрый, чистый, питонический способ разделить список ровно на n почти равных разделов.

partition([1,2,3,4,5],5)->[[1],[2],[3],[4],[5]]
partition([1,2,3,4,5],2)->[[1,2],[3,4,5]] (or [[1,2,3],[4,5]])
partition([1,2,3,4,5],3)->[[1,2],[3,4],[5]] (there are other ways to slice this one too)

Здесь есть несколько ответов Итерация по фрагментам списка, которые очень близки к тому, что я хочу, за исключением они ориентированы на размер списка, и меня волнует количество списков (некоторые из них также дополняются None). Очевидно, они легко конвертируются, но я ищу передовой метод.

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


person Drew    schedule 17.04.2010    source источник


Ответы (5)


def partition(lst, n):
    division = len(lst) / float(n)
    return [ lst[int(round(division * i)): int(round(division * (i + 1)))] for i in xrange(n) ]

>>> partition([1,2,3,4,5],5)
[[1], [2], [3], [4], [5]]
>>> partition([1,2,3,4,5],2)
[[1, 2, 3], [4, 5]]
>>> partition([1,2,3,4,5],3)
[[1, 2], [3, 4], [5]]
>>> partition(range(105), 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, 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, 100, 101, 102, 103, 104]]

Версия Python 3:

def partition(lst, n):
    division = len(lst) / n
    return [lst[round(division * i):round(division * (i + 1))] for i in range(n)]
person João Silva    schedule 17.04.2010
comment
Это не работает для нетривиальных примеров. Для partition(range(105), 10) последний подсписок будет содержать только 6 элементов. - person Daniel Stutzbach; 18.04.2010
comment
Как бы вы разделили этот список? - person João Silva; 18.04.2010
comment
@JG: 5 подсписок по 10 элементов и 5 подсписок по 11 элементов. - person Daniel Stutzbach; 18.04.2010
comment
@Daniel: Достаточно честно, хотя в исходном вопросе это не очень ясно. Сейчас это исправлено. - person João Silva; 18.04.2010
comment
@JG: Я считаю, что «ровно n почти равных» должно было быть «ровно n почти равной длины»; «до тех пор, пока оно в пределах 1» также является сильным намеком, даже если он расплывчатый и неясный. - person tzot; 18.04.2010

Просто другой подход, который работает только в том случае, если [[1,3,5],[2,4]] является приемлемым разделом в вашем примере.

def partition ( lst, n ):
    return [ lst[i::n] for i in xrange(n) ]

Это удовлетворяет примеру, упомянутому в примере @Daniel Stutzbach:

partition(range(105),10)
# [[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],
# [1, 11, 21, 31, 41, 51, 61, 71, 81, 91, 101],
# [2, 12, 22, 32, 42, 52, 62, 72, 82, 92, 102],
# [3, 13, 23, 33, 43, 53, 63, 73, 83, 93, 103],
# [4, 14, 24, 34, 44, 54, 64, 74, 84, 94, 104],
# [5, 15, 25, 35, 45, 55, 65, 75, 85, 95],
# [6, 16, 26, 36, 46, 56, 66, 76, 86, 96],
# [7, 17, 27, 37, 47, 57, 67, 77, 87, 97],
# [8, 18, 28, 38, 48, 58, 68, 78, 88, 98],
# [9, 19, 29, 39, 49, 59, 69, 79, 89, 99]]
person Manuel Zubieta    schedule 13.02.2013
comment
Это чудесное питоническое решение - person flatline; 03.05.2013
comment
Я чувствую, что должен быть какой-то умный способ получить исходный желаемый ввод, но zip( *partition(range(105),10) ) не работает, потому что zip обрезается ... Тем не менее, очень приятно. - person Walter Nissen; 15.11.2013
comment
О, черт возьми, это замечательно. - person coneyhelixlake; 09.12.2015
comment
если вы использовали itertools.izip дольше всего, он должен работать - person Oscar Smith; 28.06.2016
comment
Действительно, очень аккуратно (если вам не нужны непрерывные разделы). - person Petr Vepřek; 30.12.2020

Вот версия, похожая на версию Даниэля: она делится максимально равномерно, но помещает все большие разделы в начало:

def partition(lst, n):
    q, r = divmod(len(lst), n)
    indices = [q*i + min(i, r) for i in xrange(n+1)]
    return [lst[indices[i]:indices[i+1]] for i in xrange(n)]

Он также избегает использования арифметики с плавающей запятой, поскольку это всегда доставляет мне дискомфорт. :)

Изменить: пример, просто чтобы показать контраст с решением Даниэля Штутцбаха

>>> print [len(x) for x in partition(range(105), 10)]
[11, 11, 11, 11, 11, 10, 10, 10, 10, 10]
person Mark Dickinson    schedule 17.04.2010

Ниже представлен один из способов.

def partition(lst, n):
    increment = len(lst) / float(n)
    last = 0
    i = 1
    results = []
    while last < len(lst):
        idx = int(round(increment * i))
        results.append(lst[last:idx])
        last = idx
        i += 1
    return results

Если len (lst) не может быть равномерно разделено на n, эта версия будет распределять дополнительные элементы примерно через равные промежутки времени. Например:

>>> print [len(x) for x in partition(range(105), 10)]
[11, 10, 11, 10, 11, 10, 11, 10, 11, 10]

Код мог бы быть проще, если бы вы не возражали, чтобы все 11 были в начале или в конце.

person Daniel Stutzbach    schedule 17.04.2010
comment
Использование чисел с плавающей запятой может привести к машинно-зависимым результатам: когда приращение * i (математически) находится точно посередине между двумя целыми числами, округление может происходить в любом направлении в зависимости от того, какие числовые ошибки были введены. Как насчет того, чтобы вместо этого использовать что-то вроде idx = len(lst)*i//n? Или, возможно, idx = (len(lst)*i + n//2)//n, чтобы получить результаты, аналогичные текущему коду. - person Mark Dickinson; 18.04.2010
comment
К вашему сведению, это хорошо известный алгоритм Брезенхема. - person smci; 03.07.2011

Этот ответ предоставляет функцию split(list_, n, max_ratio) для людей, которые хотят разбить свой список на n части с соотношением не более max_ratio в длине частей. Он допускает большее количество вариантов, чем «максимум 1 разница в длине куска» у опрашивающего.

Он работает, выбирая отрезки длины n в пределах желаемого диапазона отношения [1, max_ratio), помещая их друг за другом, чтобы сформировать «сломанную палку» с правильными расстояниями между «точки останова», но неправильная общая длина. Масштабирование сломанной ручки до желаемой длины дает нам приблизительное положение точек останова, которое мы хотим. Для получения целых точек останова требуется последующее округление.

К сожалению, округление может привести к тому, что части будут слишком короткими, и вы сможете превысить max_ratio. См. Пример внизу этого ответа.

import random

def splitting_points(length, n, max_ratio):
    """n+1 slice points [0, ..., length] for n random-sized slices.

    max_ratio is the largest allowable ratio between the largest and the
    smallest part.
    """
    ratios = [random.uniform(1, max_ratio) for _ in range(n)]
    normalized_ratios = [r / sum(ratios) for r in ratios]
    cumulative_ratios = [
        sum(normalized_ratios[0:i])
        for i in range(n+1)
    ]
    scaled_distances = [
        int(round(r * length))
        for r in cumulative_ratios
    ]

    return scaled_distances


def split(list_, n, max_ratio):
    """Slice a list into n randomly-sized parts.

    max_ratio is the largest allowable ratio between the largest and the
    smallest part.
    """

    points = splitting_points(len(list_), n, ratio)

    return [
        list_[ points[i] : points[i+1] ]
        for i in range(n)
    ]

Вы можете попробовать это так:

for _ in range(10):
    parts = split('abcdefghijklmnopqrstuvwxyz', 4, 2)
    print([(len(part), part) for part in parts])

Пример плохого результата:

parts = split('abcdefghijklmnopqrstuvwxyz', 10, 2)

# lengths range from 1 to 4, not 2 to 4
[(3, 'abc'),  (3, 'def'), (1, 'g'),
 (4, 'hijk'), (3, 'lmn'), (2, 'op'),
 (2, 'qr'),  (3, 'stu'),  (2, 'vw'),
 (3, 'xyz')]
person Esteis    schedule 24.08.2015