Following from this answer, I modified the code to fit this problem.
Notice the array is fed to the function in descending order.
def combinations_cutoff(array, tuple_length, s, prev_array=[], n_skips=[]):
if len(prev_array) == tuple_length:
if sum(prev_array) == s:
return [prev_array]
return []
combs = []
for i, val in enumerate(array):
prev_array_extended = prev_array.copy()
prev_array_extended.append(val)
if sum(prev_array_extended) > s:
n_skips[0] += 1
print(f"cutoff! arr={prev_array_extended}, s={sum(prev_array_extended)}, skip #{n_skips[0]}")
continue
combs += combinations_cutoff(array[i+1:], tuple_length, s, prev_array_extended, n_skips=n_skips)
return combs
def main():
n = 20
k = 3
s = 15
arr = np.arange(n)[::-1]
n_skips = [0]
gen = combinations_cutoff(arr, k, s, n_skips=n_skips)
lst = []
for c in gen:
lst.append(c)
print(c)
print(f"total solutions: {len(lst)}, skips={n_skips[0]}")
if __name__ == "__main__":
main()
Output:
cutoff! arr=[19], s=19, skip #1
cutoff! arr=[18], s=18, skip #2
cutoff! arr=[17], s=17, skip #3
cutoff! arr=[16], s=16, skip #4
cutoff! arr=[15, 14], s=29, skip #5
cutoff! arr=[15, 13], s=28, skip #6
cutoff! arr=[15, 12], s=27, skip #7
cutoff! arr=[15, 11], s=26, skip #8
cutoff! arr=[15, 10], s=25, skip #9
cutoff! arr=[15, 9], s=24, skip #10
cutoff! arr=[15, 8], s=23, skip #11
cutoff! arr=[15, 7], s=22, skip #12
cutoff! arr=[15, 6], s=21, skip #13
cutoff! arr=[15, 5], s=20, skip #14
cutoff! arr=[15, 4], s=19, skip #15
cutoff! arr=[15, 3], s=18, skip #16
cutoff! arr=[15, 2], s=17, skip #17
cutoff! arr=[15, 1], s=16, skip #18
cutoff! arr=[14, 13], s=27, skip #19
cutoff! arr=[14, 12], s=26, skip #20
cutoff! arr=[14, 11], s=25, skip #21
cutoff! arr=[14, 10], s=24, skip #22
cutoff! arr=[14, 9], s=23, skip #23
cutoff! arr=[14, 8], s=22, skip #24
cutoff! arr=[14, 7], s=21, skip #25
cutoff! arr=[14, 6], s=20, skip #26
cutoff! arr=[14, 5], s=19, skip #27
cutoff! arr=[14, 4], s=18, skip #28
cutoff! arr=[14, 3], s=17, skip #29
cutoff! arr=[14, 2], s=16, skip #30
cutoff! arr=[13, 12], s=25, skip #31
cutoff! arr=[13, 11], s=24, skip #32
cutoff! arr=[13, 10], s=23, skip #33
cutoff! arr=[13, 9], s=22, skip #34
cutoff! arr=[13, 8], s=21, skip #35
cutoff! arr=[13, 7], s=20, skip #36
cutoff! arr=[13, 6], s=19, skip #37
cutoff! arr=[13, 5], s=18, skip #38
cutoff! arr=[13, 4], s=17, skip #39
cutoff! arr=[13, 3], s=16, skip #40
cutoff! arr=[13, 2, 1], s=16, skip #41
cutoff! arr=[12, 11], s=23, skip #42
cutoff! arr=[12, 10], s=22, skip #43
cutoff! arr=[12, 9], s=21, skip #44
cutoff! arr=[12, 8], s=20, skip #45
cutoff! arr=[12, 7], s=19, skip #46
cutoff! arr=[12, 6], s=18, skip #47
cutoff! arr=[12, 5], s=17, skip #48
cutoff! arr=[12, 4], s=16, skip #49
cutoff! arr=[12, 3, 2], s=17, skip #50
cutoff! arr=[12, 3, 1], s=16, skip #51
cutoff! arr=[11, 10], s=21, skip #52
cutoff! arr=[11, 9], s=20, skip #53
cutoff! arr=[11, 8], s=19, skip #54
cutoff! arr=[11, 7], s=18, skip #55
cutoff! arr=[11, 6], s=17, skip #56
cutoff! arr=[11, 5], s=16, skip #57
cutoff! arr=[11, 4, 3], s=18, skip #58
cutoff! arr=[11, 4, 2], s=17, skip #59
cutoff! arr=[11, 4, 1], s=16, skip #60
cutoff! arr=[11, 3, 2], s=16, skip #61
cutoff! arr=[10, 9], s=19, skip #62
cutoff! arr=[10, 8], s=18, skip #63
cutoff! arr=[10, 7], s=17, skip #64
cutoff! arr=[10, 6], s=16, skip #65
cutoff! arr=[10, 5, 4], s=19, skip #66
cutoff! arr=[10, 5, 3], s=18, skip #67
cutoff! arr=[10, 5, 2], s=17, skip #68
cutoff! arr=[10, 5, 1], s=16, skip #69
cutoff! arr=[10, 4, 3], s=17, skip #70
cutoff! arr=[10, 4, 2], s=16, skip #71
cutoff! arr=[9, 8], s=17, skip #72
cutoff! arr=[9, 7], s=16, skip #73
cutoff! arr=[9, 6, 5], s=20, skip #74
cutoff! arr=[9, 6, 4], s=19, skip #75
cutoff! arr=[9, 6, 3], s=18, skip #76
cutoff! arr=[9, 6, 2], s=17, skip #77
cutoff! arr=[9, 6, 1], s=16, skip #78
cutoff! arr=[9, 5, 4], s=18, skip #79
cutoff! arr=[9, 5, 3], s=17, skip #80
cutoff! arr=[9, 5, 2], s=16, skip #81
cutoff! arr=[9, 4, 3], s=16, skip #82
cutoff! arr=[8, 7, 6], s=21, skip #83
cutoff! arr=[8, 7, 5], s=20, skip #84
cutoff! arr=[8, 7, 4], s=19, skip #85
cutoff! arr=[8, 7, 3], s=18, skip #86
cutoff! arr=[8, 7, 2], s=17, skip #87
cutoff! arr=[8, 7, 1], s=16, skip #88
cutoff! arr=[8, 6, 5], s=19, skip #89
cutoff! arr=[8, 6, 4], s=18, skip #90
cutoff! arr=[8, 6, 3], s=17, skip #91
cutoff! arr=[8, 6, 2], s=16, skip #92
cutoff! arr=[8, 5, 4], s=17, skip #93
cutoff! arr=[8, 5, 3], s=16, skip #94
cutoff! arr=[7, 6, 5], s=18, skip #95
cutoff! arr=[7, 6, 4], s=17, skip #96
cutoff! arr=[7, 6, 3], s=16, skip #97
cutoff! arr=[7, 5, 4], s=16, skip #98
[14, 1, 0]
[13, 2, 0]
[12, 3, 0]
[12, 2, 1]
[11, 4, 0]
[11, 3, 1]
[10, 5, 0]
[10, 4, 1]
[10, 3, 2]
[9, 6, 0]
[9, 5, 1]
[9, 4, 2]
[8, 7, 0]
[8, 6, 1]
[8, 5, 2]
[8, 4, 3]
[7, 6, 2]
[7, 5, 3]
[6, 5, 4]
total solutions: 19, skips=98
The function always returns a list of all combinations of length tuple_length
, and that start with prev_array
and end with a combination of array
, and that sum up to s
.
At each step, we consider if prev_array
is "ready" (=of correct length and sum). If so, it should be returned.
If it is the sum wasn't reached, and the array is already long enough, then it is invalid.
Now, we are left with a prev_array
that isn't long enough, and we consider which element should be added to it right now.
The options for elements are as defined by the recursion, all of array
's elements, thus we iterate over them and try them out.
When an element is being tried out, only elements following it can be tried later.
Trying out an element means calling the recursion with a prev_array_extended
that is the prev_array
followed by the selected element.
Now to the main dish - the cutoff - BEFORE calling the recursion, we can consider prev_array
's sum. Since array
is guaranteed to be of descending order [notice how it is called from main
], if any prefix of a candidate tuple has passed s
, then there is no point moving on with checking possibilities for that candidate, and we can stop and move on to smaller values to push to prev_array
's end.
n_skips
is just an ugly hack to count how many times a cutoff was done.