quicktools-atom 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quicktools-atom
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Math and string utilities with a bundled CLI
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quicktools-atom"
7
- version = "0.1.1"
7
+ version = "0.2.0"
8
8
  description = "Math and string utilities with a bundled CLI"
9
9
  requires-python = ">=3.9"
10
10
  readme = "README.md"
@@ -0,0 +1,65 @@
1
+ """Calculus: numerical differentiation, integration, and series approximations."""
2
+ import math
3
+ from typing import Callable
4
+
5
+
6
+ def derivative(f: Callable[[float], float], x: float, h: float = 1e-6) -> float:
7
+ """Approximate f'(x) using the central difference method."""
8
+ return (f(x + h) - f(x - h)) / (2 * h)
9
+
10
+
11
+ def second_derivative(f: Callable[[float], float], x: float, h: float = 1e-4) -> float:
12
+ """Approximate f''(x) using the central difference method."""
13
+ return (f(x + h) - 2 * f(x) + f(x - h)) / (h ** 2)
14
+
15
+
16
+ def integral_trapezoidal(f: Callable[[float], float], a: float, b: float, n: int = 1000) -> float:
17
+ """Approximate the definite integral of f from a to b using the trapezoidal rule."""
18
+ if n <= 0:
19
+ raise ValueError("n must be positive")
20
+ h = (b - a) / n
21
+ total = (f(a) + f(b)) / 2
22
+ for i in range(1, n):
23
+ total += f(a + i * h)
24
+ return total * h
25
+
26
+
27
+ def integral_simpson(f: Callable[[float], float], a: float, b: float, n: int = 1000) -> float:
28
+ """Approximate the definite integral of f from a to b using Simpson's rule (n must be even)."""
29
+ if n % 2 != 0:
30
+ n += 1
31
+ h = (b - a) / n
32
+ total = f(a) + f(b)
33
+ for i in range(1, n):
34
+ coeff = 4 if i % 2 != 0 else 2
35
+ total += coeff * f(a + i * h)
36
+ return total * h / 3
37
+
38
+
39
+ def taylor_series_exp(x: float, terms: int = 15) -> float:
40
+ """Approximate e^x using its Taylor series expansion around 0."""
41
+ return sum((x ** n) / math.factorial(n) for n in range(terms))
42
+
43
+
44
+ def taylor_series_sin(x: float, terms: int = 15) -> float:
45
+ """Approximate sin(x) using its Taylor series expansion around 0."""
46
+ return sum(((-1) ** n) * (x ** (2 * n + 1)) / math.factorial(2 * n + 1) for n in range(terms))
47
+
48
+
49
+ def taylor_series_cos(x: float, terms: int = 15) -> float:
50
+ """Approximate cos(x) using its Taylor series expansion around 0."""
51
+ return sum(((-1) ** n) * (x ** (2 * n)) / math.factorial(2 * n) for n in range(terms))
52
+
53
+
54
+ def newtons_method(f: Callable[[float], float], x0: float, tol: float = 1e-10, max_iter: int = 100) -> float:
55
+ """Find a root of f near x0 using Newton's method."""
56
+ x = x0
57
+ for _ in range(max_iter):
58
+ fx = f(x)
59
+ if abs(fx) < tol:
60
+ return x
61
+ dfx = derivative(f, x)
62
+ if dfx == 0:
63
+ raise ZeroDivisionError("Derivative is zero; Newton's method failed")
64
+ x = x - fx / dfx
65
+ return x
@@ -0,0 +1,36 @@
1
+ """Combinatorics: permutations, combinations, and counting sequences."""
2
+ import math
3
+
4
+
5
+ def permutations_count(n: int, r: int) -> int:
6
+ """Number of ways to arrange r items out of n, order matters: nPr."""
7
+ if r > n or r < 0:
8
+ return 0
9
+ return math.factorial(n) // math.factorial(n - r)
10
+
11
+
12
+ def combinations_count(n: int, r: int) -> int:
13
+ """Number of ways to choose r items out of n, order doesn't matter: nCr."""
14
+ if r > n or r < 0:
15
+ return 0
16
+ return math.factorial(n) // (math.factorial(r) * math.factorial(n - r))
17
+
18
+
19
+ def catalan_number(n: int) -> int:
20
+ """The nth Catalan number — counts things like valid bracket sequences and binary tree shapes."""
21
+ return combinations_count(2 * n, n) // (n + 1)
22
+
23
+
24
+ def fibonacci(n: int) -> int:
25
+ """The nth Fibonacci number (0-indexed: fibonacci(0) == 0)."""
26
+ if n < 0:
27
+ raise ValueError("n must be non-negative")
28
+ a, b = 0, 1
29
+ for _ in range(n):
30
+ a, b = b, a + b
31
+ return a
32
+
33
+
34
+ def binomial_expansion_coeffs(n: int) -> list[int]:
35
+ """Coefficients of (x + y)^n, i.e. row n of Pascal's triangle."""
36
+ return [combinations_count(n, k) for k in range(n + 1)]
@@ -0,0 +1,99 @@
1
+ """Linear algebra: matrix operations, determinants, inverses, linear systems.
2
+
3
+ Matrices are represented as lists of lists, e.g. [[1, 2], [3, 4]].
4
+ No external dependencies (no numpy) — pure Python.
5
+ """
6
+
7
+
8
+ def matrix_add(a: list[list[float]], b: list[list[float]]) -> list[list[float]]:
9
+ """Add two matrices of the same shape."""
10
+ return [[a[i][j] + b[i][j] for j in range(len(a[0]))] for i in range(len(a))]
11
+
12
+
13
+ def matrix_multiply(a: list[list[float]], b: list[list[float]]) -> list[list[float]]:
14
+ """Multiply two matrices (a is m x n, b is n x p, result is m x p)."""
15
+ rows_a, cols_a = len(a), len(a[0])
16
+ rows_b, cols_b = len(b), len(b[0])
17
+ if cols_a != rows_b:
18
+ raise ValueError(f"Cannot multiply {rows_a}x{cols_a} by {rows_b}x{cols_b}")
19
+ result = [[0.0] * cols_b for _ in range(rows_a)]
20
+ for i in range(rows_a):
21
+ for j in range(cols_b):
22
+ result[i][j] = sum(a[i][k] * b[k][j] for k in range(cols_a))
23
+ return result
24
+
25
+
26
+ def transpose(a: list[list[float]]) -> list[list[float]]:
27
+ """Return the transpose of a matrix."""
28
+ return [list(row) for row in zip(*a)]
29
+
30
+
31
+ def identity(n: int) -> list[list[float]]:
32
+ """Return the n x n identity matrix."""
33
+ return [[1.0 if i == j else 0.0 for j in range(n)] for i in range(n)]
34
+
35
+
36
+ def determinant(a: list[list[float]]) -> float:
37
+ """Determinant of a square matrix via cofactor expansion (Laplace expansion)."""
38
+ n = len(a)
39
+ if n != len(a[0]):
40
+ raise ValueError("Matrix must be square")
41
+ if n == 1:
42
+ return a[0][0]
43
+ if n == 2:
44
+ return a[0][0] * a[1][1] - a[0][1] * a[1][0]
45
+ det = 0.0
46
+ for col in range(n):
47
+ minor = [row[:col] + row[col + 1:] for row in a[1:]]
48
+ sign = 1 if col % 2 == 0 else -1
49
+ det += sign * a[0][col] * determinant(minor)
50
+ return det
51
+
52
+
53
+ def inverse(a: list[list[float]]) -> list[list[float]]:
54
+ """Inverse of a square matrix using Gauss-Jordan elimination."""
55
+ n = len(a)
56
+ aug = [row[:] + [1.0 if i == j else 0.0 for j in range(n)] for i, row in enumerate(a)]
57
+
58
+ for col in range(n):
59
+ pivot_row = max(range(col, n), key=lambda r: abs(aug[r][col]))
60
+ if abs(aug[pivot_row][col]) < 1e-12:
61
+ raise ValueError("Matrix is singular and cannot be inverted")
62
+ aug[col], aug[pivot_row] = aug[pivot_row], aug[col]
63
+
64
+ pivot_val = aug[col][col]
65
+ aug[col] = [x / pivot_val for x in aug[col]]
66
+
67
+ for r in range(n):
68
+ if r != col:
69
+ factor = aug[r][col]
70
+ aug[r] = [aug[r][k] - factor * aug[col][k] for k in range(2 * n)]
71
+
72
+ return [row[n:] for row in aug]
73
+
74
+
75
+ def solve_linear_system(a: list[list[float]], b: list[float]) -> list[float]:
76
+ """Solve Ax = b for x, given square matrix A and vector b."""
77
+ n = len(a)
78
+ aug = [a[i][:] + [b[i]] for i in range(n)]
79
+
80
+ for col in range(n):
81
+ pivot_row = max(range(col, n), key=lambda r: abs(aug[r][col]))
82
+ if abs(aug[pivot_row][col]) < 1e-12:
83
+ raise ValueError("System has no unique solution (singular matrix)")
84
+ aug[col], aug[pivot_row] = aug[pivot_row], aug[col]
85
+
86
+ pivot_val = aug[col][col]
87
+ aug[col] = [x / pivot_val for x in aug[col]]
88
+
89
+ for r in range(n):
90
+ if r != col:
91
+ factor = aug[r][col]
92
+ aug[r] = [aug[r][k] - factor * aug[col][k] for k in range(n + 1)]
93
+
94
+ return [row[n] for row in aug]
95
+
96
+
97
+ def trace(a: list[list[float]]) -> float:
98
+ """Sum of the diagonal elements of a square matrix."""
99
+ return sum(a[i][i] for i in range(len(a)))
@@ -0,0 +1,61 @@
1
+ """Number theory: primes, modular arithmetic, and related functions."""
2
+
3
+
4
+ def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
5
+ """Return (g, x, y) such that a*x + b*y = g = gcd(a, b)."""
6
+ if a == 0:
7
+ return b, 0, 1
8
+ g, x1, y1 = extended_gcd(b % a, a)
9
+ return g, y1 - (b // a) * x1, x1
10
+
11
+
12
+ def mod_inverse(a: int, m: int) -> int:
13
+ """Return the modular multiplicative inverse of a modulo m."""
14
+ g, x, _ = extended_gcd(a % m, m)
15
+ if g != 1:
16
+ raise ValueError(f"No modular inverse exists for {a} mod {m}")
17
+ return x % m
18
+
19
+
20
+ def sieve_of_eratosthenes(limit: int) -> list[int]:
21
+ """Return all prime numbers up to and including `limit`."""
22
+ if limit < 2:
23
+ return []
24
+ is_prime_arr = [True] * (limit + 1)
25
+ is_prime_arr[0] = is_prime_arr[1] = False
26
+ for i in range(2, int(limit ** 0.5) + 1):
27
+ if is_prime_arr[i]:
28
+ for multiple in range(i * i, limit + 1, i):
29
+ is_prime_arr[multiple] = False
30
+ return [i for i, prime in enumerate(is_prime_arr) if prime]
31
+
32
+
33
+ def prime_factors(n: int) -> dict[int, int]:
34
+ """Return the prime factorization of n as {prime: exponent}."""
35
+ if n < 2:
36
+ return {}
37
+ factors = {}
38
+ d = 2
39
+ while d * d <= n:
40
+ while n % d == 0:
41
+ factors[d] = factors.get(d, 0) + 1
42
+ n //= d
43
+ d += 1
44
+ if n > 1:
45
+ factors[n] = factors.get(n, 0) + 1
46
+ return factors
47
+
48
+
49
+ def euler_totient(n: int) -> int:
50
+ """Count integers up to n that are coprime with n (Euler's totient function)."""
51
+ result = n
52
+ factors = prime_factors(n)
53
+ for p in factors:
54
+ result -= result // p
55
+ return result
56
+
57
+
58
+ def is_coprime(a: int, b: int) -> bool:
59
+ """Return True if a and b share no common factors other than 1."""
60
+ import math
61
+ return math.gcd(a, b) == 1
@@ -0,0 +1,106 @@
1
+ """String utilities: text checks, transforms, and simple ciphers."""
2
+ import re
3
+
4
+
5
+ def is_palindrome(text: str) -> bool:
6
+ """Return True if text reads the same forwards and backwards (ignoring case/spaces)."""
7
+ cleaned = re.sub(r"[^a-zA-Z0-9]", "", text).lower()
8
+ return cleaned == cleaned[::-1]
9
+
10
+
11
+ def slugify(text: str) -> str:
12
+ """Convert text into a url-friendly slug, e.g. 'Hello World!' -> 'hello-world'."""
13
+ text = text.lower().strip()
14
+ text = re.sub(r"[^a-z0-9]+", "-", text)
15
+ return text.strip("-")
16
+
17
+
18
+ def word_frequency(text: str) -> dict[str, int]:
19
+ """Count occurrences of each word in text (case-insensitive)."""
20
+ words = re.findall(r"[a-zA-Z']+", text.lower())
21
+ freq: dict[str, int] = {}
22
+ for w in words:
23
+ freq[w] = freq.get(w, 0) + 1
24
+ return freq
25
+
26
+
27
+ def caesar_cipher(text: str, shift: int) -> str:
28
+ """Shift each letter in text by `shift` positions (Caesar cipher). Negative shift decodes."""
29
+ result = []
30
+ for ch in text:
31
+ if ch.isalpha():
32
+ base = ord('A') if ch.isupper() else ord('a')
33
+ result.append(chr((ord(ch) - base + shift) % 26 + base))
34
+ else:
35
+ result.append(ch)
36
+ return "".join(result)
37
+
38
+
39
+ def reverse_words(text: str) -> str:
40
+ """Reverse the order of words in a sentence."""
41
+ return " ".join(text.split()[::-1])
42
+
43
+
44
+ def levenshtein_distance(s1: str, s2: str) -> int:
45
+ """Minimum number of single-character edits (insertions, deletions, substitutions)
46
+ needed to turn s1 into s2."""
47
+ if len(s1) < len(s2):
48
+ return levenshtein_distance(s2, s1)
49
+ if len(s2) == 0:
50
+ return len(s1)
51
+
52
+ previous_row = list(range(len(s2) + 1))
53
+ for i, c1 in enumerate(s1):
54
+ current_row = [i + 1]
55
+ for j, c2 in enumerate(s2):
56
+ insertions = previous_row[j + 1] + 1
57
+ deletions = current_row[j] + 1
58
+ substitutions = previous_row[j] + (c1 != c2)
59
+ current_row.append(min(insertions, deletions, substitutions))
60
+ previous_row = current_row
61
+ return previous_row[-1]
62
+
63
+
64
+ def is_anagram(s1: str, s2: str) -> bool:
65
+ """Return True if s1 and s2 use exactly the same letters (ignoring case/spaces)."""
66
+ clean1 = re.sub(r"[^a-z0-9]", "", s1.lower())
67
+ clean2 = re.sub(r"[^a-z0-9]", "", s2.lower())
68
+ return sorted(clean1) == sorted(clean2)
69
+
70
+
71
+ def to_snake_case(text: str) -> str:
72
+ """Convert 'camelCase' or 'Title Case' text into 'snake_case'."""
73
+ text = re.sub(r"(?<!^)(?=[A-Z])", "_", text)
74
+ text = re.sub(r"[\s\-]+", "_", text)
75
+ return text.lower().strip("_")
76
+
77
+
78
+ def to_camel_case(text: str) -> str:
79
+ """Convert 'snake_case' or 'kebab-case' text into 'camelCase'."""
80
+ parts = re.split(r"[_\-\s]+", text.strip())
81
+ if not parts:
82
+ return ""
83
+ return parts[0].lower() + "".join(p.capitalize() for p in parts[1:])
84
+
85
+
86
+ def rot13(text: str) -> str:
87
+ """Apply the ROT13 cipher (Caesar cipher with a fixed shift of 13)."""
88
+ return caesar_cipher(text, 13)
89
+
90
+
91
+ def vigenere_cipher(text: str, key: str, decode: bool = False) -> str:
92
+ """Encode or decode text using the Vigenere cipher with the given key."""
93
+ result = []
94
+ key = key.lower()
95
+ key_index = 0
96
+ for ch in text:
97
+ if ch.isalpha():
98
+ shift = ord(key[key_index % len(key)]) - ord('a')
99
+ if decode:
100
+ shift = -shift
101
+ base = ord('A') if ch.isupper() else ord('a')
102
+ result.append(chr((ord(ch) - base + shift) % 26 + base))
103
+ key_index += 1
104
+ else:
105
+ result.append(ch)
106
+ return "".join(result)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quicktools-atom
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Math and string utilities with a bundled CLI
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -1,8 +1,12 @@
1
1
  README.md
2
2
  pyproject.toml
3
3
  src/quicktools/__init__.py
4
+ src/quicktools/calculus.py
4
5
  src/quicktools/cli.py
6
+ src/quicktools/combinatorics.py
7
+ src/quicktools/linalg.py
5
8
  src/quicktools/mathtools.py
9
+ src/quicktools/numbertheory.py
6
10
  src/quicktools/strtools.py
7
11
  src/quicktools_atom.egg-info/PKG-INFO
8
12
  src/quicktools_atom.egg-info/SOURCES.txt
@@ -1,41 +0,0 @@
1
- """String utilities: text checks, transforms, and simple ciphers."""
2
- import re
3
-
4
-
5
- def is_palindrome(text: str) -> bool:
6
- """Return True if text reads the same forwards and backwards (ignoring case/spaces)."""
7
- cleaned = re.sub(r"[^a-zA-Z0-9]", "", text).lower()
8
- return cleaned == cleaned[::-1]
9
-
10
-
11
- def slugify(text: str) -> str:
12
- """Convert text into a url-friendly slug, e.g. 'Hello World!' -> 'hello-world'."""
13
- text = text.lower().strip()
14
- text = re.sub(r"[^a-z0-9]+", "-", text)
15
- return text.strip("-")
16
-
17
-
18
- def word_frequency(text: str) -> dict[str, int]:
19
- """Count occurrences of each word in text (case-insensitive)."""
20
- words = re.findall(r"[a-zA-Z']+", text.lower())
21
- freq: dict[str, int] = {}
22
- for w in words:
23
- freq[w] = freq.get(w, 0) + 1
24
- return freq
25
-
26
-
27
- def caesar_cipher(text: str, shift: int) -> str:
28
- """Shift each letter in text by `shift` positions (Caesar cipher). Negative shift decodes."""
29
- result = []
30
- for ch in text:
31
- if ch.isalpha():
32
- base = ord('A') if ch.isupper() else ord('a')
33
- result.append(chr((ord(ch) - base + shift) % 26 + base))
34
- else:
35
- result.append(ch)
36
- return "".join(result)
37
-
38
-
39
- def reverse_words(text: str) -> str:
40
- """Reverse the order of words in a sentence."""
41
- return " ".join(text.split()[::-1])