Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion knapsack/knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def knapsack(
weights: list[int],
values: list[int],
counter: int,
allow_repetition=False,
allow_repetition: bool = False,
) -> int:
"""
Returns the maximum value that can be put in a knapsack of a capacity cap,
Expand Down Expand Up @@ -62,6 +62,61 @@ def knapsack_recur(capacity: int, counter: int) -> int:
return knapsack_recur(capacity, counter)


def knapsack_with_count(
capacity: int,
weights: list[int],
values: list[int],
counter: int,
allow_repetition: bool = False,
) -> tuple[int, int]:
"""
Return both the maximum knapsack value and the number of optimal subsets.

The return value is ``(max_value, number_of_optimal_subsets)``.
If multiple choices produce the same maximum value, their counts are added.

Comment on lines +72 to +81
>>> cap = 50
>>> val = [60, 100, 120]
>>> w = [10, 20, 30]
>>> c = len(val)
>>> knapsack_with_count(cap, w, val, c)
(220, 1)
>>> knapsack_with_count(cap, w, val, c, True)
(300, 1)
>>> knapsack_with_count(3, [1, 2, 3], [1, 2, 3], 3)
(3, 2)
>>> knapsack_with_count(2, [1, 2], [1, 2], 2, True)
(2, 2)
"""

@lru_cache
def knapsack_recur(remaining_capacity: int, item_count: int) -> tuple[int, int]:
# Base Case: one empty subset yields value 0.
if item_count == 0 or remaining_capacity == 0:
return 0, 1

if weights[item_count - 1] > remaining_capacity:
return knapsack_recur(remaining_capacity, item_count - 1)

left_capacity = remaining_capacity - weights[item_count - 1]
included_value, included_count = knapsack_recur(
left_capacity, item_count if allow_repetition else item_count - 1
)
included_value += values[item_count - 1]

excluded_value, excluded_count = knapsack_recur(
remaining_capacity, item_count - 1
)

if included_value > excluded_value:
return included_value, included_count
if excluded_value > included_value:
return excluded_value, excluded_count
return included_value, included_count + excluded_count

return knapsack_recur(capacity, counter)


if __name__ == "__main__":
import doctest

Expand Down
18 changes: 18 additions & 0 deletions knapsack/tests/test_knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ def test_knapsack_repetition(self):
c = len(val)
assert k.knapsack(cap, w, val, c, True) == 300

def test_knapsack_with_count(self):
"""
test for maximum value and number of optimal subsets
"""
cap = 50
val = [60, 100, 120]
w = [10, 20, 30]
c = len(val)
assert k.knapsack_with_count(cap, w, val, c) == (220, 1)
assert k.knapsack_with_count(cap, w, val, c, True) == (300, 1)
Comment on lines +61 to +70

def test_knapsack_with_count_ties(self):
"""
test tie handling for counting optimal subsets
"""
assert k.knapsack_with_count(3, [1, 2, 3], [1, 2, 3], 3) == (3, 2)
assert k.knapsack_with_count(2, [1, 2], [1, 2], 2, True) == (2, 2)


if __name__ == "__main__":
unittest.main()
Loading