Optimizing Subset Sums using Dynamic Programming Paradigm
I stumbled upon the Dynamic Programming Paradigm early in my career, when I was still young and fit, while working on reconciliations. It was my job and I needed an efficient way to reconcile the books. After coming home that night, unsure where to begin, I researched algorithms to help me find optimal solutions. During my exploration, I came across Richard Bellman's work on the Dynamic Programming Paradigm. This new approach enabled me to effectively address my initial challenge. Combined with regression analysis, it has proven invaluable in reconciliations.
You may be asking, "So how does it work?"
Basically, the dynamic programming method breaks down a large problem — like finding which payments match a certain deposit — into smaller parts, and stores those intermediate results so it doesn’t do the same work over and over. I went through dozens of iterations trying to get this right for reconciliation tasks. After a ton of trial and error, I found that this approach works surprisingly well when trying to match a list of transactions to a specific total — whether it’s vendor payments, customer receipts, or even batch deposits.
One thing I could have done better is build this with a Python GUI, but I’ll save that for a future project. It's still quite simple to use for individuals with no programming background; all you need to do is adjust the number set (the part of the code where it says "numbers = [") & target (the part of the code where it says "target =") to the numbers you have and then run this through a Python compiler and Tadaah! It'll print out the solution to your problem — or not. LOL! Feel free to try at your own risk and let me know how it goes for you.
Check out the example I set below with some randomly generated numbers.
def find_subset_sum(numbers, target, tolerance=1e-2):
# Scale the target and numbers for better precision with integers
scale = 100
scaled_numbers = [int(num * scale) for num in numbers]
scaled_target = int(target * scale)
dp = [None] * (scaled_target + 1)
dp[0] = []
for num in scaled_numbers:
for i in range(scaled_target, num - 1, -1):
if dp[i - num] is not None:
dp[i] = dp[i - num] + [num / scale]
result = dp[scaled_target]
return result if result is not None else []
# Example usage
numbers = [
256.28, 409.25, 4945.26, 177.00,
818.40, 191.00, 871.00, 205.00,
804.99, 125.00, 786.00, 1768.00,
568.55, 992.00, 110.00, 992.00,
2839.00, 837.60, 112.75, 106.34,
1611.76, 2410.83, 1488.00, 999.16,
999.65, 5850.00, 5850.00, 5850.00
]
target = 3442.60
result = find_subset_sum(numbers, target)
print(f"Subset that sums up to {target}: {result}")
# This is licensed under the GNU General Public License. Download license for more details.
Here’s a deeper breakdown to help you better understand it.
The Function: The find_subset_sum function is designed to find a combination of numbers that add up to a specific target value, taking into account a small tolerance to manage any floating-point precision issues.
Scale Values: We use a scaling factor, set to 100, to convert floating-point numbers into whole numbers. This makes our calculations more precise.
Scale Numbers: Each number in the list is multiplied by the scaling factor and converted to an integer. This helps in handling floating-point numbers more accurately.
Scale Target: Similarly, the target value is also scaled to match the format of the numbers for consistency in calculations.
Initialize DP Array: We create a list called dp where each position represents a possible sum up to our scaled target. Initially, all entries are set to None, which means we haven’t found any sums yet.
Set Base Case: We set dp[0] to an empty list, signifying that a sum of zero can be achieved with no numbers.
Iterate Through Numbers: We loop through each number in the scaled list to process them.
Update DP Array: We check each possible sum from our target down to the current number, making sure we use each number only once. If a valid subset that adds up to a certain sum is found, we update our list to include the current number, reverting it back to its original scale.
Check and Update: We verify if we can form the current sum by checking if the previous sum (i - num) has a valid subset. If it does, we update our list to include the current number.
Get Result: We retrieve the subset from our list that adds up to the scaled target, if it exists.
Return Result: If a valid subset is found, we return it; otherwise, we return an empty list.
Define Numbers and Target: We set up a list of numbers and specify the target sum we want to achieve.
Call Function: We invoke the find_subset_sum function with our numbers and target to find the subset.
Print Result: Finally, we display the subset that adds up to the target sum, if such a subset is found.