From Coursera's Algorithmic Toolbox course.
Problem Introduction
You are given a set of bars of gold and your goal is to take as much gold as possible into your bag. There is just one copy of each bar and for each bar you can either take it or not (hence you cannot take a fraction of a bar).
Problem Description
Task. Given gold bars, find the maximum weight of gold that fits into a bag of capacity . Input Format. The first line of the input contains the capacity of a knapsack and the number of bars of gold. The next line contains integers 0,1, . . . ,−1 defining the weights of the bars of gold.
Constraints. 1 ≤ ≤ 10^4; 1 ≤ ≤ 300; 0 ≤ 0, . . . , −1 ≤ 10^5.
Output Format. Output the maximum weight of gold that fits into a knapsack of capacity .
My solution in Python using dynamic programming:
def optimal_weight(W, w):
golds = [0] + w
gold_dict = {}
for i in range(0, W+1):
gold_dict[(i, golds[0])] = 0
for i in golds:
gold_dict[(0, i)] = 0
for i in range(1, len(golds)):
for weight in range(1, W+1):
gold_dict[(weight, golds[i])] = gold_dict[(weight, golds[i-1])]
if golds[i] <= weight:
val = gold_dict[(weight-golds[i], golds[i-1])] + golds[i]
if gold_dict[(weight, golds[i])] < val:
gold_dict[(weight, golds[i])] = val
return max(gold_dict.values())
The second input would be a list of gold bars with their weight as integer. For the input of:
10, [1, 4, 8]
The result should be an integer 9 (which is 1+8).
I have tested a list of examples including some extreme value such as:
1, [0]
And for all of them, the result seems to be right. However, the final test is keeping showing one of the tests is not passed due to the wrong answer. Hence there must be a bug within my code. Yet I am having difficulty locating it. (the assignment test would not release testing parameters, hence I could not get the input that triggers the wrong answer)
Could someone please advise what is the potential issue? Even a hint would be really appreciated. Thanks a lot.
Edit:
I have added the main function to show how input was read, though I do not think that links to the potential bug:
if __name__ == '__main__':
input = sys.stdin.read()
W, n, *w = list(map(int, input.split()))
print(optimal_weight(W, w))
The sys.stdin will read a testing txt file with example input as follows:
10 3
1 4 8
Which means a bag capacity of 10, 3 gold bars, weight for 1, 4, 8 each.
The main method comes with the question and is not modified.
Edit2 Answer:
Thanks for Arty's advice below, the issue came from the use of gold[i] as part of the tuple key for gold_dict. If there are multiple gold bars that have the same weight, their index in the gold[] list will be different (different i) but their gold[i] value will be the same. In such a condition, the tuple key (weight, gold[i]) will potentially be referring to the wrong object.
I have done a random test on Arty's correct code with my wrong one to find the parameters that could trigger the bug, which shows up after ~3000 runs:
209 38
16, 21, 21, 96, 129, 144, 159, 253, 254, 259, 259, 267, 285, 290, 304, 351, 351, 383, 411, 429, 493, 494, 527, 530, 534, 596, 619, 625, 692, 717, 727, 727, 745, 772, 833, 853, 856, 946
For this example, my code will get an answer 208 while the correct answer is 202.
Fix is very simple actually, just change gold[i] to i for all key tuples:
def optimal_weight(W, w):
golds = [0] + w
gold_dict = {}
for i in range(0, W+1):
gold_dict[(i, 0)] = 0
for i in range(0, len(golds)):
gold_dict[(0, i)] = 0
for i in range(1, len(golds)):
for weight in range(1, W+1):
gold_dict[(weight, i)] = gold_dict[(weight, (i-1))]
if golds[i] <= weight:
val = gold_dict[(weight-golds[i], i-1)] + golds[i]
if gold_dict[(weight, i)] < val:
gold_dict[(weight, i)] = val
return max(gold_dict.values())