diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 28c5b19dbe36..9d5155cdd7e8 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -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 diff --git a/maths/repunit_theorem.py b/maths/repunit_theorem.py new file mode 100644 index 000000000000..2779147b8545 --- /dev/null +++ b/maths/repunit_theorem.py @@ -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() diff --git a/other/bank_account.py b/other/bank_account.py new file mode 100644 index 000000000000..c3aa5a07b1ef --- /dev/null +++ b/other/bank_account.py @@ -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()