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``.
"""
Comment on lines +1 to +9
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

PR description/title focus on adding BankAccount, but this change set also adds a new maths module (maths/repunit_theorem.py) and a new knapsack implementation. Please either update the PR description/title to reflect the additional features or split these into separate PRs to keep the change scoped and reviewable.

Copilot uses AI. Check for mistakes.

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)
Comment on lines +21 to +28
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

initial_balance < 0 will not reject NaN (comparisons with NaN are always false), so BankAccount(..., initial_balance=float('nan')) creates an account with a corrupted balance. Consider coercing initial_balance to float first and rejecting non-finite values (NaN/inf) before storing it.

Copilot uses AI. Check for mistakes.

@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
Comment on lines +40 to +54
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

amount <= 0 does not reject NaN, and adding/subtracting NaN will propagate and permanently corrupt the balance. Consider validating that amount is a finite number (and then > 0) before applying the operation (same for both deposit and withdraw).

Copilot uses AI. Check for mistakes.


if __name__ == "__main__":
import doctest

doctest.testmod()
Loading