Reputation: 1
Using Python
Here's some requirements:
I want to find a list of of numbers [ ] that:
add up to a number (say 30)
Be within a range of (start,end) let's say (8, 20)
Have Y (say 3) elements within the list
Ex: [8,10,12]
I've tried the code below, which works for what I want, but it gives me ALL combinations which is very heavy on memory. To select one, I've just randomly selected one, however I would like to use this for bigger lists with greater ranges, so this is not efficient.
list(combinations(list(range(8,20)),3))
Upvotes: 0
Views: 87
Reputation: 42133
Here's an example of a recursive function that will do this efficiently.
def rangeToSum(start,stop,target,count):
if count == 0: return []
minSum = sum(range(start,start+count-1))
stop = min(stop,target+1-minSum)
if count == 1 :
return [target] if target in range(start,stop) else []
for n in reversed(range(start,stop)):
subSum = rangeToSum(start,n,target-n,count-1)
if subSum: return subSum+[n]
return []
print(rangeToSum(8,20,30,3)) # [8,10,12]
The way it works is by trying the largest numbers first and calling itself to find numbers in the remaining range that add up to the remaining value. This will skip whole swats of combinations that cannot produce the target sum. e.g. trying 20 skips over combinations containing 19,18,16,15,14,13,12 or 11.
It also takes into account the minimum sum that the first count-1 items will produce to further reduce the stop value. e.g. to reach 30 from 8 with 3 numbers will use at least 17 (8+9) for the first two numbers so the stop value of the range can be reduced to 14 because 17 + 13 will reach 30 and any higher value will go beyond 30.
For large numbers, the function will find a solution quickly in most cases but it could also take a very long time depending on the combination of parameters.
rangeToSum(80000,20000000,3000000,10) # 0.6 second
# [80000, 80002, 80004, 80006, 80008, 80010, 80012, 80014, 80016, 2279928]
If you need it to be faster, you could try memoization (e.g. using lru_cache from functools).
Upvotes: 0
Reputation: 81594
The code you posted does not check for the sum.
The below snippets optimize memory use, not run time
If you are using Python 3 then combinations
already returns a generator. All you have to do is iterate over the combinations. If the sum is correct, print the combination and break
from the loop:
from itertools import combinations
for comb in combinations(range(8, 20), 3):
if sum(comb) == 30:
print(comb)
break
Outputs
(8, 9, 13)
Alternatively, you could use filter
and then call next
on the result. This way you can get as many combinations as you want:
from itertools import combinations
valid_combs = filter(lambda c: sum(c) == 30, combinations(range(8, 20), 3))
print(next(valid_combs))
print(next(valid_combs))
print(next(valid_combs))
Outputs
(8, 9, 13)
(8, 10, 12)
(9, 10, 11)
A bit more advanced and dynamic solution is with a function and yield from
(if you are using Python >= 3.3):
from itertools import combinations
def get_combs(r, n, s):
yield from filter(lambda c: sum(c) == s, combinations(r, n))
valid_combs = get_combs(range(8, 20), 3, 30)
print(next(valid_combs))
print(next(valid_combs))
print(next(valid_combs))
Outputs
(8, 9, 13)
(8, 10, 12)
(9, 10, 11)
Upvotes: 1