Skip to content

Commit 7bd96ad

Browse files
authored
Merge branch 'master' into sorts/make-odd-even-transposition-generic
2 parents f670b4b + cce4d26 commit 7bd96ad

8 files changed

Lines changed: 471 additions & 18 deletions

File tree

‎DIRECTORY.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@
671671
* [Travelling Salesman Problem](graphs/travelling_salesman_problem.py)
672672

673673
## [Greedy Methods](greedy_methods)
674+
* [Activity Selection](greedy_methods/activity_selection.py)
674675
* [Best Time To Buy And Sell Stock](greedy_methods/best_time_to_buy_and_sell_stock.py)
675676
* [Fractional Cover Problem](greedy_methods/fractional_cover_problem.py)
676677
* [Fractional Knapsack](greedy_methods/fractional_knapsack.py)
@@ -885,6 +886,7 @@
885886
* [Nevilles Method](maths/numerical_analysis/nevilles_method.py)
886887
* [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py)
887888
* [Newton Raphson](maths/numerical_analysis/newton_raphson.py)
889+
* [Nth Root](maths/numerical_analysis/nth_root.py)
888890
* [Numerical Integration](maths/numerical_analysis/numerical_integration.py)
889891
* [Proper Fractions](maths/numerical_analysis/proper_fractions.py)
890892
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
@@ -1592,6 +1594,7 @@
15921594
* [Knuth Morris Pratt](strings/knuth_morris_pratt.py)
15931595
* [Largest Smallest Words](strings/largest_smallest_words.py)
15941596
* [Levenshtein Distance](strings/levenshtein_distance.py)
1597+
* [Longest Word In Sentence](strings/longest_word_in_sentence.py)
15951598
* [Lower](strings/lower.py)
15961599
* [Manacher](strings/manacher.py)
15971600
* [Min Cost String Conversion](strings/min_cost_string_conversion.py)
@@ -1606,6 +1609,7 @@
16061609
* [Reverse Words](strings/reverse_words.py)
16071610
* [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py)
16081611
* [Split](strings/split.py)
1612+
* [String Is Valid Number](strings/string_is_valid_number.py)
16091613
* [String Switch Case](strings/string_switch_case.py)
16101614
* [Strip](strings/strip.py)
16111615
* [Suffix Automaton](strings/suffix_automaton.py)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""
2+
The Activity Selection Problem is a classic problem in which a set of activities,
3+
each with a start and end time, needs to be scheduled in such a way that the
4+
maximum number of non-overlapping activities is selected.
5+
This is a greedy algorithm where at each step,
6+
we choose the activity that finishes the earliest
7+
and does not conflict with previously selected activities.
8+
9+
Wikipedia: https://en.wikipedia.org/wiki/Activity_selection_problem
10+
"""
11+
12+
13+
def activity_selection(activities: list[tuple[int, int]]) -> list[tuple[int, int]]:
14+
"""
15+
Solve the Activity Selection Problem using a greedy algorithm by selecting
16+
the maximum number of non-overlapping activities from a list of activities.
17+
18+
Parameters:
19+
activities: A list of tuples where each tuple contains
20+
the start and end times of an activity.
21+
22+
Returns:
23+
A list of selected activities that are non-overlapping.
24+
25+
Example:
26+
>>> activity_selection([(1, 3), (2, 5), (3, 9), (6, 8)])
27+
[(1, 3), (6, 8)]
28+
29+
>>> activity_selection([(0, 6), (1, 4), (3, 5), (5, 7), (5, 9), (8, 9)])
30+
[(1, 4), (5, 7), (8, 9)]
31+
32+
>>> activity_selection([(1, 2), (2, 4), (3, 5), (0, 6)])
33+
[(1, 2), (2, 4)]
34+
35+
>>> activity_selection([(5, 9), (1, 2), (3, 4), (0, 6)])
36+
[(1, 2), (3, 4), (5, 9)]
37+
38+
>>> all(activity_selection(x) == [] for x in ([], {}, None, False, 0, 0.0))
39+
True
40+
"""
41+
if not activities:
42+
return []
43+
44+
# Step 1: Sort the activities by their end time
45+
sorted_activities = sorted(activities, key=lambda activity: activity[1])
46+
47+
# Step 2: Select the first activity (the one that finishes the earliest)
48+
# as the initial activity
49+
selected_activities = [sorted_activities[0]]
50+
51+
# Step 3: Iterate through the sorted activities and select the ones
52+
# that do not overlap with the last selected activity
53+
for i in range(1, len(sorted_activities)):
54+
if sorted_activities[i][0] >= selected_activities[-1][1]:
55+
selected_activities.append(sorted_activities[i])
56+
57+
return selected_activities
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()

‎sorts/recursive_mergesort_array.py‎

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1-
"""A merge sort which accepts an array as input and recursively
2-
splits an array in half and sorts and combines them.
1+
"""A merge sort which accepts comparable items and recursively
2+
splits them in half, then sorts and combines the halves.
3+
4+
https://en.wikipedia.org/wiki/Merge_sort
35
"""
46

5-
"""https://en.wikipedia.org/wiki/Merge_sort """
7+
from collections.abc import Iterable
8+
from typing import Protocol
9+
10+
11+
class Comparable(Protocol):
12+
def __lt__(self, other: object, /) -> bool: ...
13+
14+
15+
def merge[T: Comparable](collection: Iterable[T]) -> list[T]:
16+
"""Return a new list of ``collection`` sorted in ascending order.
617
18+
The input is copied, so the original iterable is left unchanged.
19+
Items must be mutually comparable with ``<``.
720
8-
def merge(arr: list[int]) -> list[int]:
9-
"""Return a sorted array.
1021
>>> merge([10,9,8,7,6,5,4,3,2,1])
1122
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
1223
>>> merge([1,2,3,4,5,6,7,8,9,10])
@@ -17,19 +28,30 @@ def merge(arr: list[int]) -> list[int]:
1728
[100]
1829
>>> merge([])
1930
[]
31+
>>> merge(["c", "a", "b"])
32+
['a', 'b', 'c']
33+
>>> merge([2.5, -1, 0.0])
34+
[-1, 0.0, 2.5]
35+
>>> values = [3, 1, 2]
36+
>>> merge(values)
37+
[1, 2, 3]
38+
>>> values
39+
[3, 1, 2]
40+
>>> merge(("b", "c", "a"))
41+
['a', 'b', 'c']
42+
>>> merge([1, "a"])
43+
Traceback (most recent call last):
44+
...
45+
TypeError: '<' not supported between instances of 'int' and 'str'
2046
"""
47+
arr = list(collection)
2148
if len(arr) > 1:
2249
middle_length = len(arr) // 2 # Finds the middle of the array
23-
left_array = arr[
24-
:middle_length
25-
] # Creates an array of the elements in the first half.
26-
right_array = arr[
27-
middle_length:
28-
] # Creates an array of the elements in the second half.
50+
# Sort each half into a new list, then combine those halves in ``arr``.
51+
left_array = merge(arr[:middle_length])
52+
right_array = merge(arr[middle_length:])
2953
left_size = len(left_array)
3054
right_size = len(right_array)
31-
merge(left_array) # Starts sorting the left.
32-
merge(right_array) # Starts sorting the right
3355
left_index = 0 # Left Counter
3456
right_index = 0 # Right Counter
3557
index = 0 # Position Counter
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()

0 commit comments

Comments
 (0)