Skip to content

Commit e4eca06

Browse files
authored
Update knapsack.py
add DP implementation and improve recursive version
1 parent 5c07fad commit e4eca06

File tree

1 file changed

+67
-47
lines changed

1 file changed

+67
-47
lines changed

knapsack/knapsack.py

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
"""A recursive implementation of 0-N Knapsack Problem
2-
https://en.wikipedia.org/wiki/Knapsack_problem
31
"""
2+
Recursive and Dynamic Programming implementation of the 0-N Knapsack Problem.
43
5-
from __future__ import annotations
4+
References:
5+
https://en.wikipedia.org/wiki/Knapsack_problem
6+
"""
67

78
from functools import lru_cache
89

@@ -11,58 +12,77 @@ def knapsack(
1112
capacity: int,
1213
weights: list[int],
1314
values: list[int],
14-
counter: int,
15-
allow_repetition=False,
15+
allow_repetition: bool = False,
16+
method: str = "recursive",
1617
) -> int:
1718
"""
18-
Returns the maximum value that can be put in a knapsack of a capacity cap,
19-
whereby each weight w has a specific value val
20-
with option to allow repetitive selection of items
21-
22-
>>> cap = 50
23-
>>> val = [60, 100, 120]
24-
>>> w = [10, 20, 30]
25-
>>> c = len(val)
26-
>>> knapsack(cap, w, val, c)
27-
220
28-
29-
Given the repetition is NOT allowed,
30-
the result is 220 cause the values of 100 and 120 got the weight of 50
31-
which is the limit of the capacity.
32-
>>> knapsack(cap, w, val, c, True)
33-
300
34-
35-
Given the repetition is allowed,
36-
the result is 300 cause the values of 60*5 (pick 5 times)
37-
got the weight of 10*5 which is the limit of the capacity.
19+
Compute the maximum total value that can be obtained by placing items
20+
in a knapsack of given capacity.
21+
22+
Args:
23+
capacity (int): Maximum weight capacity of the knapsack.
24+
weights (list[int]): List of item weights.
25+
values (list[int]): List of item values corresponding to weights.
26+
allow_repetition (bool): If True, items can be taken multiple times.
27+
method (str): "recursive" (default) or "dp" for bottom-up approach.
28+
29+
Returns:
30+
int: Maximum achievable value.
31+
32+
Examples:
33+
>>> knapsack(50, [10, 20, 30], [60, 100, 120])
34+
220
35+
>>> knapsack(50, [10, 20, 30], [60, 100, 120], allow_repetition=True)
36+
300
3837
"""
38+
if len(weights) != len(values):
39+
raise ValueError("weights and values must have the same length")
40+
if capacity < 0:
41+
raise ValueError("capacity must be non-negative")
42+
if method not in ("recursive", "dp"):
43+
raise ValueError("method must be 'recursive' or 'dp'")
44+
45+
n_items = len(weights)
46+
47+
if method == "dp":
48+
return _knapsack_dp(capacity, weights, values, allow_repetition)
3949

40-
@lru_cache
41-
def knapsack_recur(capacity: int, counter: int) -> int:
42-
# Base Case
43-
if counter == 0 or capacity == 0:
50+
@lru_cache(maxsize=None)
51+
def recur(cap: int, idx: int) -> int:
52+
if idx == 0 or cap == 0:
4453
return 0
54+
if weights[idx - 1] > cap:
55+
return recur(cap, idx - 1)
56+
include_value = values[idx - 1] + recur(
57+
cap - weights[idx - 1],
58+
idx if allow_repetition else idx - 1,
59+
)
60+
exclude_value = recur(cap, idx - 1)
61+
return max(include_value, exclude_value)
4562

46-
# If weight of the nth item is more than Knapsack of capacity,
47-
# then this item cannot be included in the optimal solution,
48-
# else return the maximum of two cases:
49-
# (1) nth item included only once (0-1), if allow_repetition is False
50-
# nth item included one or more times (0-N), if allow_repetition is True
51-
# (2) not included
52-
if weights[counter - 1] > capacity:
53-
return knapsack_recur(capacity, counter - 1)
54-
else:
55-
left_capacity = capacity - weights[counter - 1]
56-
new_value_included = values[counter - 1] + knapsack_recur(
57-
left_capacity, counter - 1 if not allow_repetition else counter
58-
)
59-
without_new_value = knapsack_recur(capacity, counter - 1)
60-
return max(new_value_included, without_new_value)
61-
62-
return knapsack_recur(capacity, counter)
63+
return recur(capacity, n_items)
64+
65+
66+
def _knapsack_dp(capacity: int, weights: list[int], values: list[int], allow_repetition: bool) -> int:
67+
"""Iterative dynamic programming version of the knapsack problem."""
68+
n = len(weights)
69+
dp = [0] * (capacity + 1)
70+
71+
if allow_repetition:
72+
# Unbounded knapsack
73+
for cap in range(1, capacity + 1):
74+
for i in range(n):
75+
if weights[i] <= cap:
76+
dp[cap] = max(dp[cap], dp[cap - weights[i]] + values[i])
77+
else:
78+
# 0-1 knapsack
79+
for i in range(n):
80+
for cap in range(capacity, weights[i] - 1, -1):
81+
dp[cap] = max(dp[cap], dp[cap - weights[i]] + values[i])
82+
83+
return dp[capacity]
6384

6485

6586
if __name__ == "__main__":
6687
import doctest
67-
6888
doctest.testmod()

0 commit comments

Comments
 (0)