mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-10-05 21:29:29 +00:00
Compare commits
12 Commits
255c6b3fdd
...
0b0d7945d1
Author | SHA1 | Date | |
---|---|---|---|
|
0b0d7945d1 | ||
|
fcf82a1eda | ||
|
ad6395d340 | ||
|
50aca04c67 | ||
|
5a8655d306 | ||
|
9a572dec2b | ||
|
f468b67211 | ||
|
bddf603c2c | ||
|
8785ab7c39 | ||
|
5c9d6709ea | ||
|
25a03cf503 | ||
|
3c22c60052 |
|
@ -96,7 +96,7 @@ We want your work to be readable by others; therefore, we encourage you to note
|
|||
|
||||
```bash
|
||||
python3 -m pip install ruff # only required the first time
|
||||
ruff .
|
||||
ruff check
|
||||
```
|
||||
|
||||
- Original code submission require docstrings or comments to describe your work.
|
||||
|
|
38
data_structures/stacks/lexicographical_numbers.py
Normal file
38
data_structures/stacks/lexicographical_numbers.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from collections.abc import Iterator
|
||||
|
||||
|
||||
def lexical_order(max_number: int) -> Iterator[int]:
|
||||
"""
|
||||
Generate numbers in lexical order from 1 to max_number.
|
||||
|
||||
>>> " ".join(map(str, lexical_order(13)))
|
||||
'1 10 11 12 13 2 3 4 5 6 7 8 9'
|
||||
>>> list(lexical_order(1))
|
||||
[1]
|
||||
>>> " ".join(map(str, lexical_order(20)))
|
||||
'1 10 11 12 13 14 15 16 17 18 19 2 20 3 4 5 6 7 8 9'
|
||||
>>> " ".join(map(str, lexical_order(25)))
|
||||
'1 10 11 12 13 14 15 16 17 18 19 2 20 21 22 23 24 25 3 4 5 6 7 8 9'
|
||||
>>> list(lexical_order(12))
|
||||
[1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
"""
|
||||
|
||||
stack = [1]
|
||||
|
||||
while stack:
|
||||
num = stack.pop()
|
||||
if num > max_number:
|
||||
continue
|
||||
|
||||
yield num
|
||||
if (num % 10) != 9:
|
||||
stack.append(num + 1)
|
||||
|
||||
stack.append(num * 10)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
print(f"Numbers from 1 to 25 in lexical order: {list(lexical_order(26))}")
|
|
@ -28,6 +28,24 @@ def longest_common_subsequence(x: str, y: str):
|
|||
(2, 'ph')
|
||||
>>> longest_common_subsequence("computer", "food")
|
||||
(1, 'o')
|
||||
>>> longest_common_subsequence("", "abc") # One string is empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "") # Other string is empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("", "") # Both strings are empty
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "def") # No common subsequence
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abc", "abc") # Identical strings
|
||||
(3, 'abc')
|
||||
>>> longest_common_subsequence("a", "a") # Single character match
|
||||
(1, 'a')
|
||||
>>> longest_common_subsequence("a", "b") # Single character no match
|
||||
(0, '')
|
||||
>>> longest_common_subsequence("abcdef", "ace") # Interleaved subsequence
|
||||
(3, 'ace')
|
||||
>>> longest_common_subsequence("ABCD", "ACBD") # No repeated characters
|
||||
(3, 'ABD')
|
||||
"""
|
||||
# find the length of strings
|
||||
|
||||
|
|
|
@ -1,54 +1,44 @@
|
|||
from math import asin, atan, cos, radians, sin, sqrt, tan
|
||||
|
||||
AXIS_A = 6378137.0
|
||||
AXIS_B = 6356752.314245
|
||||
RADIUS = 6378137
|
||||
from math import asin, cos, radians, sin, sqrt
|
||||
|
||||
|
||||
def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
|
||||
"""
|
||||
Calculate great circle distance between two points in a sphere,
|
||||
given longitudes and latitudes https://en.wikipedia.org/wiki/Haversine_formula
|
||||
|
||||
We know that the globe is "sort of" spherical, so a path between two points
|
||||
isn't exactly a straight line. We need to account for the Earth's curvature
|
||||
when calculating distance from point A to B. This effect is negligible for
|
||||
small distances but adds up as distance increases. The Haversine method treats
|
||||
the earth as a sphere which allows us to "project" the two points A and B
|
||||
onto the surface of that sphere and approximate the spherical distance between
|
||||
them. Since the Earth is not a perfect sphere, other methods which model the
|
||||
Earth's ellipsoidal nature are more accurate but a quick and modifiable
|
||||
computation like Haversine can be handy for shorter range distances.
|
||||
Calculate the great-circle distance between two points
|
||||
on the Earth specified by latitude and longitude using
|
||||
the Haversine formula.
|
||||
|
||||
Args:
|
||||
lat1, lon1: latitude and longitude of coordinate 1
|
||||
lat2, lon2: latitude and longitude of coordinate 2
|
||||
lat1, lon1: Latitude and longitude of point 1 in decimal degrees.
|
||||
lat2, lon2: Latitude and longitude of point 2 in decimal degrees.
|
||||
|
||||
|
||||
Returns:
|
||||
geographical distance between two points in metres
|
||||
Distance between the two points in meters.
|
||||
|
||||
>>> from collections import namedtuple
|
||||
>>> point_2d = namedtuple("point_2d", "lat lon")
|
||||
>>> SAN_FRANCISCO = point_2d(37.774856, -122.424227)
|
||||
>>> YOSEMITE = point_2d(37.864742, -119.537521)
|
||||
>>> f"{haversine_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters"
|
||||
'254,352 meters'
|
||||
'254,033 meters'
|
||||
"""
|
||||
# CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System
|
||||
# Distance in metres(m)
|
||||
# Equation parameters
|
||||
# Equation https://en.wikipedia.org/wiki/Haversine_formula#Formulation
|
||||
flattening = (AXIS_A - AXIS_B) / AXIS_A
|
||||
phi_1 = atan((1 - flattening) * tan(radians(lat1)))
|
||||
phi_2 = atan((1 - flattening) * tan(radians(lat2)))
|
||||
lambda_1 = radians(lon1)
|
||||
lambda_2 = radians(lon2)
|
||||
# Equation
|
||||
sin_sq_phi = sin((phi_2 - phi_1) / 2)
|
||||
sin_sq_lambda = sin((lambda_2 - lambda_1) / 2)
|
||||
# Square both values
|
||||
sin_sq_phi *= sin_sq_phi
|
||||
sin_sq_lambda *= sin_sq_lambda
|
||||
h_value = sqrt(sin_sq_phi + (cos(phi_1) * cos(phi_2) * sin_sq_lambda))
|
||||
return 2 * RADIUS * asin(h_value)
|
||||
|
||||
radius = 6378137 # earth radius (meters)
|
||||
|
||||
lat1_rad = radians(lat1)
|
||||
lat2_rad = radians(lat2)
|
||||
delta_lat = radians(lat2 - lat1)
|
||||
delta_lon = radians(lon2 - lon1)
|
||||
|
||||
# Haversine formula
|
||||
a = (
|
||||
sin(delta_lat / 2) ** 2
|
||||
+ cos(lat1_rad) * cos(lat2_rad) * sin(delta_lon / 2) ** 2
|
||||
)
|
||||
c = 2 * asin(sqrt(a))
|
||||
|
||||
# Great-Circle Distance
|
||||
return radius * c
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -15,22 +15,21 @@ def lamberts_ellipsoidal_distance(
|
|||
two points on the surface of earth given longitudes and latitudes
|
||||
https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines
|
||||
|
||||
NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle,
|
||||
sigma
|
||||
|
||||
NOTE: This algorithm uses geodesy/haversine_distance.py to compute the
|
||||
central angle, sigma
|
||||
Representing the earth as an ellipsoid allows us to approximate distances between
|
||||
points on the surface much better than a sphere. Ellipsoidal formulas treat the
|
||||
Earth as an oblate ellipsoid which means accounting for the flattening that happens
|
||||
at the North and South poles. Lambert's formulae provide accuracy on the order of
|
||||
10 meteres over thousands of kilometeres. Other methods can provide
|
||||
millimeter-level accuracy but this is a simpler method to calculate long range
|
||||
at the North and South poles. Lambert's formulas provide accuracy on the order of
|
||||
10 meters over thousands of kilometers. Other methods can provide
|
||||
millimeter-level accuracy, but this is a simpler method to calculate long-range
|
||||
distances without increasing computational intensity.
|
||||
|
||||
Args:
|
||||
lat1, lon1: latitude and longitude of coordinate 1
|
||||
lat2, lon2: latitude and longitude of coordinate 2
|
||||
Returns:
|
||||
geographical distance between two points in metres
|
||||
geographical distance between two points in meters
|
||||
|
||||
>>> from collections import namedtuple
|
||||
>>> point_2d = namedtuple("point_2d", "lat lon")
|
||||
|
@ -39,25 +38,20 @@ def lamberts_ellipsoidal_distance(
|
|||
>>> NEW_YORK = point_2d(40.713019, -74.012647)
|
||||
>>> VENICE = point_2d(45.443012, 12.313071)
|
||||
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters"
|
||||
'254,351 meters'
|
||||
'254,032 meters'
|
||||
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *NEW_YORK):0,.0f} meters"
|
||||
'4,138,992 meters'
|
||||
'4,133,295 meters'
|
||||
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *VENICE):0,.0f} meters"
|
||||
'9,737,326 meters'
|
||||
'9,719,525 meters'
|
||||
"""
|
||||
|
||||
# CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System
|
||||
# Distance in metres(m)
|
||||
# Equation Parameters
|
||||
# https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines
|
||||
flattening = (AXIS_A - AXIS_B) / AXIS_A
|
||||
# Parametric latitudes
|
||||
# https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude
|
||||
b_lat1 = atan((1 - flattening) * tan(radians(lat1)))
|
||||
b_lat2 = atan((1 - flattening) * tan(radians(lat2)))
|
||||
|
||||
# Compute central angle between two points
|
||||
# using haversine theta. sigma = haversine_distance / equatorial radius
|
||||
# Compute the central angle between two points using the haversine function
|
||||
sigma = haversine_distance(lat1, lon1, lat2, lon2) / EQUATORIAL_RADIUS
|
||||
|
||||
# Intermediate P and Q values
|
||||
|
@ -65,13 +59,11 @@ def lamberts_ellipsoidal_distance(
|
|||
q_value = (b_lat2 - b_lat1) / 2
|
||||
|
||||
# Intermediate X value
|
||||
# X = (sigma - sin(sigma)) * sin^2Pcos^2Q / cos^2(sigma/2)
|
||||
x_numerator = (sin(p_value) ** 2) * (cos(q_value) ** 2)
|
||||
x_demonimator = cos(sigma / 2) ** 2
|
||||
x_value = (sigma - sin(sigma)) * (x_numerator / x_demonimator)
|
||||
x_denominator = cos(sigma / 2) ** 2
|
||||
x_value = (sigma - sin(sigma)) * (x_numerator / x_denominator)
|
||||
|
||||
# Intermediate Y value
|
||||
# Y = (sigma + sin(sigma)) * cos^2Psin^2Q / sin^2(sigma/2)
|
||||
y_numerator = (cos(p_value) ** 2) * (sin(q_value) ** 2)
|
||||
y_denominator = sin(sigma / 2) ** 2
|
||||
y_value = (sigma + sin(sigma)) * (y_numerator / y_denominator)
|
||||
|
|
3
geodesy/temp_code_runner_file.py
Normal file
3
geodesy/temp_code_runner_file.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
# if __name__ == "__main__":
|
||||
# import doctest
|
||||
# doctest.testmod()
|
|
@ -7,6 +7,8 @@ the Binet's formula function because the Binet formula function uses floats
|
|||
|
||||
NOTE 2: the Binet's formula function is much more limited in the size of inputs
|
||||
that it can handle due to the size limitations of Python floats
|
||||
NOTE 3: the matrix function is the fastest and most memory efficient for large n
|
||||
|
||||
|
||||
See benchmark numbers in __main__ for performance comparisons/
|
||||
https://en.wikipedia.org/wiki/Fibonacci_number for more information
|
||||
|
@ -17,6 +19,9 @@ from collections.abc import Iterator
|
|||
from math import sqrt
|
||||
from time import time
|
||||
|
||||
import numpy as np
|
||||
from numpy import ndarray
|
||||
|
||||
|
||||
def time_func(func, *args, **kwargs):
|
||||
"""
|
||||
|
@ -230,6 +235,88 @@ def fib_binet(n: int) -> list[int]:
|
|||
return [round(phi**i / sqrt_5) for i in range(n + 1)]
|
||||
|
||||
|
||||
def matrix_pow_np(m: ndarray, power: int) -> ndarray:
|
||||
"""
|
||||
Raises a matrix to the power of 'power' using binary exponentiation.
|
||||
|
||||
Args:
|
||||
m: Matrix as a numpy array.
|
||||
power: The power to which the matrix is to be raised.
|
||||
|
||||
Returns:
|
||||
The matrix raised to the power.
|
||||
|
||||
Raises:
|
||||
ValueError: If power is negative.
|
||||
|
||||
>>> m = np.array([[1, 1], [1, 0]], dtype=int)
|
||||
>>> matrix_pow_np(m, 0) # Identity matrix when raised to the power of 0
|
||||
array([[1, 0],
|
||||
[0, 1]])
|
||||
|
||||
>>> matrix_pow_np(m, 1) # Same matrix when raised to the power of 1
|
||||
array([[1, 1],
|
||||
[1, 0]])
|
||||
|
||||
>>> matrix_pow_np(m, 5)
|
||||
array([[8, 5],
|
||||
[5, 3]])
|
||||
|
||||
>>> matrix_pow_np(m, -1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: power is negative
|
||||
"""
|
||||
result = np.array([[1, 0], [0, 1]], dtype=int) # Identity Matrix
|
||||
base = m
|
||||
if power < 0: # Negative power is not allowed
|
||||
raise ValueError("power is negative")
|
||||
while power:
|
||||
if power % 2 == 1:
|
||||
result = np.dot(result, base)
|
||||
base = np.dot(base, base)
|
||||
power //= 2
|
||||
return result
|
||||
|
||||
|
||||
def fib_matrix_np(n: int) -> int:
|
||||
"""
|
||||
Calculates the n-th Fibonacci number using matrix exponentiation.
|
||||
https://www.nayuki.io/page/fast-fibonacci-algorithms#:~:text=
|
||||
Summary:%20The%20two%20fast%20Fibonacci%20algorithms%20are%20matrix
|
||||
|
||||
Args:
|
||||
n: Fibonacci sequence index
|
||||
|
||||
Returns:
|
||||
The n-th Fibonacci number.
|
||||
|
||||
Raises:
|
||||
ValueError: If n is negative.
|
||||
|
||||
>>> fib_matrix_np(0)
|
||||
0
|
||||
>>> fib_matrix_np(1)
|
||||
1
|
||||
>>> fib_matrix_np(5)
|
||||
5
|
||||
>>> fib_matrix_np(10)
|
||||
55
|
||||
>>> fib_matrix_np(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: n is negative
|
||||
"""
|
||||
if n < 0:
|
||||
raise ValueError("n is negative")
|
||||
if n == 0:
|
||||
return 0
|
||||
|
||||
m = np.array([[1, 1], [1, 0]], dtype=int)
|
||||
result = matrix_pow_np(m, n - 1)
|
||||
return int(result[0, 0])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
|
@ -242,3 +329,4 @@ if __name__ == "__main__":
|
|||
time_func(fib_memoization, num) # 0.0100 ms
|
||||
time_func(fib_recursive_cached, num) # 0.0153 ms
|
||||
time_func(fib_recursive, num) # 257.0910 ms
|
||||
time_func(fib_matrix_np, num) # 0.0000 ms
|
||||
|
|
113
searches/exponential_search.py
Normal file
113
searches/exponential_search.py
Normal file
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Pure Python implementation of exponential search algorithm
|
||||
|
||||
For more information, see the Wikipedia page:
|
||||
https://en.wikipedia.org/wiki/Exponential_search
|
||||
|
||||
For doctests run the following command:
|
||||
python3 -m doctest -v exponential_search.py
|
||||
|
||||
For manual testing run:
|
||||
python3 exponential_search.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def binary_search_by_recursion(
|
||||
sorted_collection: list[int], item: int, left: int = 0, right: int = -1
|
||||
) -> int:
|
||||
"""Pure implementation of binary search algorithm in Python using recursion
|
||||
|
||||
Be careful: the collection must be ascending sorted otherwise, the result will be
|
||||
unpredictable.
|
||||
|
||||
:param sorted_collection: some ascending sorted collection with comparable items
|
||||
:param item: item value to search
|
||||
:param left: starting index for the search
|
||||
:param right: ending index for the search
|
||||
:return: index of the found item or -1 if the item is not found
|
||||
|
||||
Examples:
|
||||
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 0, 0, 4)
|
||||
0
|
||||
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 15, 0, 4)
|
||||
4
|
||||
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 5, 0, 4)
|
||||
1
|
||||
>>> binary_search_by_recursion([0, 5, 7, 10, 15], 6, 0, 4)
|
||||
-1
|
||||
"""
|
||||
if right < 0:
|
||||
right = len(sorted_collection) - 1
|
||||
if list(sorted_collection) != sorted(sorted_collection):
|
||||
raise ValueError("sorted_collection must be sorted in ascending order")
|
||||
if right < left:
|
||||
return -1
|
||||
|
||||
midpoint = left + (right - left) // 2
|
||||
|
||||
if sorted_collection[midpoint] == item:
|
||||
return midpoint
|
||||
elif sorted_collection[midpoint] > item:
|
||||
return binary_search_by_recursion(sorted_collection, item, left, midpoint - 1)
|
||||
else:
|
||||
return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right)
|
||||
|
||||
|
||||
def exponential_search(sorted_collection: list[int], item: int) -> int:
|
||||
"""
|
||||
Pure implementation of an exponential search algorithm in Python.
|
||||
For more information, refer to:
|
||||
https://en.wikipedia.org/wiki/Exponential_search
|
||||
|
||||
Be careful: the collection must be ascending sorted, otherwise the result will be
|
||||
unpredictable.
|
||||
|
||||
:param sorted_collection: some ascending sorted collection with comparable items
|
||||
:param item: item value to search
|
||||
:return: index of the found item or -1 if the item is not found
|
||||
|
||||
The time complexity of this algorithm is O(log i) where i is the index of the item.
|
||||
|
||||
Examples:
|
||||
>>> exponential_search([0, 5, 7, 10, 15], 0)
|
||||
0
|
||||
>>> exponential_search([0, 5, 7, 10, 15], 15)
|
||||
4
|
||||
>>> exponential_search([0, 5, 7, 10, 15], 5)
|
||||
1
|
||||
>>> exponential_search([0, 5, 7, 10, 15], 6)
|
||||
-1
|
||||
"""
|
||||
if list(sorted_collection) != sorted(sorted_collection):
|
||||
raise ValueError("sorted_collection must be sorted in ascending order")
|
||||
|
||||
if sorted_collection[0] == item:
|
||||
return 0
|
||||
|
||||
bound = 1
|
||||
while bound < len(sorted_collection) and sorted_collection[bound] < item:
|
||||
bound *= 2
|
||||
|
||||
left = bound // 2
|
||||
right = min(bound, len(sorted_collection) - 1)
|
||||
return binary_search_by_recursion(sorted_collection, item, left, right)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
# Manual testing
|
||||
user_input = input("Enter numbers separated by commas: ").strip()
|
||||
collection = sorted(int(item) for item in user_input.split(","))
|
||||
target = int(input("Enter a number to search for: "))
|
||||
result = exponential_search(sorted_collection=collection, item=target)
|
||||
if result == -1:
|
||||
print(f"{target} was not found in {collection}.")
|
||||
else:
|
||||
print(f"{target} was found at index {result} in {collection}.")
|
Loading…
Reference in New Issue
Block a user