Skip to content

Commit bc25a22

Browse files
committed
Add knapsack variant counting optimal subsets
1 parent fc2f947 commit bc25a22

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

knapsack/knapsack.py

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def knapsack(
1212
weights: list[int],
1313
values: list[int],
1414
counter: int,
15-
allow_repetition=False,
15+
allow_repetition: bool = False,
1616
) -> int:
1717
"""
1818
Returns the maximum value that can be put in a knapsack of a capacity cap,
@@ -62,6 +62,61 @@ def knapsack_recur(capacity: int, counter: int) -> int:
6262
return knapsack_recur(capacity, counter)
6363

6464

65+
def knapsack_with_count(
66+
capacity: int,
67+
weights: list[int],
68+
values: list[int],
69+
counter: int,
70+
allow_repetition: bool = False,
71+
) -> tuple[int, int]:
72+
"""
73+
Return both the maximum knapsack value and the number of optimal subsets.
74+
75+
The return value is ``(max_value, number_of_optimal_subsets)``.
76+
If multiple choices produce the same maximum value, their counts are added.
77+
78+
>>> cap = 50
79+
>>> val = [60, 100, 120]
80+
>>> w = [10, 20, 30]
81+
>>> c = len(val)
82+
>>> knapsack_with_count(cap, w, val, c)
83+
(220, 1)
84+
>>> knapsack_with_count(cap, w, val, c, True)
85+
(300, 1)
86+
>>> knapsack_with_count(3, [1, 2, 3], [1, 2, 3], 3)
87+
(3, 2)
88+
>>> knapsack_with_count(2, [1, 2], [1, 2], 2, True)
89+
(2, 2)
90+
"""
91+
92+
@lru_cache
93+
def knapsack_recur(remaining_capacity: int, item_count: int) -> tuple[int, int]:
94+
# Base Case: one empty subset yields value 0.
95+
if item_count == 0 or remaining_capacity == 0:
96+
return 0, 1
97+
98+
if weights[item_count - 1] > remaining_capacity:
99+
return knapsack_recur(remaining_capacity, item_count - 1)
100+
101+
left_capacity = remaining_capacity - weights[item_count - 1]
102+
included_value, included_count = knapsack_recur(
103+
left_capacity, item_count if allow_repetition else item_count - 1
104+
)
105+
included_value += values[item_count - 1]
106+
107+
excluded_value, excluded_count = knapsack_recur(
108+
remaining_capacity, item_count - 1
109+
)
110+
111+
if included_value > excluded_value:
112+
return included_value, included_count
113+
if excluded_value > included_value:
114+
return excluded_value, excluded_count
115+
return included_value, included_count + excluded_count
116+
117+
return knapsack_recur(capacity, counter)
118+
119+
65120
if __name__ == "__main__":
66121
import doctest
67122

knapsack/tests/test_knapsack.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,24 @@ def test_knapsack_repetition(self):
5858
c = len(val)
5959
assert k.knapsack(cap, w, val, c, True) == 300
6060

61+
def test_knapsack_with_count(self):
62+
"""
63+
test for maximum value and number of optimal subsets
64+
"""
65+
cap = 50
66+
val = [60, 100, 120]
67+
w = [10, 20, 30]
68+
c = len(val)
69+
assert k.knapsack_with_count(cap, w, val, c) == (220, 1)
70+
assert k.knapsack_with_count(cap, w, val, c, True) == (300, 1)
71+
72+
def test_knapsack_with_count_ties(self):
73+
"""
74+
test tie handling for counting optimal subsets
75+
"""
76+
assert k.knapsack_with_count(3, [1, 2, 3], [1, 2, 3], 3) == (3, 2)
77+
assert k.knapsack_with_count(2, [1, 2], [1, 2], 2, True) == (2, 2)
78+
6179

6280
if __name__ == "__main__":
6381
unittest.main()

0 commit comments

Comments
 (0)