You can write up a recursive algorithm for that which prunes all the impossible options early on:
def make_weight_combs(min_wt, max_wt, step, nb_assets, req_wt):
weights = range(min_wt, max_wt + 1, step)
current = []
yield from _make_weight_combs_rec(weights, nb_assets, req_wt, current)
def _make_weight_combs_rec(weights, nb_assets, req_wt, current):
if nb_assets <= 0:
yield tuple(current)
else:
# Discard weights that cannot possibly be used
while weights and weights[0] + weights[-1] * (nb_assets - 1) < req_wt:
weights = weights[1:]
while weights and weights[-1] + weights[0] * (nb_assets - 1) > req_wt:
weights = weights[:-1]
# Add all possible weights
for w in weights:
current.append(w)
yield from _make_weight_combs_rec(weights, nb_assets - 1, req_wt - w, current)
current.pop()
min_wt = 10
max_wt = 50
step = 10
nb_assets = 5
req_wt = 100
for comb in make_weight_combs(min_wt, max_wt, step, nb_assets, req_wt):
print(comb, sum(comb))
Output:
(10, 10, 10, 20, 50) 100
(10, 10, 10, 30, 40) 100
(10, 10, 10, 40, 30) 100
(10, 10, 10, 50, 20) 100
(10, 10, 20, 10, 50) 100
(10, 10, 20, 20, 40) 100
(10, 10, 20, 30, 30) 100
(10, 10, 20, 40, 20) 100
...
If order of the weights does not matter (so, for example, (10, 10, 10, 20, 50)
and (50, 20, 10, 10, 10)
are the same), then you can modify the for
loop as follows:
for i, w in enumerate(weights):
current.append(w)
yield from _make_weight_combs_rec(weights[i:], nb_assets - 1, req_wt - w, current)
current.pop()
Which gives the output:
(10, 10, 10, 20, 50) 100
(10, 10, 10, 30, 40) 100
(10, 10, 20, 20, 40) 100
(10, 10, 20, 30, 30) 100
(10, 20, 20, 20, 30) 100
(20, 20, 20, 20, 20) 100