Skip to content
74 changes: 74 additions & 0 deletions maths/ncr_combinations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Generalized nCr (combinations) calculator for real numbers n and integer r.
Wikipedia URL: https://en.wikipedia.org/wiki/Binomial_coefficient
"""

from math import factorial as math_factorial


def nCr(n: float, r: int) -> float:

Check failure on line 9 in maths/ncr_combinations.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (N802)

maths/ncr_combinations.py:9:5: N802 Function name `nCr` should be lowercase

Choose a reason for hiding this comment

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

Variable and function names should follow the snake_case naming convention. Please update the following name accordingly: nCr

Please provide descriptive name for the parameter: n

Please provide descriptive name for the parameter: r

"""
Compute the number of combinations (n choose r) for real n and integer r
using the formula:

nCr = n * (n-1) * (n-2) * ... * (n-r+1) / r!

Parameters
----------
n : float
Total number of items. Can be any real number.
r : int
Number of items to choose. Must be a non-negative integer.

Returns
-------
float
The number of combinations.

Raises
------
ValueError
If r is not an integer or r < 0

Examples
--------
>>> nCr(5, 2)
10.0
>>> nCr(5.5, 2)
12.375
>>> nCr(10, 0)
1.0
>>> nCr(0, 0)
1.0
>>> nCr(5, -1)
Traceback (most recent call last):
...
ValueError: r must be a non-negative integer
>>> nCr(5, 2.5)
Traceback (most recent call last):
...
ValueError: r must be a non-negative integer
"""
if not isinstance(r, int) or r < 0:
raise ValueError("r must be a non-negative integer")

if r == 0:
return 1.0

numerator = 1.0
for i in range(r):
numerator *= n - i

denominator = math_factorial(r)
return numerator / denominator


if __name__ == "__main__":
import doctest

doctest.testmod()

# Example usage
n = float(input("Enter n (real number): ").strip() or 0)
r = int(input("Enter r (integer): ").strip() or 0)
print(f"nCr({n}, {r}) = {nCr(n, r)}")
65 changes: 65 additions & 0 deletions maths/optimised_sieve_of_eratosthenes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Optimized Sieve of Eratosthenes: An efficient algorithm to compute all prime numbers
# up to limit. This version skips even numbers after 2, improving both memory and time
# usage. It is particularly efficient for larger limits (e.g., up to 10**8 on typical
# hardware).
# Wikipedia URL - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes

from math import isqrt


def optimized_sieve(limit: int) -> list[int]:
"""
Compute all prime numbers up to and including `limit` using an optimized
Sieve of Eratosthenes.

This implementation skips even numbers after 2 to reduce memory and
runtime by about 50%.

Parameters
----------
limit : int
Upper bound (inclusive) of the range in which to find prime numbers.
Expected to be a non-negative integer. If limit < 2 the function
returns an empty list.

Returns
-------
list[int]
A list of primes in ascending order that are <= limit.

Examples
--------
>>> optimized_sieve(10)
[2, 3, 5, 7]
>>> optimized_sieve(1)
[]
>>> optimized_sieve(2)
[2]
>>> optimized_sieve(30)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
"""
if limit < 2:
return []

# Handle 2 separately, then consider only odd numbers
primes = [2] if limit >= 2 else []

# Only odd numbers from 3 to limit
size = (limit - 1) // 2
is_prime = [True] * (size + 1)
bound = isqrt(limit)

for i in range((bound - 1) // 2 + 1):
if is_prime[i]:
p = 2 * i + 3
# Start marking from p^2, converted to index
start = (p * p - 3) // 2
for j in range(start, size + 1, p):
is_prime[j] = False

primes.extend(2 * i + 3 for i in range(size + 1) if is_prime[i])
return primes


if __name__ == "__main__":
print(optimized_sieve(50))
93 changes: 93 additions & 0 deletions matrix/determinant_calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Determinant calculator using cofactor expansion.
# Calculates the determinant of a square matrix recursively.
# Wikipedia URL - https://en.wikipedia.org/wiki/Determinant


def get_minor(
matrix: list[list[int] | list[float]], row: int, col: int
) -> list[list[int] | list[float]]:
"""
Returns the minor matrix obtained by removing the specified row and column.

Parameters
----------
matrix : list[list[int] | list[float]]
The original square matrix.
row : int
Row to remove.
col : int
Column to remove.

Returns
-------
list[list[int] | list[float]]
Minor matrix.

Examples
--------
>>> get_minor([[1, 2], [3, 4]], 0, 0)
[[4]]
>>> get_minor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], 1, 1)
[[1, 3], [7, 9]]
"""
return [r[:col] + r[col + 1 :] for i, r in enumerate(matrix) if i != row]


def determinant_manual(matrix: list[list[int] | list[float]]) -> int | float:
"""
Calculates the determinant of a square matrix using cofactor expansion.

Parameters
----------
matrix : list[list[int] | list[float]]
A square matrix.

Returns
-------
int | float
Determinant of the matrix.

Examples
--------
>>> determinant_manual([[2]])
2
>>> determinant_manual([[1, 2],
... [3, 4]])
-2
>>> determinant_manual([
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9]
... ])
0
>>> determinant_manual([
... [3, 0, 2],
... [2, 0, -2],
... [0, 1, 1]
... ])
10
"""
n = len(matrix)

if n == 1:
return matrix[0][0]

if n == 2:
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]

det = 0
for j in range(n):
minor = get_minor(matrix, 0, j)
cofactor = (-1) ** j * determinant_manual(minor)
det += matrix[0][j] * cofactor
return det


if __name__ == "__main__":

matrix_demo = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
print(f"The determinant is: {determinant_manual(matrix_demo)}")
Loading