I wrote a solution to the Knapsack problem in Python, using a bottom-up dynamic programming algorithm. It correctly computes the optimal value, given a list of items with values and weights, and a maximum allowed weight.
Any critique on code style, comment style, readability, and best-practice would be greatly appreciated. I'm not sure how Pythonic the code is; filling in a matrix seems like a natural way of implementing dynamic programming, but it doesn't "feel" Pythonic (and is that a bad thing, in this case?).
Note that the comments are a little on the verbose side; as I was writing the algorithm, I tried to be as explicit about each step as possible, since I was trying to understand it to the fullest extent possible. If they are excessive (or just plain incorrect), feel free to comment (hah!) on it.
import sys
def knapsack(items, maxweight):
# Create an (N+1) by (W+1) 2-d list to contain the running values
# which are to be filled by the dynamic programming routine.
#
# There are N+1 rows because we need to account for the possibility
# of choosing from 0 up to and including N possible items.
# There are W+1 columns because we need to account for possible
# "running capacities" from 0 up to and including the maximum weight W.
bestvalues = [[0] * (maxweight + 1)
for i in xrange(len(items) + 1)]
# Enumerate through the items and fill in the best-value table
for i, (value, weight) in enumerate(items):
# Increment i, because the first row (0) is the case where no items
# are chosen, and is already initialized as 0, so we're skipping it
i += 1
for capacity in xrange(maxweight + 1):
# Handle the case where the weight of the current item is greater
# than the "running capacity" - we can't add it to the knapsack
if weight > capacity:
bestvalues[i][capacity] = bestvalues[i - 1][capacity]
else:
# Otherwise, we must choose between two possible candidate values:
# 1) the value of "running capacity" as it stands with the last item
# that was computed; if this is larger, then we skip the current item
# 2) the value of the current item plus the value of a previously computed
# set of items, constrained by the amount of capacity that would be left
# in the knapsack (running capacity - item's weight)
candidate1 = bestvalues[i - 1][capacity]
candidate2 = bestvalues[i - 1][capacity - weight] + value
# Just take the maximum of the two candidates; by doing this, we are
# in effect "setting in stone" the best value so far for a particular
# prefix of the items, and for a particular "prefix" of knapsack capacities
bestvalues[i][capacity] = max(candidate1, candidate2)
# Reconstruction
# Iterate through the values table, and check
# to see which of the two candidates were chosen. We can do this by simply
# checking if the value is the same as the value of the previous row. If so, then
# we say that the item was not included in the knapsack (this is how we arbitrarily
# break ties) and simply move the pointer to the previous row. Otherwise, we add
# the item to the reconstruction list and subtract the item's weight from the
# remaining capacity of the knapsack. Once we reach row 0, we're done
reconstruction = []
i = len(items)
j = maxweight
while i > 0:
if bestvalues[i][j] != bestvalues[i - 1][j]:
reconstruction.append(items[i - 1])
j -= items[i - 1][1]
i -= 1
# Reverse the reconstruction list, so that it is presented
# in the order that it was given
reconstruction.reverse()
# Return the best value, and the reconstruction list
return bestvalues[len(items)][maxweight], reconstruction
if __name__ == '__main__':
if len(sys.argv) != 2:
print('usage: knapsack.py [file]')
sys.exit(1)
filename = sys.argv[1]
with open(filename) as f:
lines = f.readlines()
maxweight = int(lines[0])
items = [map(int, line.split()) for line in lines[1:]]
bestvalue, reconstruction = knapsack(items, maxweight)
print('Best possible value: {0}'.format(bestvalue))
print('Items:')
for value, weight in reconstruction:
print('V: {0}, W: {1}'.format(value, weight))
The input file that it expects is as follows:
165
92 23
57 31
49 29
68 44
60 53
43 38
67 63
84 85
87 89
72 82
The first line contains the maximum weight allowed, and subsequent lines contain the items, represented by value-weight pairs.
enumerate
allows you to specify the starting index (e.g.for i,item in enumerate(items,1):
would havei
begin at 1. \$\endgroup\$enumerate
used that way. I'll definitely keep it in mind. \$\endgroup\$