Распределите целое число по набору слотов как можно более равномерно

Я пытаюсь изобразить элегантный способ реализации распределения суммы в данном наборе слотов в Python.

Например:

7 апельсинов, распределенных на 4 тарелки, вернутся:

[2, 2, 2, 1]

10 апельсинов на 4 тарелках будут:

[3, 3, 2, 2]

Ответ 1

Если a - ваш номер, а b - количество слотов, вы можете использовать следующее понимание списка:

[(a//b) + (i < (a % b)) for i in range(b)]

пример:

>>> a = 23
>>> b = 17
>>> [(a//b) + (i < (a % b)) for i in range(b)]
[2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

Ответ 2

Концептуально, что вы хотите сделать, это вычислить 7//4 = 1 и 7 % 4 = 3. Это означает, что все тарелки получают 1 целый апельсин. Остальная часть 3 говорит вам, что три из пластин получают дополнительный оранжевый.

divmod ярлык для одновременного получения обеих величин:

def distribute(oranges, plates):
    base, extra = divmod(oranges, plates)
    return [base + (i < extra) for i in range(plates)]

С вашим примером:

>>> distribute(oranges=7, plates=4)
[2, 2, 2, 1]

Для полноты, вы, вероятно, захотите проверить, что oranges неотрицательны и plates положительны. Учитывая эти условия, вот несколько дополнительных тестов:

>>> distribute(oranges=7, plates=1)
[7]

>>> distribute(oranges=0, plates=4)
[0, 0, 0, 0]

>>> distribute(oranges=20, plates=2)
[10, 10]

>>> distribute(oranges=19, plates=4)
[5, 5, 5, 4]

>>> distribute(oranges=10, plates=4)
[3, 3, 2, 2]

Ответ 3

Вы хотите взглянуть на алгоритм Брезенхэма для рисования линий (т.е. Распределение X пикселей в диапазоне Y как можно более "прямолинейно"; применение этого к задаче распределения является простым).

Это реализация, которую я нашел здесь:

def get_line(start, end):
    """Bresenham Line Algorithm
    Produces a list of tuples from start and end

    >>> points1 = get_line((0, 0), (3, 4))
    >>> points2 = get_line((3, 4), (0, 0))
    >>> assert(set(points1) == set(points2))
    >>> print points1
    [(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
    >>> print points2
    [(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
    """
    # Setup initial conditions
    x1, y1 = start
    x2, y2 = end
    dx = x2 - x1
    dy = y2 - y1

    # Determine how steep the line is
    is_steep = abs(dy) > abs(dx)

    # Rotate line
    if is_steep:
        x1, y1 = y1, x1
        x2, y2 = y2, x2

    # Swap start and end points if necessary and store swap state
    swapped = False
    if x1 > x2:
        x1, x2 = x2, x1
        y1, y2 = y2, y1
        swapped = True

    # Recalculate differentials
    dx = x2 - x1
    dy = y2 - y1

    # Calculate error
    error = int(dx / 2.0)
    ystep = 1 if y1 < y2 else -1

    # Iterate over bounding box generating points between start and end
    y = y1
    points = []
    for x in range(x1, x2 + 1):
        coord = (y, x) if is_steep else (x, y)
        points.append(coord)
        error -= abs(dy)
        if error < 0:
            y += ystep
            error += dx

    # Reverse the list if the coordinates were swapped
    if swapped:
        points.reverse()
    return points

Ответ 4

Смотрите также more_itertools.distribute, сторонний инструмент и его исходный код.

Код

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

import more_itertools as mit


def sum_distrib(m, n):
    """Return an iterable of m items distributed across n spaces."""
    return [sum(x) for x in mit.distribute(n, [1]*m)]

демонстрация

sum_distrib(10, 4)
# [3, 3, 2, 2]

sum_distrib(7, 4)
# [2, 2, 2, 1]

sum_distrib(23, 17)
# [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

пример

Эта идея сродни распределению колоды карт между игроками. Вот начальная игра Slapjack

import random
import itertools as it


players = 8
suits = list("♠♡♢♣")
ranks = list(range(2, 11)) + list("JQKA")
deck = list(it.product(ranks, suits))
random.shuffle(deck)

hands = [list(hand) for hand in mit.distribute(players, deck)]
hands
# [[('A', '♣'), (9, '♠'), ('K', '♣'), (7, '♢'), ('A', '♠'), (5, '♠'), (2, '♠')],
#  [(6, '♣'), ('Q', '♠'), (5, '♢'), (5, '♡'), (3, '♡'), (8, '♡'), (7, '♣')],
#  [(7, '♡'), (9, '♢'), (2, '♢'), (9, '♡'), (7, '♠'), ('K', '♠')],
#   ...]

где карты распределяются "как можно более равномерно между всеми [8] игроками":

[len(hand) for hand in hands]
# [7, 7, 7, 7, 6, 6, 6, 6]

Ответ 5

Безумный физик ответит идеально. Но если вы хотите распределить апельсины равномерно по тарелкам (например, 2 3 2 3 против 2 2 3 3 в примере 7 апельсинов и 4 тарелок), здесь простая идея.

Легкий случай

Возьмите пример с 31 апельсином и 7 тарелками для примера.

Шаг 1: Вы начинаете как Безумный Физик с евклидовым делением: 31 = 4*7 + 3. Положите 4 апельсина в каждую тарелку и оставьте 3.

[4, 4, 4, 4, 4, 4, 4]

Шаг 2: Теперь у вас больше тарелок, чем апельсинов, и это совсем другое: вам нужно распределить тарелки по апельсинам. У вас осталось 7 тарелок и 3 апельсина: 7 = 2*3 + 1. У вас будет 2 тарелки за апельсин (у вас осталась тарелка, но это не имеет значения). Давай назовем это 2 leap. Начало в leap/2 будет довольно:

[4, 5, 4, 5, 4, 5, 4]

Не такой легкий случай

Это был легкий случай. Что происходит с 34 апельсинами и 7 тарелками?

Шаг 1: Вы все еще начинаете как Безумный Физик с евклидовым делением: 34 = 4*7 + 6. Положите 4 апельсина в каждую тарелку и оставьте 6.

[4, 4, 4, 4, 4, 4, 4]

Шаг 2: Теперь у вас осталось 7 тарелок и 6 апельсинов: 7 = 1*6 + 1. У вас будет одна тарелка на апельсин. Но подождите.. У меня нет 7 апельсинов! Не бойся, я одолжу тебе яблоко

[5, 5, 5, 5, 5, 5, 4+apple]

Но если вы хотите получить единообразие, вы должны поместить это яблоко в другом месте! Почему бы не попробовать распространять яблоки, как апельсины в первом случае? 7 тарелок, 1 яблоко: 7 = 1*7 + 0. leap равен 7, начинается с leap/2, то есть 3:

[5, 5, 5, 4+apple, 5, 5, 5]

Шаг 3. Ты должен мне яблоко. Пожалуйста, верните мне мое яблоко:

[5, 5, 5, 4, 5, 5, 5]

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

Код

Хватит говорить, код:

def distribute(oranges, plates):
    base, extra = divmod(oranges, plates) # extra < plates
    if extra == 0:
        L = [base for _ in range(plates)]
    elif extra <= plates//2:
        leap = plates // extra
        L = [base + (i%leap == leap//2) for i in range(plates)]
    else: # plates/2 < extra < plates
        leap = plates // (plates-extra) # plates - extra is the number of apples I lent you
        L = [base + (1 - (i%leap == leap//2)) for i in range(plates)]
    return L

Некоторые тесты:

>>> distribute(oranges=28, plates=7)
[4, 4, 4, 4, 4, 4, 4]
>>> distribute(oranges=29, plates=7)
[4, 4, 4, 5, 4, 4, 4]
>>> distribute(oranges=30, plates=7)
[4, 5, 4, 4, 5, 4, 4]
>>> distribute(oranges=31, plates=7)
[4, 5, 4, 5, 4, 5, 4]
>>> distribute(oranges=32, plates=7)
[5, 4, 5, 4, 5, 4, 5]
>>> distribute(oranges=33, plates=7)
[5, 4, 5, 5, 4, 5, 5]
>>> distribute(oranges=34, plates=7)
[5, 5, 5, 4, 5, 5, 5]
>>> distribute(oranges=35, plates=7)
[5, 5, 5, 5, 5, 5, 5]

Ответ 6

Не может быть эффективным, но простым итеративным решением

oranges=7
list_range=4

l=[1]*list_range
for i in range(oranges-list_range):
    l[i]+=1

Ответ 7

Не уверен, как это работает. Но он возвращает точно такие же результаты

a = 23
b = 17
s = pd.Series(pd.cut(mylist, b), index=mylist)
s.groupby(s).size().values