Skip to content
Closed
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
66 changes: 66 additions & 0 deletions dynamic_programming/knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,72 @@ def knapsack(w, wt, val, n):
return dp[n][w_], dp


def knapsack_space_optimized(
capacity: int, weights: list[int], values: list[int], num_items: int
) -> int:
"""
Comment on lines +42 to +45
Solve the 0/1 knapsack problem with O(capacity) extra space.

It uses a 1D dynamic programming array and iterates capacities in reverse
for each item to avoid reusing the same item more than once.

>>> knapsack_space_optimized(50, [10, 20, 30], [60, 100, 120], 3)
220
>>> knapsack_space_optimized(0, [10, 20, 30], [60, 100, 120], 3)
0
>>> knapsack_space_optimized(6, [4, 3, 2, 3], [3, 2, 4, 4], 4)
8
>>> knapsack_space_optimized(-1, [1], [1], 1)
Traceback (most recent call last):
...
ValueError: The knapsack capacity cannot be negative.
>>> knapsack_space_optimized(1, [1], [1], -1)
Traceback (most recent call last):
...
ValueError: The number of items cannot be negative.
>>> knapsack_space_optimized(1, [1], [1], 2)
Traceback (most recent call last):
...
ValueError: The number of items exceeds the provided input lengths.
>>> knapsack_space_optimized(1, [-1], [1], 1)
Traceback (most recent call last):
...
ValueError: Weight at index 0 cannot be negative.
>>> knapsack_space_optimized(1, [1], [1.5], 1)
Traceback (most recent call last):
...
TypeError: Value at index 0 must be an integer.
"""
if num_items < 0:
raise ValueError("The number of items cannot be negative.")
if capacity < 0:
raise ValueError("The knapsack capacity cannot be negative.")
if num_items > len(weights) or num_items > len(values):
raise ValueError("The number of items exceeds the provided input lengths.")
Comment on lines +78 to +83
for item_index in range(num_items):
item_weight = weights[item_index]
item_value = values[item_index]
if not isinstance(item_weight, int):
msg = f"Weight at index {item_index} must be an integer."
raise TypeError(msg)
if item_weight < 0:
msg = f"Weight at index {item_index} cannot be negative."
raise ValueError(msg)
if not isinstance(item_value, int):
msg = f"Value at index {item_index} must be an integer."
raise TypeError(msg)

dp = [0] * (capacity + 1)
for item_index in range(num_items):
item_weight = weights[item_index]
item_value = values[item_index]
for current_capacity in range(capacity, item_weight - 1, -1):
dp[current_capacity] = max(
dp[current_capacity], item_value + dp[current_capacity - item_weight]
)
return dp[capacity]


def knapsack_with_example_solution(w: int, wt: list, val: list):
"""
Solves the integer weights knapsack problem returns one of
Expand Down
63 changes: 63 additions & 0 deletions maths/repunit_theorem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
Utilities related to repunits and a classical repunit divisibility theorem.

A repunit of length ``k`` is the number made of ``k`` ones:
``R_k = 11...1``.

For every positive integer ``n`` with ``gcd(n, 10) = 1``,
there exists a repunit ``R_k`` divisible by ``n``.
"""

from math import gcd


def has_repunit_multiple(divisor: int) -> bool:
"""
Check whether a divisor admits a repunit multiple.

>>> has_repunit_multiple(7)
True
>>> has_repunit_multiple(13)
True
>>> has_repunit_multiple(2)
False
>>> has_repunit_multiple(25)
False
"""
if divisor <= 0:
raise ValueError("divisor must be a positive integer")
return gcd(divisor, 10) == 1
Comment on lines +27 to +29


def least_repunit_length(divisor: int) -> int:
"""
Return the smallest length ``k`` such that repunit ``R_k`` is divisible by divisor.

Uses modular arithmetic to avoid constructing huge integers.

>>> least_repunit_length(3)
3
>>> least_repunit_length(7)
6
>>> least_repunit_length(41)
5
"""
if divisor <= 0:
raise ValueError("divisor must be a positive integer")
if not has_repunit_multiple(divisor):
raise ValueError("divisor must be coprime to 10")

remainder = 0
for length in range(1, divisor + 1):
remainder = (remainder * 10 + 1) % divisor
if remainder == 0:
return length

# Unreachable when gcd(divisor, 10) == 1 (pigeonhole principle theorem).
raise ArithmeticError("no repunit length found for divisor")


if __name__ == "__main__":
import doctest

doctest.testmod()
Loading