Skip to content

Commit bf26cee

Browse files
authored
Merge branch 'master' into activity_selection_new_algo2
2 parents c0c9542 + 836bd71 commit bf26cee

9 files changed

Lines changed: 235 additions & 41 deletions

File tree

‎DIRECTORY.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@
886886
* [Nevilles Method](maths/numerical_analysis/nevilles_method.py)
887887
* [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py)
888888
* [Newton Raphson](maths/numerical_analysis/newton_raphson.py)
889+
* [Nth Root](maths/numerical_analysis/nth_root.py)
889890
* [Numerical Integration](maths/numerical_analysis/numerical_integration.py)
890891
* [Proper Fractions](maths/numerical_analysis/proper_fractions.py)
891892
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
@@ -1593,6 +1594,7 @@
15931594
* [Knuth Morris Pratt](strings/knuth_morris_pratt.py)
15941595
* [Largest Smallest Words](strings/largest_smallest_words.py)
15951596
* [Levenshtein Distance](strings/levenshtein_distance.py)
1597+
* [Longest Word In Sentence](strings/longest_word_in_sentence.py)
15961598
* [Lower](strings/lower.py)
15971599
* [Manacher](strings/manacher.py)
15981600
* [Min Cost String Conversion](strings/min_cost_string_conversion.py)

‎maths/fermat_little_theorem.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,25 @@
55
# Wikipedia reference: https://en.wikipedia.org/wiki/Fermat%27s_little_theorem
66

77

8-
def binary_exponentiation(a: int, n: float, mod: int) -> int:
8+
def binary_exponentiation(a: int, n: int, mod: int) -> int:
9+
"""
10+
Calculate (a ** n) % mod using binary exponentiation, which runs in O(log n) time.
11+
12+
>>> binary_exponentiation(2, 10, 17)
13+
4
14+
>>> binary_exponentiation(3, 0, 5)
15+
1
16+
>>> binary_exponentiation(5, 3, 13)
17+
8
18+
"""
919
if n == 0:
1020
return 1
1121

1222
elif n % 2 == 1:
1323
return (binary_exponentiation(a, n - 1, mod) * a) % mod
1424

1525
else:
16-
b = binary_exponentiation(a, n / 2, mod)
26+
b = binary_exponentiation(a, n // 2, mod)
1727
return (b * b) % mod
1828

1929

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"""
2+
Approximate the nth root of a real number using Newton's Method.
3+
4+
The nth root of a real number R can be computed with Newton's method,
5+
which starts with an initial guess x_0 and then iterates using the
6+
recurrence relation:
7+
8+
x_{k + 1} = x_k - ((x_k)**n - R)/(n*(x_k)**(n-1))
9+
10+
The recurrence relation can be rewritten for computational efficiency:
11+
12+
x_{k + 1} = (n-1)/n*x_k + R/(n*(x_k)**(n-1))
13+
14+
Given a tolerance TOL, a stopping criterion can be set as:
15+
16+
abs(x_{k + 1} - x_k) < TOL
17+
18+
References:
19+
- https://en.wikipedia.org/wiki/Nth_root#Using_Newton's_method
20+
- Sauer, T. (2011): Numerical analysis.
21+
USA. Addison-Wesley Publishing Company.
22+
"""
23+
24+
from math import pow # noqa: A004
25+
26+
27+
def nth_root(radicand: float, index: int, tolerance: float = 0.0001) -> float:
28+
"""
29+
Approximate the nth root of the radicand for the given index
30+
31+
Args:
32+
radicand: number from which the root is taken
33+
index: positive integer which is the degree of the root
34+
tolerance: positive real number that establishes the stopping criterion
35+
36+
Returns:
37+
new_approximation: approximation of the nth root of the radicand for the
38+
given index
39+
40+
Raises:
41+
TypeError: radicand is not a real number
42+
TypeError: index is not an integer
43+
ValueError: index is not a positive integer
44+
TypeError: tolerance is not a real number
45+
ValueError: tolerance is not a positive real number
46+
ValueError: math domain error
47+
48+
>>> round(nth_root(9, 2),1)
49+
3.0
50+
51+
>>> int(round(nth_root(-8, 3, 0.001)))
52+
-2
53+
54+
>>> int(round(nth_root(256, 4, 0.001)))
55+
4
56+
57+
>>> round(nth_root(2, 2), 5)
58+
1.41421
59+
60+
>>> round(nth_root(0.25, 2, 0.00000001), 1)
61+
0.5
62+
63+
>>> round(nth_root(-8/27, 3, 0.0000001), 5)
64+
-0.66667
65+
66+
>>> nth_root(0, 2, 0.1)
67+
0.0
68+
69+
>>> nth_root(0.0, 5)
70+
0.0
71+
72+
>>> all(abs(nth_root(k, k, 0.00000001) - k**(1/k)) <= 1e-10 for k in range(1,10))
73+
True
74+
75+
>>> nth_root('invalid input', 3, 0.0001)
76+
Traceback (most recent call last):
77+
...
78+
TypeError: radicand must be a real number, not a str
79+
80+
>>> nth_root(4, 0.5, 0.0001)
81+
Traceback (most recent call last):
82+
...
83+
TypeError: index must be an integer, not a float
84+
85+
>>> nth_root(16, -4, 0.001)
86+
Traceback (most recent call last):
87+
...
88+
ValueError: index must be a positive integer, -4 <= 0
89+
90+
>>> nth_root(4, 2, '0.000001')
91+
Traceback (most recent call last):
92+
...
93+
TypeError: tolerance must be a real number, not str
94+
95+
>>> nth_root(9, 2, -0.01)
96+
Traceback (most recent call last):
97+
...
98+
ValueError: tolerance must be a positive real number, -0.01 <= 0
99+
100+
>>> nth_root(-256, 4, 0.0001)
101+
Traceback (most recent call last):
102+
...
103+
ValueError: math domain error, radicand must be nonnegative for even index
104+
"""
105+
if not isinstance(radicand, (int, float)):
106+
error_message = (
107+
f"radicand must be a real number, not a {type(radicand).__name__}"
108+
)
109+
raise TypeError(error_message)
110+
111+
if not isinstance(index, int):
112+
error_message = f"index must be an integer, not a {type(index).__name__}"
113+
raise TypeError(error_message)
114+
115+
if index <= 0:
116+
error_message = f"index must be a positive integer, {index} <= 0"
117+
raise ValueError(error_message)
118+
119+
if not isinstance(tolerance, (int, float)):
120+
error_message = (
121+
f"tolerance must be a real number, not {type(tolerance).__name__}"
122+
)
123+
raise TypeError(error_message)
124+
125+
if tolerance <= 0:
126+
error_message = f"tolerance must be a positive real number, {tolerance} <= 0"
127+
raise ValueError(error_message)
128+
129+
if radicand < 0 and index % 2 == 0:
130+
error_message = "math domain error, radicand must be nonnegative for even index"
131+
raise ValueError(error_message)
132+
133+
if radicand == 0.0:
134+
return 0.0
135+
136+
# Set initial guess
137+
new_approximation = radicand
138+
# Set old_approximation to enter the loop
139+
old_approximation = new_approximation + tolerance + 0.1
140+
141+
# Iterate as long as the stop criterion is not satisfied
142+
while tolerance <= abs(old_approximation - new_approximation):
143+
old_approximation = new_approximation
144+
# Compute new_approximation with the recurrence relation described above
145+
first_summand = (index - 1) / index * old_approximation
146+
second_summand = radicand / (index * pow(old_approximation, index - 1))
147+
new_approximation = first_summand + second_summand
148+
149+
return new_approximation
150+
151+
152+
if __name__ == "__main__":
153+
import doctest
154+
155+
doctest.testmod()

‎pyproject.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ dependencies = [
2222
"numpy>=2.1.3",
2323
"pandas>=2.3.3",
2424
"pillow>=11.3",
25-
"polars>=1.44.2",
2625
"rich>=13.9.4",
2726
"scikit-learn>=1.9.1",
2827
"scipy>=1.18.1",

‎sorts/reversort.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717
python reversort.py
1818
"""
1919

20-
from typing import Any
20+
from typing import Any, Protocol
2121

2222

23-
def reversort(collection: list[Any]) -> list[Any]:
23+
class Comparable(Protocol):
24+
def __lt__(self, other: Any, /) -> bool: ...
25+
26+
27+
def reversort[T: Comparable](collection: list[T]) -> list[T]:
2428
"""
2529
Sort a list using the Reversort algorithm.
2630
@@ -78,6 +82,10 @@ def reversort(collection: list[Any]) -> list[Any]:
7882
>>> collection_arg = random.choices(string.ascii_letters + string.digits, k=100)
7983
>>> reversort(collection_arg) == sorted(collection_arg)
8084
True
85+
>>> reversort([1, "a"]) # doctest: +IGNORE_EXCEPTION_DETAIL
86+
Traceback (most recent call last):
87+
...
88+
TypeError: '<' not supported between instances of 'str' and 'int'
8189
"""
8290
arr = collection[:] # Create a copy to avoid modifying the original
8391
n = len(arr)
@@ -96,7 +104,7 @@ def reversort(collection: list[Any]) -> list[Any]:
96104
return arr
97105

98106

99-
def reversort_cost(collection: list[Any]) -> int:
107+
def reversort_cost[T: Comparable](collection: list[T]) -> int:
100108
"""
101109
Calculate the cost of sorting using Reversort.
102110
@@ -119,6 +127,10 @@ def reversort_cost(collection: list[Any]) -> int:
119127
0
120128
>>> reversort_cost([])
121129
0
130+
>>> reversort_cost([1, "a"]) # doctest: +IGNORE_EXCEPTION_DETAIL
131+
Traceback (most recent call last):
132+
...
133+
TypeError: '<' not supported between instances of 'str' and 'int'
122134
"""
123135
arr = collection[:] # Create a copy to avoid modifying the original
124136
n = len(arr)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
def longest_word(sentence: str) -> str:
2+
"""
3+
Finds the longest word in a sentence.
4+
>>> longest_word("The quick brown fox jumped over the lazy dog")
5+
'jumped'
6+
>>> longest_word("Python is amazing")
7+
'amazing'
8+
>>> longest_word("")
9+
''
10+
>>> longest_word("a")
11+
'a'
12+
>>> longest_word("A journey of a thousand miles begins with a single step")
13+
'thousand'
14+
>>> longest_word("To be or not to be that is the question")
15+
'question'
16+
>>> longest_word("Beauty is in the eye of the beholder")
17+
'beholder'
18+
>>> longest_word("A picture is worth a thousand words")
19+
'thousand'
20+
>>> longest_word("All that glitters is not gold")
21+
'glitters'
22+
"""
23+
words = sentence.split()
24+
return max(words, key=len) if words else ""
25+
26+
27+
if __name__ == "__main__":
28+
from doctest import testmod
29+
30+
testmod()

‎tests/test_sorts.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from sorts.patience_sort import patience_sort
4242
from sorts.quick_sort import quick_sort
4343
from sorts.recursive_insertion_sort import rec_insertion_sort
44+
from sorts.reversort import reversort
4445
from sorts.selection_sort import selection_sort
4546
from sorts.shell_sort import shell_sort
4647
from sorts.shrink_shell_sort import shell_sort as shrink_shell_sort
@@ -74,6 +75,7 @@ def test_heap_sort() -> None:
7475
pancake_sort,
7576
patience_sort,
7677
quick_sort,
78+
reversort,
7779
selection_sort,
7880
shell_sort,
7981
shrink_shell_sort,
@@ -141,6 +143,7 @@ def test_rec_insertion_sort(case) -> None:
141143
merge_sort,
142144
odd_even_sort,
143145
pancake_sort,
146+
reversort,
144147
selection_sort,
145148
shrink_shell_sort,
146149
strand_sort,

‎uv.lock‎

Lines changed: 0 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)