From c93841eb9468cf6433e057a230d60063cfcd1dd8 Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Sat, 11 Apr 2026 16:18:01 +0200 Subject: [PATCH 1/6] Add space optimized 0/1 knapsack implementation --- dynamic_programming/knapsack.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index 28c5b19dbe36..ecdb38e7989c 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -39,6 +39,40 @@ 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 + """ + 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.") + + 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 From 07539cb4d9f849628ab46d21c801f9714c4987cb Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Sat, 11 Apr 2026 16:50:31 +0200 Subject: [PATCH 2/6] Harden knapsack_space_optimized input validation --- dynamic_programming/knapsack.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index ecdb38e7989c..a6500543deec 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -54,6 +54,26 @@ def knapsack_space_optimized( 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.") @@ -61,6 +81,15 @@ def knapsack_space_optimized( 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): + raise TypeError(f"Weight at index {item_index} must be an integer.") + if item_weight < 0: + raise ValueError(f"Weight at index {item_index} cannot be negative.") + if not isinstance(item_value, int): + raise TypeError(f"Value at index {item_index} must be an integer.") dp = [0] * (capacity + 1) for item_index in range(num_items): From 3fb7cf30b4a7bc899828eb7eca8e746fb2d0dfb3 Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Sat, 11 Apr 2026 16:56:26 +0200 Subject: [PATCH 3/6] Fix ruff EM102 in knapsack_space_optimized --- dynamic_programming/knapsack.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py index a6500543deec..9d5155cdd7e8 100644 --- a/dynamic_programming/knapsack.py +++ b/dynamic_programming/knapsack.py @@ -85,11 +85,14 @@ def knapsack_space_optimized( item_weight = weights[item_index] item_value = values[item_index] if not isinstance(item_weight, int): - raise TypeError(f"Weight at index {item_index} must be an integer.") + msg = f"Weight at index {item_index} must be an integer." + raise TypeError(msg) if item_weight < 0: - raise ValueError(f"Weight at index {item_index} cannot be negative.") + msg = f"Weight at index {item_index} cannot be negative." + raise ValueError(msg) if not isinstance(item_value, int): - raise TypeError(f"Value at index {item_index} must be an integer.") + 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): From 15f558851193d33f39b629adf00b3c9fe9894d13 Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Mon, 13 Apr 2026 10:44:01 +0200 Subject: [PATCH 4/6] feat(maths): add repunit theorem helpers --- maths/repunit_theorem.py | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 maths/repunit_theorem.py 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() From cee29ebfbf9be239d4dc357351fbaa3121e2fb80 Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Mon, 13 Apr 2026 10:44:32 +0200 Subject: [PATCH 5/6] feat(other): add basic BankAccount OOP class --- other/bank_account.py | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 other/bank_account.py 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() From 73df8df8b4853943bce9dddc66a7b9c0d6f148a9 Mon Sep 17 00:00:00 2001 From: nickzerjeski Date: Mon, 13 Apr 2026 10:45:06 +0200 Subject: [PATCH 6/6] feat(other): add grocery store cart model --- other/grocery_store_cart.py | 52 +++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 other/grocery_store_cart.py diff --git a/other/grocery_store_cart.py b/other/grocery_store_cart.py new file mode 100644 index 000000000000..eb2be39d0547 --- /dev/null +++ b/other/grocery_store_cart.py @@ -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") + 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") + 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()) + + +if __name__ == "__main__": + import doctest + + doctest.testmod()