66using dynamic programming.
77"""
88
9+ from __future__ import annotations
910
10- def mf_knapsack (i , wt , val , j ):
11+ from functools import lru_cache
12+
13+
14+ def mf_knapsack (i : int , wt : list [int ], val : list [int ], j : int ) -> int :
1115 """
12- This code involves the concept of memory functions. Here we solve the subproblems
13- which are needed unlike the below example
14- F is a 2D array with ``-1`` s filled up
16+ Return the optimal value for the 0/1 knapsack problem using memoization.
17+
18+ This implementation caches subproblems with ``functools.lru_cache`` and avoids
19+ global mutable state.
20+
21+ >>> mf_knapsack(4, [4, 3, 2, 3], [3, 2, 4, 4], 6)
22+ 8
23+ >>> mf_knapsack(3, [10, 20, 30], [60, 100, 120], 50)
24+ 220
25+ >>> mf_knapsack(0, [1], [10], 50)
26+ 0
1527 """
16- global f # a global dp table for knapsack
17- if f [i ][j ] < 0 :
18- if j < wt [i - 1 ]:
19- val = mf_knapsack (i - 1 , wt , val , j )
20- else :
21- val = max (
22- mf_knapsack (i - 1 , wt , val , j ),
23- mf_knapsack (i - 1 , wt , val , j - wt [i - 1 ]) + val [i - 1 ],
24- )
25- f [i ][j ] = val
26- return f [i ][j ]
28+ if i < 0 :
29+ raise ValueError ("The number of items to consider cannot be negative." )
30+ if j < 0 :
31+ raise ValueError ("The knapsack capacity cannot be negative." )
32+ if len (wt ) != len (val ):
33+ raise ValueError ("The number of weights must match the number of values." )
34+ if i > len (wt ):
35+ raise ValueError ("The number of items to consider cannot exceed input length." )
36+
37+ weights = tuple (wt )
38+ values = tuple (val )
39+
40+ @lru_cache (maxsize = None )
41+ def solve (item_count : int , capacity : int ) -> int :
42+ if item_count == 0 or capacity == 0 :
43+ return 0
44+ if weights [item_count - 1 ] > capacity :
45+ return solve (item_count - 1 , capacity )
46+ return max (
47+ solve (item_count - 1 , capacity ),
48+ solve (item_count - 1 , capacity - weights [item_count - 1 ])
49+ + values [item_count - 1 ],
50+ )
51+
52+ return solve (i , j )
2753
2854
29- def knapsack (w , wt , val , n ) :
55+ def knapsack (w : int , wt : list [ int ] , val : list [ int ] , n : int ) -> tuple [ int , list [ list [ int ]]] :
3056 dp = [[0 ] * (w + 1 ) for _ in range (n + 1 )]
3157
3258 for i in range (1 , n + 1 ):
@@ -36,10 +62,10 @@ def knapsack(w, wt, val, n):
3662 else :
3763 dp [i ][w_ ] = dp [i - 1 ][w_ ]
3864
39- return dp [n ][w_ ], dp
65+ return dp [n ][w ], dp
4066
4167
42- def knapsack_with_example_solution (w : int , wt : list , val : list ):
68+ def knapsack_with_example_solution (w : int , wt : list , val : list ) -> tuple [ int , set [ int ]] :
4369 """
4470 Solves the integer weights knapsack problem returns one of
4571 the several possible optimal subsets.
@@ -100,7 +126,9 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
100126 return optimal_val , example_optional_set
101127
102128
103- def _construct_solution (dp : list , wt : list , i : int , j : int , optimal_set : set ):
129+ def _construct_solution (
130+ dp : list [list [int ]], wt : list [int ], i : int , j : int , optimal_set : set [int ]
131+ ) -> None :
104132 """
105133 Recursively reconstructs one of the optimal subsets given
106134 a filled DP table and the vector of weights
@@ -139,10 +167,9 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
139167 wt = [4 , 3 , 2 , 3 ]
140168 n = 4
141169 w = 6
142- f = [[0 ] * (w + 1 )] + [[0 ] + [- 1 ] * (w + 1 ) for _ in range (n + 1 )]
143170 optimal_solution , _ = knapsack (w , wt , val , n )
144171 print (optimal_solution )
145- print (mf_knapsack (n , wt , val , w )) # switched the n and w
172+ print (mf_knapsack (n , wt , val , w ))
146173
147174 # testing the dynamic programming problem with example
148175 # the optimal subset for the above example are items 3 and 4
0 commit comments