Разделить массив на основе веса куска

У меня есть массив с 2 <= n <= 100 удвоениями:

A = [a1, a2, ... , an], ai > 0

и целое число 2 <= k <= min(n, 20). Мне нужно разбить A на k подмассивов:

B1 = [a1,     a2, ... , ap]
B2 = [ap+1, ap+2, ... , aq]

             ...

Bk = [aw+1, aw+2, ... , an] 

так что сумма в каждом B почти равна (трудно дать строгое определение, что это значит - меня интересует приблизительное решение).

Пример:

Input: A = [1, 2, 1, 2, 1], k=2
Output: [[1, 2, 1], [2, 1]] or [[1, 2], [1, 2, 1]]

Я попробовал вероятностный подход:

  • выборка из [1, 2, .., n] с использованием A в качестве веса вероятности

  • разрезать выборку на квантили, чтобы найти хороший раздел,

но это было недостаточно стабильно для производства.

tl;dr Этот вопрос спрашивает о 2- кусковые деления. Мне нужно k-чанковое деление.


person Pawel    schedule 30.08.2018    source источник


Ответы (1)


Вычислить общую сумму массива S. Сумма каждого фрагмента должна быть около S / K.

Затем пройдитесь по массиву, вычислив текущую сумму R. Когда R+A[i+1] - S/K станет больше, чем S/K - R, закрыть текущий чанк и сделать R=0. Продолжайте со следующим фрагментом.

Вы также можете компенсировать накапливающуюся ошибку (если она возникает), сравнивая общую сумму M чанков с M * S / K

Код на скорую руку для последнего подхода (не проверено досконально)

def chunks(lst, k):
    s = sum(lst)
    sk = s / k
    #sk = max(s / k, max(lst))
    #variant from user2052436 in comments  
    idx = 0
    chunkstart = 0
    r = 0
    res = []
    for m in range(1, k):
        for idx in range(chunkstart, len(lst)):
            km = k -m
            irest = len(lst)-idx
            if((km>=irest) or (2*r+lst[idx]>2*m*sk)) and (idx>chunkstart):
                res.append(lst[chunkstart:idx])
                chunkstart = idx
                break
            r += lst[idx]
    res.append(lst[idx:len(lst)])
    return res

print(chunks([3,1,5,2,8,3,2], 3))
print(chunks([1,1,1,100], 3))

>>>[[3, 1, 5], [2, 8], [3, 2]]
   [[1, 1], [1], [100]]
person MBo    schedule 30.08.2018
comment
Спасибо за хороший фрагмент! Проблема с этим подходом в том, что иногда он дает сбой — например, chunks([1, 1, 1, 100], 3) вернет 2 подмассива, а не 3. - person Pawel; 30.08.2018
comment
Да, мы должны добавить ограничение на длину чанка (учитывать сравнение k-m и len-idx) - person MBo; 30.08.2018
comment
Сделал эту поправку. Возможно, есть и другие тяжелые случаи. - person MBo; 30.08.2018
comment
Кажется, это работает очень хорошо, отличная работа! (Я не создал тест, который не работает) - person Pawel; 30.08.2018
comment
К сожалению, существует такой тяжелый случай: chunks([16, 8, 6, 4], 4) возвращает [[16], [], [8], [6, 4]] - person Pawel; 30.08.2018
comment
Логическая ошибка в алгоритме - добавляется пустой чанк, когда накопленная сумма слишком велика. Проверю. - person MBo; 30.08.2018
comment
Добавлена ​​проверка на пустой чанк. - person MBo; 30.08.2018
comment
Проходит все тесты, которые я придумал! - person Pawel; 31.08.2018
comment
Есть ли доказательства такого подхода? - person Pham Trung; 24.10.2018
comment
@Pham Trung Нет, просто произвольно выбранная и адаптированная эвристика. - person MBo; 24.10.2018
comment
Не подходит для print(chunks([100,1,1,103,90], 3)). Результат: [[100], [1, 1, 103], [90]]. - person user2052436; 20.11.2018
comment
sk = max(s / k, max(lst)) исправляет приведенный выше пример: результат [[100, 1, 1], [103], [90]] - person user2052436; 20.11.2018
comment
@ ser2052436 Но, вероятно, в некоторых случаях может быть худший вариант (не проверял). См. также решение Pham Trung в связанной теме. - person MBo; 20.11.2018
comment
Он не должен давать худших вариантов: обычно (особенно при n >> k), s / k is > max(lst), так что у вас остается среднее. Если есть огромный выброс, то фрагмент, содержащий выброс, будет иметь сумму, по крайней мере, равную этому выбросу, поэтому имеет смысл использовать выброс вместо среднего. - person user2052436; 20.11.2018