The goal is to get a list of all possible variants of sequences of positive integer (nominal coin) values from a list L where each integer (nominal coin) value can be used multiple times (i.e. allowing repetitions) where the sum of the (nominal coin) values equals targetSum with the constraint that the total amount of numbers (coins) in the generated variant of the sequence is limited to the range between n and m (including n and m).
The code below is what I have came up with up to now, but it runs way too slow for the target purpose of being part of an optimization problem:
def allArrangementsOfIntegerItemsInLsummingUpTOtargetSum(L, targetSum, n=None, m=None): if n is None: n = 1 if m is None: m = targetSum lenL = len(L) # print(f"{targetSum=}, {L=}, {n=}, {m=}") Stack = [] # Initialize the Stack with the starting point for each element in L for i in range(lenL): currentSum = L[ i ] path = [ L[ i ] ] start = 0 # Start from 0 allows revisiting of all items Stack.append( (currentSum, path, start ) ) while Stack: currentSum, path, start = Stack.pop() # Check if the current path meets the criteria if currentSum == targetSum and n <= len(path) <= m: yield path if currentSum > targetSum or len(path) > m: continue # ^ - NEXT please: stop exploring this path as it's not valid or complete # Continue to build the path by adding elements from L, starting from 0 index for i in range(len(L)): # Change start to 0 if you want to include all permutations newSum = currentSum + L[ i ] newPath = path + [ L[ i ] ] Stack.append((newSum, newPath, 0)) # Start from 0 allows every possibility# def allArrangementsOfIntegerItemsInLsummingUpTOtargetSumsplitsGenerator = allArrangementsOfIntegerItemsInLsummingUpTOtargetSumL = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 ; m=1 ; n=30sT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> {listOfSplits}")L = [ 60, 61, 62, 63, 64 ] ; targetSum = 600 # m=1 ; n=6000 are DEFAULT valuessT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 6000 -> {listOfSplits}")giving as output:
dT=0.000047 s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> [[17, 13], [13, 17]]dT=5.487905 s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 600 -> [[60, 60, 60, 60, 60, 60, 60, 60, 60, 60]]where you can see that the algorithm needs over 5 seconds to come up with the only possible variant making same kind of calculation for targetSum = 6000 lasting "forever".
Any idea of how to write code able to come up at least an order of magnitude faster with a result?
I searched the Internet already for weeks and found that all of the knapsack, coin change and dynamic programming based known optimization approaches are not covering such a basic task which special case is to be used in order to divide a list of items into sub-lists (partitions) with sizes ranging from a to b for the purpose of optimization of an overall weight function which uses values obtained from a local weight function calculating single weights out of the items in each of the sub-list (partition).
Notice that both sequences [[17, 13], [13, 17]] consist of same (nominal coin) values. In other words the order of the values matter giving two variants instead of only one if the order were deliberate.
What I am interested in is another algorithm able to come up with the result much faster, so the programming language in which this algorithm is written or expressed is secondary, but I would prefer Python to describe the algorithm as this will make it as a side-effect easy to test it against the already provided code.
UPDATE considering code provided in the answer by M.S :
def count_combinations(L, targetSum, n=None, m=None): if n is None: n = 1 if m is None: m = targetSum # Create the DP table with dimensions (m+1) x (targetSum+1) dp = [[0] * (targetSum + 1) for _ in range(m + 1)] dp[0][0] = 1 # Base case # Update the DP table for num in L: for count in range(m, n - 1, -1): # Go from m to n for sum_val in range(num, targetSum + 1): dp[count][sum_val] += dp[count - 1][sum_val - num] # Extract all valid combinations result = [] for count in range(n, m + 1): if dp[count][targetSum] > 0: result.extend(extract_combinations(L, count, targetSum, dp)) return resultdef extract_combinations(L, count, targetSum, dp): combinations = [] def backtrack(current_count, current_sum, combination): if current_count == 0 and current_sum == 0: combinations.append(combination.copy()) return if current_count <= 0 or current_sum <= 0: return # Backtrack from the last number considered for num in L: if current_sum >= num and dp[current_count-1][current_sum-num] > 0: combination.append(num) backtrack(current_count-1, current_sum-num, combination) combination.pop() backtrack(count, targetSum, []) return combinationssplitsGenerator = count_combinationsL = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 ; m=1 ; n=30sT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> {listOfSplits}")L = [ 60, 61, 62, 63, 64 ] ; targetSum = 600 # m=1 ; n=6000 are DEFAULT valuessT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 6000 -> {listOfSplits}")which outputs:
dT=0.000469 s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> [[13, 17], [17, 13]]dT=0.285951 s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 6000 -> []failing to provide a result in case of the second example.
UPDATE considering the by brevity excelling code provided in an answer by chrslg :
def bb3( L, target, partial=[]): if target < 0 : return [] if target ==0 : return [partial] if target<L[0] : return [] sols=[] for c in L: if target>=c: sols.extend( bb3(L, target-c, partial+[c]) ) return solssplitsGenerator = bb3L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 ; m=1 ; n=30 # m=1 ; n=30 are DEFAULT valuessT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> {listOfSplits}")L = [ 60, 61, 62, 63, 64 ] ; targetSum = 600 # m=1 ; n=6000 are DEFAULT valuessT=T(); listOfSplits = list(splitsGenerator(L, targetSum) ); eT=T(); dT=eT-sTprint( f"{dT=:.6f} s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 6000 -> {listOfSplits}")outputs:
dT=0.000012 s -> L = [ 13, 17, 23, 24, 25 ] ; targetSum = 30 -> [[13, 17], [17, 13]]dT=0.933661 s -> L = [ 60, 61, 62, 63, 64 ] ; targetSum = 6000 -> [[60, 60, 60, 60, 60, 60, 60, 60, 60, 60]]running 4 to 6 times faster than code I have provided as reference.
Considering that it needs almost one second to come up with an obvious result in second case it is still beyond the expectation of at least an order of magnitude.
I have started with the recursive approach but because recursion can run into recursion limits the code I provided in the question is a re-written version of the recursive one and maybe therefore got slower because of replacing recursion with Stack ?