Skip to content

Enhancement of the knapsack algorithm with memorization and generalisation #9295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion knapsack/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# A naive recursive implementation of 0-1 Knapsack Problem
# A recursive implementation of 0-N Knapsack Problem

This overview is taken from:

Expand Down
71 changes: 48 additions & 23 deletions knapsack/knapsack.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,68 @@
""" A naive recursive implementation of 0-1 Knapsack Problem
""" A recursive implementation of 0-N Knapsack Problem
https://en.wikipedia.org/wiki/Knapsack_problem
"""
from __future__ import annotations

from functools import lru_cache

def knapsack(capacity: int, weights: list[int], values: list[int], counter: int) -> int:

def knapsack(
capacity: int,
weights: list[int],
values: list[int],
counter: int,
allow_repetition=False,
) -> int:
"""
Returns the maximum value that can be put in a knapsack of a capacity cap,
whereby each weight w has a specific value val.
whereby each weight w has a specific value val
with option to allow repetitive selection of items

>>> cap = 50
>>> val = [60, 100, 120]
>>> w = [10, 20, 30]
>>> c = len(val)
>>> knapsack(cap, w, val, c)
>>> knapsack(cap, w, val, c, False)
220

The result is 220 cause the values of 100 and 120 got the weight of 50
Given the repetition is NOT allowed,
the result is 220 cause the values of 100 and 120 got the weight of 50
which is the limit of the capacity.
>>> knapsack(cap, w, val, c, True)
300

Given the repetition is allowed,
the result is 300 cause the values of 60*5 (pick 5 times)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The algorithm has been updated to allow repetition. An example/testcase where repetition is used would be appropriate.

which is the limit of the capacity.
"""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to consider a few things before getting into the algorithm:

  1. Handle any input constraints. Ensure the inputs make sense and raise an exception for the user if they don't
  • non-positive numbers
  • a capacity lower than the weight of any single object
  • etc
  1. Before getting into the main algorithm, you can go ahead and remove any items that have a weight above the capacity of the knapsack.

# Base Case
if counter == 0 or capacity == 0:
return 0

# If weight of the nth item is more than Knapsack of capacity,
# then this item cannot be included in the optimal solution,
# else return the maximum of two cases:
# (1) nth item included
# (2) not included
if weights[counter - 1] > capacity:
return knapsack(capacity, weights, values, counter - 1)
else:
left_capacity = capacity - weights[counter - 1]
new_value_included = values[counter - 1] + knapsack(
left_capacity, weights, values, counter - 1
)
without_new_value = knapsack(capacity, weights, values, counter - 1)
return max(new_value_included, without_new_value)
@lru_cache
def knapsack_recur(cap: int, c: int) -> int:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the lru_cache is a nice improvement. I might be worth exploring adding allow_repitition as a parameter, since lru_cache uses these parameters as a part of its memorization key.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cap and c should be given full descriptive names.

# Base Case
if c == 0 or cap == 0:
return 0

# If weight of the nth item is more than Knapsack of capacity,
# then this item cannot be included in the optimal solution,
# else return the maximum of two cases:
# (1) not included
# (2) nth item included one or more times (0-N), if allow_repetition is true
# nth item included only once (0-1), if allow_repetition is false
if weights[c - 1] > cap:
return knapsack_recur(cap, c - 1)
else:
without_new_value = knapsack_recur(cap, c - 1)
if allow_repetition:
new_value_included = values[c - 1] + knapsack_recur(
cap - weights[c - 1], c
)
else:
new_value_included = values[c - 1] + knapsack_recur(
cap - weights[c - 1], c - 1
)
return max(new_value_included, without_new_value)

return knapsack_recur(capacity, counter)


if __name__ == "__main__":
Expand Down
12 changes: 11 additions & 1 deletion knapsack/tests/test_knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,17 @@ def test_knapsack(self):
val = [60, 100, 120]
w = [10, 20, 30]
c = len(val)
self.assertEqual(k.knapsack(cap, w, val, c), 220)
self.assertEqual(k.knapsack(cap, w, val, c, False), 220)

def test_knapsack_repetition(self):
"""
test for the knapsack
"""
cap = 50
val = [60, 100, 120]
w = [10, 20, 30]
c = len(val)
self.assertEqual(k.knapsack(cap, w, val, c, True), 300)


if __name__ == "__main__":
Expand Down