Страницы

Поиск по вопросам

суббота, 4 января 2020 г.

Как эффективно перебрать процентные распределения?

#python #алгоритм


Необходимо с помощью Python решить задачу:
Есть набор финансовых инструментов (пусть 10 штук). Есть сумма инвестиционного портфеля.

Необходимо найти ВСЕ возможные варианты распределения долей от общей суммы инвестиционного
портфеля, приходящихся на каждый инструмент(включая 0%).
Общая сумма должна быть = 100%. Шаг задан (пусть 2%). Т.е. значения процентов 0,2,4,6..100%.
Пример: 
    [14, 6, 10, 6, 10, 0, 16, 24, 2, 12].

Вариант влоб:

step = 2.0
num_of_tickers = 10
steps = math.floor(100.0 / step) + 1
share_lst = [idx * step for idx in range(0, steps)]
comb_all = itertools.product(share_lst, repeat=num_of_tickers)
comb_res = list(filter(lambda x: sum(x) == 100.0, comb_all))


Считает ОЧЕНЬ долго.

Пробовал ухищрения, сокращающие набор по условию <=100%:

comb_all = itertools.product(share_lst, repeat=5)
comb_all_lst = list(filter(lambda x: sum(x) <= 100.0, comb_all))
for lst in comb_all_lst:
    for lst_1 in comb_all_lst:
        if (sum(lst) + sum(lst_1)) == 100.0:
            comb_res.append(lst + lst_1)


Уже реальнее, но все равно долго.

Как добиться более быстрой скорости работы?

Пример для step = 25.0 и числа инструментов num_of_tickers = 4:

Возможные значения процентной доли:

[0.0, 25.0, 50.0, 75.0, 100.0]


На выходе должна быть таблица распределений вида:

0,   0,   0,   100
0,   0,   25,  75
0,   25,  0,   75
25,  0,   0,   75
25,  0,   25,  50
...
100, 0,   0,   0

    


Ответы

Ответ 1



Задача сводится к перебору композиций/разложений натурального числа (weak сompositions) для 100 / step целого числа: #!/usr/bin/env python3 import math step = 2 n = 100 // step k = 10 # количество инструментов def binom(n, k): f = math.factorial return f(n) // (f(k) * f(n - k)) print("Количество распределений:", binom(n+k-1, n)) for comp in weak_compositions(k, n): assert len(comp) == k and sum(n*step for n in comp) == 100 print(*[n*step for n in comp]) где weak_composition(k, n) это читаемая реализация из ответа @user3736966 на схожий вопрос на Stack Overflow: def weak_compositions(boxes, balls, parent=tuple()): if boxes > 1: for i in range(balls + 1): yield from weak_compositions(boxes - 1, i, parent + (balls - i,)) else: yield parent + (balls,) Пример: >>> print(*[' + '.join(map(str, c)) for c in weak_compositions(3, 5)], sep='\n') 5 + 0 + 0 4 + 1 + 0 4 + 0 + 1 3 + 2 + 0 3 + 1 + 1 3 + 0 + 2 2 + 3 + 0 2 + 2 + 1 2 + 1 + 2 2 + 0 + 3 1 + 4 + 0 1 + 3 + 1 1 + 2 + 2 1 + 1 + 3 1 + 0 + 4 0 + 5 + 0 0 + 4 + 1 0 + 3 + 2 0 + 2 + 3 0 + 1 + 4 0 + 0 + 5 Результат для step == 25, number_of_tickets == 4 Количество распределений: 35 100 0 0 0 75 25 0 0 75 0 25 0 75 0 0 25 50 50 0 0 50 25 25 0 50 25 0 25 50 0 50 0 50 0 25 25 50 0 0 50 25 75 0 0 25 50 25 0 25 50 0 25 25 25 50 0 25 25 25 25 25 25 0 50 25 0 75 0 25 0 50 25 25 0 25 50 25 0 0 75 0 100 0 0 0 75 25 0 0 75 0 25 0 50 50 0 0 50 25 25 0 50 0 50 0 25 75 0 0 25 50 25 0 25 25 50 0 25 0 75 0 0 100 0 0 0 75 25 0 0 50 50 0 0 25 75 0 0 0 100 Результат для step == 2, number_of_tickets == 10 Количество распределений: 12565671261 100 0 0 0 0 0 0 0 0 0 98 2 0 0 0 0 0 0 0 0 98 0 2 0 0 0 0 0 0 0 ... 72 2 0 4 16 0 2 0 2 2 72 2 0 4 16 0 2 0 0 4 72 2 0 4 16 0 0 6 0 0 ... Связанные вопросы: Next Composition of n into k parts - does anyone have a working algorithm? Efficient lazy weak compositions

Ответ 2



to Rapohin Dmitriy Я и взял ваш пример: [14, 6, 10, 6, 10, 0, 16, 24, 2, 12], только для ускорения отсортировал его по убыванию. import sys import datetime def variant(i0, i1, i2, i3, i4, i5, i6, i7, i8, i9): ls = [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9] k0 = sorted(ls, reverse=True)[0] k1 = sorted(ls, reverse=True)[1] k2 = sorted(ls, reverse=True)[2] k3 = sorted(ls, reverse=True)[3] k4 = sorted(ls, reverse=True)[4] k5 = sorted(ls, reverse=True)[5] k6 = sorted(ls, reverse=True)[6] k7 = sorted(ls, reverse=True)[7] k8 = sorted(ls, reverse=True)[8] k9 = sorted(ls, reverse=True)[9] pa = 100 if k0 == 0: r0 = 1 else: r0 = int(pa / k0) + 1 if k1 == 0: r1 = 1 else: r1 = int(pa / k1) + 1 if k2 == 0: r2 = 1 else: r2 = int(pa / k2) + 1 if k3 == 0: r3 = 1 else: r3 = int(pa / k3) + 1 if k4 == 0: r4 = 1 else: r4 = int(pa / k4) + 1 if k5 == 0: r5 = 1 else: r5 = int(pa / k5) + 1 if k6 == 0: r6 = 1 else: r6 = int(pa / k6) + 1 if k7 == 0: r7 = 1 else: r7 = int(pa / k7) + 1 if k8 == 0: r8 = 1 else: r8 = int(pa / k8) + 1 if k9 == 0: r9 = 1 else: r9 = int(pa / k9) + 1 c = 0 s = 0 print(k0,' ',k1,' ',k2,' ',k3,' ',k4,' ',k5,' ',k6,' ',k7,' ',k8,' ',k9) for c0 in range(r0): s0 = c0 * k0 for c1 in range(r1): s1 = c1 * k1 if s1 + s0 > pa: break for c2 in range(r2): s2 = c2 * k2 if s2 + s1 + s0 > pa: break for c3 in range(r3): s3 = c3 * k3 if s3 + s2 + s1 + s0 > pa: break for c4 in range(r4): s4 = c4 * k4 if s4 + s3 + s2 + s1 + s0 > pa: break for c5 in range(r5): s5 = c5 * k5 if s5 + s4 + s3 + s2 + s1 + s0 > pa: break for c6 in range(r6): s6 = c6 * k6 if s6 + s5 + s4 + s3 + s2 + s1 + \ s0 > pa: break for c7 in range(r7): s7 = c7 * k7 if s7 + s6 + s5 + s4 + s3 + s2 + \ s1 + s0 > pa: break for c8 in range(r8): s8 = c8 * k8 if s8 + s7 + s6 + s5 + s4 + \ s3 + s2 + s1 + s0 > pa: break for c9 in range(r9): s9 = c9 * k9 s = s9 + s8 + s7 + s6 + s5 + \ s4 + s3 + s2 + s1 + s0 if s < pa: pass elif s == pa: print(c0*k0, c1*k1, c2*k2, c3*k3, c4*k4, c5*k5, c6*k6, c7*k7, c8*k8, c9*k9) c += 1 else: break print('Всего вариантов: ', c) return print(datetime.datetime.now()) variant(14, 6, 10, 6, 10, 0, 16, 24, 2, 12) print(datetime.datetime.now()) А если выводить результаты в файл и учесть все замечания критиканов, а не помощников в решении вопроса, то модифицировав код написанный за 5 минут получим результат за 1 секунду: 2016-07-04 11:27:36.710029 24 16 14 12 10 10 6 6 2 0 Всего вариантов: 23436 2016-07-04 11:27:37.442615

Комментариев нет:

Отправить комментарий