mirror of
https://github.com/TheAlgorithms/Python.git
synced 2024-10-05 21:29:29 +00:00
Compare commits
9 Commits
7d538f1ecc
...
a1ed6eb566
Author | SHA1 | Date | |
---|---|---|---|
|
a1ed6eb566 | ||
|
fcf82a1eda | ||
|
ad6395d340 | ||
|
50aca04c67 | ||
|
5a8655d306 | ||
|
9a572dec2b | ||
|
59ff87dc55 | ||
|
917ad62105 | ||
|
e20b503b24 |
|
@ -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,36 +1,61 @@
|
|||
def topological_sort(graph):
|
||||
def topological_sort(graph: dict[int, list[int]]) -> list[int] | None:
|
||||
"""
|
||||
Kahn's Algorithm is used to find Topological ordering of Directed Acyclic Graph
|
||||
using BFS
|
||||
Perform topological sorting of a Directed Acyclic Graph (DAG)
|
||||
using Kahn's Algorithm via Breadth-First Search (BFS).
|
||||
|
||||
Topological sorting is a linear ordering of vertices in a graph such that for
|
||||
every directed edge u → v, vertex u comes before vertex v in the ordering.
|
||||
|
||||
Parameters:
|
||||
graph: Adjacency list representing the directed graph where keys are
|
||||
vertices, and values are lists of adjacent vertices.
|
||||
|
||||
Returns:
|
||||
The topologically sorted order of vertices if the graph is a DAG.
|
||||
Returns None if the graph contains a cycle.
|
||||
|
||||
Example:
|
||||
>>> graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}
|
||||
>>> topological_sort(graph)
|
||||
[0, 1, 2, 3, 4, 5]
|
||||
|
||||
>>> graph_with_cycle = {0: [1], 1: [2], 2: [0]}
|
||||
>>> topological_sort(graph_with_cycle)
|
||||
"""
|
||||
|
||||
indegree = [0] * len(graph)
|
||||
queue = []
|
||||
topo = []
|
||||
cnt = 0
|
||||
topo_order = []
|
||||
processed_vertices_count = 0
|
||||
|
||||
# Calculate the indegree of each vertex
|
||||
for values in graph.values():
|
||||
for i in values:
|
||||
indegree[i] += 1
|
||||
|
||||
# Add all vertices with 0 indegree to the queue
|
||||
for i in range(len(indegree)):
|
||||
if indegree[i] == 0:
|
||||
queue.append(i)
|
||||
|
||||
# Perform BFS
|
||||
while queue:
|
||||
vertex = queue.pop(0)
|
||||
cnt += 1
|
||||
topo.append(vertex)
|
||||
for x in graph[vertex]:
|
||||
indegree[x] -= 1
|
||||
if indegree[x] == 0:
|
||||
queue.append(x)
|
||||
processed_vertices_count += 1
|
||||
topo_order.append(vertex)
|
||||
|
||||
if cnt != len(graph):
|
||||
print("Cycle exists")
|
||||
else:
|
||||
print(topo)
|
||||
# Traverse neighbors
|
||||
for neighbor in graph[vertex]:
|
||||
indegree[neighbor] -= 1
|
||||
if indegree[neighbor] == 0:
|
||||
queue.append(neighbor)
|
||||
|
||||
if processed_vertices_count != len(graph):
|
||||
return None # no topological ordering exists due to cycle
|
||||
return topo_order # valid topological ordering
|
||||
|
||||
|
||||
# Adjacency List of Graph
|
||||
graph = {0: [1, 2], 1: [3], 2: [3], 3: [4, 5], 4: [], 5: []}
|
||||
topological_sort(graph)
|
||||
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}.")
|
|
@ -17,11 +17,27 @@ def compute_transform_tables(
|
|||
delete_cost: int,
|
||||
insert_cost: int,
|
||||
) -> tuple[list[list[int]], list[list[str]]]:
|
||||
"""
|
||||
Finds the most cost efficient sequence
|
||||
for converting one string into another.
|
||||
|
||||
>>> costs, operations = compute_transform_tables("cat", "cut", 1, 2, 3, 3)
|
||||
>>> costs[0][:4]
|
||||
[0, 3, 6, 9]
|
||||
>>> costs[2][:4]
|
||||
[6, 4, 3, 6]
|
||||
>>> operations[0][:4]
|
||||
['0', 'Ic', 'Iu', 'It']
|
||||
>>> operations[3][:4]
|
||||
['Dt', 'Dt', 'Rtu', 'Ct']
|
||||
|
||||
>>> compute_transform_tables("", "", 1, 2, 3, 3)
|
||||
([[0]], [['0']])
|
||||
"""
|
||||
source_seq = list(source_string)
|
||||
destination_seq = list(destination_string)
|
||||
len_source_seq = len(source_seq)
|
||||
len_destination_seq = len(destination_seq)
|
||||
|
||||
costs = [
|
||||
[0 for _ in range(len_destination_seq + 1)] for _ in range(len_source_seq + 1)
|
||||
]
|
||||
|
@ -31,33 +47,51 @@ def compute_transform_tables(
|
|||
|
||||
for i in range(1, len_source_seq + 1):
|
||||
costs[i][0] = i * delete_cost
|
||||
ops[i][0] = f"D{source_seq[i - 1]:c}"
|
||||
ops[i][0] = f"D{source_seq[i - 1]}"
|
||||
|
||||
for i in range(1, len_destination_seq + 1):
|
||||
costs[0][i] = i * insert_cost
|
||||
ops[0][i] = f"I{destination_seq[i - 1]:c}"
|
||||
ops[0][i] = f"I{destination_seq[i - 1]}"
|
||||
|
||||
for i in range(1, len_source_seq + 1):
|
||||
for j in range(1, len_destination_seq + 1):
|
||||
if source_seq[i - 1] == destination_seq[j - 1]:
|
||||
costs[i][j] = costs[i - 1][j - 1] + copy_cost
|
||||
ops[i][j] = f"C{source_seq[i - 1]:c}"
|
||||
ops[i][j] = f"C{source_seq[i - 1]}"
|
||||
else:
|
||||
costs[i][j] = costs[i - 1][j - 1] + replace_cost
|
||||
ops[i][j] = f"R{source_seq[i - 1]:c}" + str(destination_seq[j - 1])
|
||||
ops[i][j] = f"R{source_seq[i - 1]}" + str(destination_seq[j - 1])
|
||||
|
||||
if costs[i - 1][j] + delete_cost < costs[i][j]:
|
||||
costs[i][j] = costs[i - 1][j] + delete_cost
|
||||
ops[i][j] = f"D{source_seq[i - 1]:c}"
|
||||
ops[i][j] = f"D{source_seq[i - 1]}"
|
||||
|
||||
if costs[i][j - 1] + insert_cost < costs[i][j]:
|
||||
costs[i][j] = costs[i][j - 1] + insert_cost
|
||||
ops[i][j] = f"I{destination_seq[j - 1]:c}"
|
||||
ops[i][j] = f"I{destination_seq[j - 1]}"
|
||||
|
||||
return costs, ops
|
||||
|
||||
|
||||
def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]:
|
||||
"""
|
||||
Assembles the transformations based on the ops table.
|
||||
|
||||
>>> ops = [['0', 'Ic', 'Iu', 'It'],
|
||||
... ['Dc', 'Cc', 'Iu', 'It'],
|
||||
... ['Da', 'Da', 'Rau', 'Rat'],
|
||||
... ['Dt', 'Dt', 'Rtu', 'Ct']]
|
||||
>>> x = len(ops) - 1
|
||||
>>> y = len(ops[0]) - 1
|
||||
>>> assemble_transformation(ops, x, y)
|
||||
['Cc', 'Rau', 'Ct']
|
||||
|
||||
>>> ops1 = [['0']]
|
||||
>>> x1 = len(ops1) - 1
|
||||
>>> y1 = len(ops1[0]) - 1
|
||||
>>> assemble_transformation(ops1, x1, y1)
|
||||
[]
|
||||
"""
|
||||
if i == 0 and j == 0:
|
||||
return []
|
||||
elif ops[i][j][0] in {"C", "R"}:
|
||||
|
|
Loading…
Reference in New Issue
Block a user