Skip to content
Open
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
83 changes: 60 additions & 23 deletions dynamic_programming/knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,56 @@
using dynamic programming.
"""

from __future__ import annotations

def mf_knapsack(i, wt, val, j):
from collections.abc import Sequence
from functools import cache


def mf_knapsack(i: int, wt: Sequence[int], val: Sequence[int], j: int) -> int:
"""
This code involves the concept of memory functions. Here we solve the subproblems
which are needed unlike the below example
F is a 2D array with ``-1`` s filled up
Return the optimal value for the 0/1 knapsack problem using memoization.

This implementation caches subproblems with ``functools.cache`` and avoids
global mutable state.

>>> mf_knapsack(4, [4, 3, 2, 3], [3, 2, 4, 4], 6)
8
>>> mf_knapsack(3, [10, 20, 30], [60, 100, 120], 50)
220
>>> mf_knapsack(0, [1], [10], 50)
0
"""
global f # a global dp table for knapsack
if f[i][j] < 0:
if j < wt[i - 1]:
val = mf_knapsack(i - 1, wt, val, j)
else:
val = max(
mf_knapsack(i - 1, wt, val, j),
mf_knapsack(i - 1, wt, val, j - wt[i - 1]) + val[i - 1],
)
f[i][j] = val
return f[i][j]
if i < 0:
raise ValueError("The number of items to consider cannot be negative.")
if j < 0:
raise ValueError("The knapsack capacity cannot be negative.")
if len(wt) != len(val):
raise ValueError("The number of weights must match the number of values.")
if i > len(wt):
raise ValueError("The number of items to consider cannot exceed input length.")

weights = tuple(wt)
values = tuple(val)

@cache
def solve(item_count: int, capacity: int) -> int:
if item_count == 0 or capacity == 0:
return 0
if weights[item_count - 1] > capacity:
return solve(item_count - 1, capacity)
return max(
solve(item_count - 1, capacity),
solve(item_count - 1, capacity - weights[item_count - 1])
+ values[item_count - 1],
)

return solve(i, j)


def knapsack(w, wt, val, n):
def knapsack(
w: int, wt: Sequence[int], val: Sequence[int], n: int
) -> tuple[int, list[list[int]]]:
dp = [[0] * (w + 1) for _ in range(n + 1)]

for i in range(1, n + 1):
Expand All @@ -36,10 +65,12 @@ def knapsack(w, wt, val, n):
else:
dp[i][w_] = dp[i - 1][w_]

return dp[n][w_], dp
return dp[n][w], dp

Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

knapsack_with_example_solution() validates that wt/val are either lists or tuples, but the updated signature still annotates them as list. Consider annotating them as collections.abc.Sequence[int] (or list[int] | tuple[int, ...]) so the type hints align with the function's documented/validated inputs (see dynamic_programming/max_subarray_sum.py:16 for the existing Sequence[...] pattern).

Copilot uses AI. Check for mistakes.

def knapsack_with_example_solution(w: int, wt: list, val: list):
def knapsack_with_example_solution(
w: int, wt: Sequence[int], val: Sequence[int]
) -> tuple[int, set[int]]:
"""
Solves the integer weights knapsack problem returns one of
the several possible optimal subsets.
Expand Down Expand Up @@ -73,9 +104,14 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
ValueError: The number of weights must be the same as the number of values.
But got 4 weights and 3 values
"""
if not (isinstance(wt, (list, tuple)) and isinstance(val, (list, tuple))):
if not (
isinstance(wt, Sequence)
and not isinstance(wt, (str, bytes))
and isinstance(val, Sequence)
and not isinstance(val, (str, bytes))
):
raise ValueError(
"Both the weights and values vectors must be either lists or tuples"
"Both the weights and values vectors must be non-string sequences"
)

num_items = len(wt)
Expand All @@ -100,7 +136,9 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
return optimal_val, example_optional_set


def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
def _construct_solution(
dp: list[list[int]], wt: Sequence[int], i: int, j: int, optimal_set: set[int]
) -> None:
"""
Recursively reconstructs one of the optimal subsets given
a filled DP table and the vector of weights
Expand Down Expand Up @@ -139,10 +177,9 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
wt = [4, 3, 2, 3]
n = 4
w = 6
f = [[0] * (w + 1)] + [[0] + [-1] * (w + 1) for _ in range(n + 1)]
optimal_solution, _ = knapsack(w, wt, val, n)
print(optimal_solution)
print(mf_knapsack(n, wt, val, w)) # switched the n and w
print(mf_knapsack(n, wt, val, w))

# testing the dynamic programming problem with example
# the optimal subset for the above example are items 3 and 4
Expand Down
Loading