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

* function for the knapsack problem which returns one of the optimal subsets

* function for the knapsack problem which returns one of the optimal subsets

* function for the knapsack problem which returns one of the optimal subsets

* function for the knapsack problem which returns one of the optimal subsets

* function for the knapsack problem which returns one of the optimal subsets

* some pep8 cleanup too
This commit is contained in:
Maxwell Aladago 2019-08-19 01:39:39 -04:00 committed by Christian Clauss
parent 05c9a05f36
commit 5d46a4dd7b

View File

@ -1,7 +1,13 @@
""" """
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 This code involves the concept of memory functions. Here we solve the subproblems which are needed
unlike the below example unlike the below example
@ -9,34 +15,129 @@ def MF_knapsack(i,wt,val,j):
''' '''
global F # a global dp table for knapsack global F # a global dp table for knapsack
if F[i][j] < 0: if F[i][j] < 0:
if j < wt[i - 1]: if j < wt[i-1]:
val = MF_knapsack(i - 1,wt,val,j) val = MF_knapsack(i-1, wt, val, j)
else: 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 F[i][j] = val
return F[i][j] return F[i][j]
def knapsack(W, wt, val, n): def knapsack(W, wt, val, n):
dp = [[0 for i in range(W+1)]for j in range(n+1)] dp = [[0 for i in range(W+1)]for j in range(n+1)]
for i in range(1,n+1): for i in range(1,n+1):
for w in range(1,W+1): for w in range(1, W+1):
if(wt[i-1]<=w): if wt[i-1] <= w:
dp[i][w] = max(val[i-1]+dp[i-1][w-wt[i-1]],dp[i-1][w]) dp[i][w] = max(val[i-1] + dp[i-1][w-wt[i-1]], dp[i-1][w])
else: else:
dp[i][w] = dp[i-1][w] 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__': if __name__ == '__main__':
''' '''
Adding test case for knapsack Adding test case for knapsack
''' '''
val = [3,2,4,4] val = [3, 2, 4, 4]
wt = [4,3,2,3] wt = [4, 3, 2, 3]
n = 4 n = 4
w = 6 w = 6
F = [[0]*(w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)] F = [[0] * (w + 1)] + [[0] + [-1 for i in range(w + 1)] for j in range(n + 1)]
print(knapsack(w,wt,val,n)) optimal_solution, _ = knapsack(w,wt,val, n)
print(MF_knapsack(n,wt,val,w)) # switched the n and w 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)