I want to build a function that can look at the raw material on hand information I provide, then come up with combinations of inputs to achieve a desired number of finished products.
on_hand_inventory = {
60: 370,
66: 180,
70: 752,
74: 97,
76: 5,
77: 11,
80: 40
}
The keys of the on_hand_inventory dictionary are the product's length in feet, and the values is the number of units on-hand.
I want to make finished products that are 1600 feet long, and I want to make 60 finished products. If this isn't possible, I want to know the combinations to use per finished product to get the most out of my inventory.
This is the code I have so far:
from itertools import combinations_with_replacement
from collections import Counter
on_hand_inventory = {
60: 370,
66: 180,
70: 752,
74: 97,
76: 5,
77: 11,
80: 40
}
min_length = min([k for k,v in on_hand_inventory.items()])
target_length = 1600
num_finished_products_need = 60
product_lengths = sorted(on_hand_inventory.keys(), reverse=True)
max_pieces = 1600//min_length
# Step 1: Generate valid combinations
def find_combinations(product_lengths, target_length, max_pieces):
valid_combos = []
for num_pieces in range(max_pieces + 1):
for combo in combinations_with_replacement(product_lengths, num_pieces):
if sum(combo) == target_length:
valid_combos.append(Counter(combo))
return valid_combos
valid_combos = find_combinations(product_lengths, target_length, max_pieces)
# Step 3: Select 60 combinations without exceeding inventory
import random
def select_combinations(valid_combos, on_hand_inventory, num_finished_products_need, max_attempts=10000):
best_selected = []
best_used_inventory = Counter()
for attempt in range(max_attempts):
random.shuffle(valid_combos)
selected_combos = []
used_inventory = Counter()
for combo in valid_combos:
temp_inventory = used_inventory + combo
if all(temp_inventory[length] <= on_hand_inventory[length] for length in on_hand_inventory):
selected_combos.append(combo)
used_inventory += combo
if len(selected_combos) == num_finished_products_need:
return selected_combos, used_inventory # full success
# Save the best partial result
if len(selected_combos) > len(best_selected):
best_selected = selected_combos
best_used_inventory = used_inventory
return best_selected, best_used_inventory # best attempt
selected_combos, inventory_used = select_combinations(valid_combos,on_hand_inventory,num_finished_products_need,max_attempts=1000)
# Step 4: Format the output
for i, combo in enumerate(selected_combos, 1):
parts = [f"{count} pieces of {length}'" for length, count in sorted(combo.items())]
print(f"String {i}: " + ", ".join(parts))
# Step 5: Print remaining quantities by length
if inventory_used:
print("\nRemaining quantities by length:")
for length in sorted(on_hand_inventory):
remaining = on_hand_inventory[length] - inventory_used.get(length, 0)
print(f"{length}': {remaining} pieces remaining")
if inventory_used:
print("\nUsed quantities by length:")
for length in sorted(inventory_used):
print(f"{length}': {inventory_used[length]} pieces remaining")
This code works somewhat; however, it stops before using obvious combinations. For example, the code consistently returns a similar version of the following:
38 finished products made
Remaining quantities by length:
60': 65 pieces remaining
66': 13 pieces remaining
70': 467 pieces remaining
74': 0 pieces remaining
76': 0 pieces remaining
77': 1 pieces remaining
80': 0 pieces remaining
As you can see there is plenty of 60' and 70' product left to produce more finished products. Combining 1x 60' and 22x 70' would make 22 more finished products than the function returned.
Any advice is welcome!
Here is the full output of the Python code:
Finished Product #1: 1 pieces of 66', 12 pieces of 70', 5 pieces of 76', 2 pieces of 77', 2 pieces of 80'
Finished Product #2: 1 pieces of 60', 6 pieces of 66', 13 pieces of 70', 1 pieces of 74', 2 pieces of 80'
Finished Product #3: 2 pieces of 60', 9 pieces of 66', 2 pieces of 70', 9 pieces of 74', 1 pieces of 80'
Finished Product #4: 5 pieces of 70', 2 pieces of 74', 6 pieces of 77', 8 pieces of 80'
Finished Product #5: 2 pieces of 60', 6 pieces of 66', 3 pieces of 70', 2 pieces of 77', 9 pieces of 80'
Finished Product #6: 11 pieces of 60', 2 pieces of 66', 3 pieces of 70', 7 pieces of 74', 1 pieces of 80'
Finished Product #7: 15 pieces of 60', 1 pieces of 66', 8 pieces of 70', 1 pieces of 74'
Finished Product #8: 5 pieces of 60', 4 pieces of 66', 3 pieces of 70', 9 pieces of 74', 2 pieces of 80'
Finished Product #9: 2 pieces of 60', 1 pieces of 66', 4 pieces of 70', 11 pieces of 74', 4 pieces of 80'
Finished Product #10: 3 pieces of 60', 6 pieces of 66', 9 pieces of 70', 1 pieces of 74', 4 pieces of 80'
Finished Product #11: 5 pieces of 60', 14 pieces of 70', 4 pieces of 80'
Finished Product #12: 17 pieces of 60', 1 pieces of 66', 4 pieces of 70', 1 pieces of 74', 2 pieces of 80'
Finished Product #13: 2 pieces of 60', 3 pieces of 66', 14 pieces of 70', 3 pieces of 74', 1 pieces of 80'
Finished Product #14: 15 pieces of 60', 2 pieces of 66', 6 pieces of 70', 2 pieces of 74'
Finished Product #15: 6 pieces of 60', 8 pieces of 66', 7 pieces of 70', 3 pieces of 74'
Finished Product #16: 3 pieces of 60', 15 pieces of 70', 5 pieces of 74'
Finished Product #17: 22 pieces of 60', 2 pieces of 66', 2 pieces of 74'
Finished Product #18: 4 pieces of 60', 14 pieces of 66', 2 pieces of 70', 4 pieces of 74'
Finished Product #19: 8 pieces of 60', 2 pieces of 66', 12 pieces of 70', 2 pieces of 74'
Finished Product #20: 8 pieces of 60', 16 pieces of 70'
Finished Product #21: 11 pieces of 60', 10 pieces of 66', 4 pieces of 70'
Finished Product #22: 13 pieces of 60', 6 pieces of 66', 5 pieces of 70', 1 pieces of 74'
Finished Product #23: 22 pieces of 60', 1 pieces of 66', 2 pieces of 70', 1 pieces of 74'
Finished Product #24: 10 pieces of 60', 9 pieces of 70', 5 pieces of 74'
Finished Product #25: 8 pieces of 60', 1 pieces of 66', 14 pieces of 70', 1 pieces of 74'
Finished Product #26: 8 pieces of 60', 3 pieces of 66', 10 pieces of 70', 3 pieces of 74'
Finished Product #27: 5 pieces of 60', 3 pieces of 66', 2 pieces of 70', 13 pieces of 74'
Finished Product #28: 1 pieces of 60', 22 pieces of 70'
Finished Product #29: 5 pieces of 60', 8 pieces of 70', 10 pieces of 74'
Finished Product #30: 13 pieces of 60', 5 pieces of 66', 7 pieces of 70'
Finished Product #31: 20 pieces of 60', 5 pieces of 66', 1 pieces of 70'
Finished Product #32: 20 pieces of 66', 4 pieces of 70'
Finished Product #33: 6 pieces of 60', 5 pieces of 66', 13 pieces of 70'
Finished Product #34: 2 pieces of 60', 15 pieces of 66', 7 pieces of 70'
Finished Product #35: 15 pieces of 60', 10 pieces of 70'
Finished Product #36: 9 pieces of 60', 15 pieces of 66', 1 pieces of 70'
Finished Product #37: 4 pieces of 60', 10 pieces of 66', 10 pieces of 70'
Finished Product #38: 22 pieces of 60', 4 pieces of 70'
Remaining quantities by length:
60': 65 pieces remaining
66': 13 pieces remaining
70': 467 pieces remaining
74': 0 pieces remaining
76': 0 pieces remaining
77': 1 pieces remaining
80': 0 pieces remaining