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:
"""
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.")
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


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()
60 changes: 60 additions & 0 deletions other/bank_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""
Simple BankAccount class demonstrating core OOP concepts.
"""


class BankAccount:
"""
Basic bank account model with encapsulated account number and balance.

>>> account = BankAccount("ACC-1001", initial_balance=100.0)
>>> account.balance
100.0
>>> account.deposit(50)
150.0
>>> account.withdraw(20)
130.0
>>> account.account_number
'ACC-1001'
"""

def __init__(self, account_number: str, initial_balance: float = 0.0) -> None:
if not account_number:
raise ValueError("account_number must be provided")
if initial_balance < 0:
raise ValueError("initial_balance cannot be negative")

self.__account_number = account_number
self._balance = float(initial_balance)

@property
def account_number(self) -> str:
"""Read-only public accessor for the encapsulated account number."""
return self.__account_number

@property
def balance(self) -> float:
"""Current account balance."""
return self._balance

def deposit(self, amount: float) -> float:
"""Deposit a positive amount and return updated balance."""
if amount <= 0:
raise ValueError("deposit amount must be positive")
self._balance += amount
return self._balance

def withdraw(self, amount: float) -> float:
"""Withdraw a positive amount and return updated balance."""
if amount <= 0:
raise ValueError("withdraw amount must be positive")
if amount > self._balance:
raise ValueError("insufficient funds")
self._balance -= amount
return self._balance


if __name__ == "__main__":
import doctest

doctest.testmod()
52 changes: 52 additions & 0 deletions other/grocery_store_cart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Console-free grocery cart logic.
"""


class GroceryStoreCart:
"""
Maintain cart item quantities and compute totals.

>>> cart = GroceryStoreCart({"apple": 1.5, "milk": 2.0})
>>> cart.add_item("apple", 2)
>>> cart.add_item("milk")
>>> round(cart.total_price(), 2)
5.0
>>> cart.remove_item("apple")
>>> round(cart.total_price(), 2)
3.5
"""

def __init__(self, price_catalog: dict[str, float]) -> None:
if not price_catalog:
raise ValueError("price_catalog cannot be empty")
self.price_catalog = dict(price_catalog)
self.quantities: dict[str, int] = {}

def add_item(self, item: str, quantity: int = 1) -> None:
if item not in self.price_catalog:
raise KeyError(f"{item!r} is not in the catalog")

Check failure on line 28 in other/grocery_store_cart.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (EM102)

other/grocery_store_cart.py:28:28: EM102 Exception must not use an f-string literal, assign to variable first help: Assign to variable; remove f-string literal
if quantity <= 0:
raise ValueError("quantity must be positive")
self.quantities[item] = self.quantities.get(item, 0) + quantity

def remove_item(self, item: str, quantity: int = 1) -> None:
if quantity <= 0:
raise ValueError("quantity must be positive")
current = self.quantities.get(item, 0)
if current == 0:
raise KeyError(f"{item!r} is not present in the cart")

Check failure on line 38 in other/grocery_store_cart.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (EM102)

other/grocery_store_cart.py:38:28: EM102 Exception must not use an f-string literal, assign to variable first help: Assign to variable; remove f-string literal
remaining = current - quantity
if remaining > 0:
self.quantities[item] = remaining
else:
self.quantities.pop(item, None)

def total_price(self) -> float:
return sum(self.price_catalog[item] * qty for item, qty in self.quantities.items())

Check failure on line 46 in other/grocery_store_cart.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (E501)

other/grocery_store_cart.py:46:89: E501 Line too long (91 > 88)


if __name__ == "__main__":
import doctest

doctest.testmod()
Loading