Skip to content

ENH: Added a functionality to make it possible to reconstruct an optimal subset for the dynamic programming problem #1139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 19, 2019
131 changes: 116 additions & 15 deletions dynamic_programming/knapsack.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,143 @@
"""
Given weights and values of n items, put these items in a knapsack of capacity W to get the maximum total value in the knapsack.
Given weights and values of n items, put these items in a knapsack of
capacity W to get the maximum total value in the knapsack.

Note that only the integer weights 0-1 knapsack problem is solvable
using dynamic programming.
"""
def MF_knapsack(i,wt,val,j):


def MF_knapsack(i, wt, val, j):
'''
This code involves the concept of memory functions. Here we solve the subproblems which are needed
unlike the below example
F is a 2D array with -1s filled up
'''
global F # a global dp table for knapsack
if F[i][j] < 0:
if j < wt[i - 1]:
val = MF_knapsack(i - 1,wt,val,j)
if j < wt[i-1]:
val = MF_knapsack(i-1, wt, val, j)
else:
val = max(MF_knapsack(i - 1,wt,val,j),MF_knapsack(i - 1,wt,val,j - wt[i - 1]) + val[i - 1])
val = max(MF_knapsack(i-1, wt, val, j),
MF_knapsack(i-1, wt, val, j - wt[i-1]) + val[i-1])
F[i][j] = val
return F[i][j]


def knapsack(W, wt, val, n):
dp = [[0 for i in range(W+1)]for j in range(n+1)]

for i in range(1,n+1):
for w in range(1,W+1):
if(wt[i-1]<=w):
dp[i][w] = max(val[i-1]+dp[i-1][w-wt[i-1]],dp[i-1][w])
for w in range(1, W+1):
if wt[i-1] <= w:
dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
else:
dp[i][w] = dp[i-1][w]

return dp[n][w]
return dp[n][W], dp


def knapsack_with_example_solution(W: int, wt: list, val:list):
"""
Solves the integer weights knapsack problem returns one of
the several possible optimal subsets.

Parameters
---------

W: int, the total maximum weight for the given knapsack problem.
wt: list, the vector of weights for all items where wt[i] is the weight
of the ith item.
val: list, the vector of values for all items where val[i] is the value
of te ith item

Returns
-------
optimal_val: float, the optimal value for the given knapsack problem
example_optional_set: set, the indices of one of the optimal subsets
which gave rise to the optimal value.

Examples
-------
>>> knapsack_with_example_solution(10, [1, 3, 5, 2], [10, 20, 100, 22])
(142, {2, 3, 4})
>>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4, 4])
(8, {3, 4})
>>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4])
Traceback (most recent call last):
...
ValueError: The number of weights must be the same as the number of values.
But got 4 weights and 3 values
"""
if not (isinstance(wt, (list, tuple)) and isinstance(val, (list, tuple))):
raise ValueError("Both the weights and values vectors must be either lists or tuples")

num_items = len(wt)
if num_items != len(val):
raise ValueError("The number of weights must be the "
"same as the number of values.\nBut "
"got {} weights and {} values".format(num_items, len(val)))
for i in range(num_items):
if not isinstance(wt[i], int):
raise TypeError("All weights must be integers but "
"got weight of type {} at index {}".format(type(wt[i]), i))

optimal_val, dp_table = knapsack(W, wt, val, num_items)
example_optional_set = set()
_construct_solution(dp_table, wt, num_items, W, example_optional_set)

return optimal_val, example_optional_set


def _construct_solution(dp:list, wt:list, i:int, j:int, optimal_set:set):
"""
Recursively reconstructs one of the optimal subsets given
a filled DP table and the vector of weights

Parameters
---------

dp: list of list, the table of a solved integer weight dynamic programming problem

wt: list or tuple, the vector of weights of the items
i: int, the index of the item under consideration
j: int, the current possible maximum weight
optimal_set: set, the optimal subset so far. This gets modified by the function.

Returns
-------
None

"""
# for the current item i at a maximum weight j to be part of an optimal subset,
# the optimal value at (i, j) must be greater than the optimal value at (i-1, j).
# where i - 1 means considering only the previous items at the given maximum weight
if i > 0 and j > 0:
if dp[i - 1][j] == dp[i][j]:
_construct_solution(dp, wt, i - 1, j, optimal_set)
else:
optimal_set.add(i)
_construct_solution(dp, wt, i - 1, j - wt[i-1], optimal_set)


if __name__ == '__main__':
'''
Adding test case for knapsack
'''
val = [3,2,4,4]
wt = [4,3,2,3]
val = [3, 2, 4, 4]
wt = [4, 3, 2, 3]
n = 4
w = 6
F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)]
print(knapsack(w,wt,val,n))
print(MF_knapsack(n,wt,val,w)) # switched the n and w

F = [[0] * (w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)]
optimal_solution, _ = knapsack(w,wt,val, n)
print(optimal_solution)
print(MF_knapsack(n,wt,val,w)) # switched the n and w

# testing the dynamic programming problem with example
# the optimal subset for the above example are items 3 and 4
optimal_solution, optimal_subset = knapsack_with_example_solution(w, wt, val)
assert optimal_solution == 8
assert optimal_subset == {3, 4}
print("optimal_value = ", optimal_solution)
print("An optimal subset corresponding to the optimal value", optimal_subset)