pure-python-ds 1.0.0__py3-none-any.whl
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.
- pure_python_ds/__init__.py +0 -0
- pure_python_ds/algorithms/__init__.py +3 -0
- pure_python_ds/algorithms/dp.py +16 -0
- pure_python_ds/algorithms/searching.py +22 -0
- pure_python_ds/algorithms/sorting.py +36 -0
- pure_python_ds/graphs/__init__.py +2 -0
- pure_python_ds/graphs/dsu.py +22 -0
- pure_python_ds/graphs/graph.py +106 -0
- pure_python_ds/linear/__init__.py +6 -0
- pure_python_ds/linear/doubly_linked_list.py +91 -0
- pure_python_ds/linear/hash_table.py +33 -0
- pure_python_ds/linear/heap.py +102 -0
- pure_python_ds/linear/queue.py +24 -0
- pure_python_ds/linear/singly_linked_list.py +106 -0
- pure_python_ds/linear/stack.py +28 -0
- pure_python_ds/nodes/__init__.py +25 -0
- pure_python_ds/nodes/b_tree_node.py +9 -0
- pure_python_ds/nodes/rb_node.py +16 -0
- pure_python_ds/trees/__init__.py +7 -0
- pure_python_ds/trees/avl_tree.py +142 -0
- pure_python_ds/trees/b_tree.py +55 -0
- pure_python_ds/trees/binary_search_tree.py +88 -0
- pure_python_ds/trees/binary_tree.py +69 -0
- pure_python_ds/trees/red_black_tree.py +111 -0
- pure_python_ds/trees/segment_tree.py +35 -0
- pure_python_ds/trees/trie.py +50 -0
- pure_python_ds/trees/utils.py +26 -0
- pure_python_ds-1.0.0.dist-info/METADATA +69 -0
- pure_python_ds-1.0.0.dist-info/RECORD +32 -0
- pure_python_ds-1.0.0.dist-info/WHEEL +5 -0
- pure_python_ds-1.0.0.dist-info/licenses/LICENSE +21 -0
- pure_python_ds-1.0.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing import Dict
|
|
2
|
+
|
|
3
|
+
def fibonacci(n: int, memo: Dict[int, int] = None) -> int:
|
|
4
|
+
"""
|
|
5
|
+
Computes the nth Fibonacci number using Top-Down DP (Memoization).
|
|
6
|
+
O(n) time complexity vs O(2^n) recursive complexity.
|
|
7
|
+
"""
|
|
8
|
+
if memo is None:
|
|
9
|
+
memo = {}
|
|
10
|
+
if n in memo:
|
|
11
|
+
return memo[n]
|
|
12
|
+
if n <= 1:
|
|
13
|
+
return n
|
|
14
|
+
|
|
15
|
+
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
|
|
16
|
+
return memo[n]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import List, TypeVar, Optional, Any
|
|
2
|
+
|
|
3
|
+
T = TypeVar('T')
|
|
4
|
+
|
|
5
|
+
def binary_search(arr: List[Any], target: Any) -> int:
|
|
6
|
+
"""
|
|
7
|
+
Performs an iterative binary search on a sorted list.
|
|
8
|
+
Returns the index of the target if found, else -1.
|
|
9
|
+
"""
|
|
10
|
+
low = 0
|
|
11
|
+
high = len(arr) - 1
|
|
12
|
+
|
|
13
|
+
while low <= high:
|
|
14
|
+
mid = (low + high) // 2
|
|
15
|
+
if arr[mid] == target:
|
|
16
|
+
return mid
|
|
17
|
+
elif arr[mid] < target:
|
|
18
|
+
low = mid + 1
|
|
19
|
+
else:
|
|
20
|
+
high = mid - 1
|
|
21
|
+
|
|
22
|
+
return -1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import List, TypeVar
|
|
2
|
+
|
|
3
|
+
T = TypeVar('T')
|
|
4
|
+
|
|
5
|
+
def merge_sort(arr: List[T]) -> List[T]:
|
|
6
|
+
"""
|
|
7
|
+
Perform an O(n log n) Merge Sort on a list.
|
|
8
|
+
Returns a new sorted list.
|
|
9
|
+
"""
|
|
10
|
+
if len(arr) <= 1:
|
|
11
|
+
return arr
|
|
12
|
+
|
|
13
|
+
mid = len(arr) // 2
|
|
14
|
+
# Recursive split
|
|
15
|
+
left = merge_sort(arr[:mid])
|
|
16
|
+
right = merge_sort(arr[mid:])
|
|
17
|
+
|
|
18
|
+
return _merge(left, right)
|
|
19
|
+
|
|
20
|
+
def _merge(left: List[T], right: List[T]) -> List[T]:
|
|
21
|
+
"""Helper method to merge two sorted lists into one."""
|
|
22
|
+
result = []
|
|
23
|
+
i = j = 0
|
|
24
|
+
|
|
25
|
+
while i < len(left) and j < len(right):
|
|
26
|
+
if left[i] < right[j]:
|
|
27
|
+
result.append(left[i])
|
|
28
|
+
i += 1
|
|
29
|
+
else:
|
|
30
|
+
result.append(right[j])
|
|
31
|
+
j += 1
|
|
32
|
+
|
|
33
|
+
# Add remaining elements
|
|
34
|
+
result.extend(left[i:])
|
|
35
|
+
result.extend(right[j:])
|
|
36
|
+
return result
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class DSU:
|
|
2
|
+
def __init__(self, n: int):
|
|
3
|
+
self.parent = list(range(n))
|
|
4
|
+
self.rank = [0] * n
|
|
5
|
+
|
|
6
|
+
def find(self, i: int) -> int:
|
|
7
|
+
if self.parent[i] == i:
|
|
8
|
+
return i
|
|
9
|
+
self.parent[i] = self.find(self.parent[i]) # Path Compression
|
|
10
|
+
return self.parent[i]
|
|
11
|
+
|
|
12
|
+
def union(self, i: int, j: int):
|
|
13
|
+
root_i = self.find(i)
|
|
14
|
+
root_j = self.find(j)
|
|
15
|
+
if root_i != root_j:
|
|
16
|
+
if self.rank[root_i] < self.rank[root_j]:
|
|
17
|
+
self.parent[root_i] = root_j
|
|
18
|
+
elif self.rank[root_i] > self.rank[root_j]:
|
|
19
|
+
self.parent[root_j] = root_i
|
|
20
|
+
else:
|
|
21
|
+
self.parent[root_i] = root_j
|
|
22
|
+
self.rank[root_j] += 1
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional, Set, Dict, List, Tuple
|
|
2
|
+
import heapq # We'll use Python's built-in min-heap for O(log n) efficiency here
|
|
3
|
+
|
|
4
|
+
T = TypeVar('T')
|
|
5
|
+
|
|
6
|
+
class Graph(Generic[T]):
|
|
7
|
+
def __init__(self, directed: bool = False):
|
|
8
|
+
# Updated to store {vertex: {neighbor: weight}}
|
|
9
|
+
self._adj_list: Dict[T, Dict[T, float]] = {}
|
|
10
|
+
self.directed = directed
|
|
11
|
+
|
|
12
|
+
def add_vertex(self, vertex: T) -> None:
|
|
13
|
+
if vertex not in self._adj_list:
|
|
14
|
+
self._adj_list[vertex] = {}
|
|
15
|
+
|
|
16
|
+
def add_edge(self, v1: T, v2: T, weight: float = 1.0) -> None:
|
|
17
|
+
"""Adds a weighted edge. Defaults to 1.0 if not specified."""
|
|
18
|
+
self.add_vertex(v1)
|
|
19
|
+
self.add_vertex(v2)
|
|
20
|
+
self._adj_list[v1][v2] = weight
|
|
21
|
+
if not self.directed:
|
|
22
|
+
self._adj_list[v2][v1] = weight
|
|
23
|
+
|
|
24
|
+
def dijkstra(self, start: T) -> Dict[T, float]:
|
|
25
|
+
"""
|
|
26
|
+
Greedy algorithm to find the shortest distance from 'start' to all other nodes.
|
|
27
|
+
Returns a dictionary of {vertex: min_distance}.
|
|
28
|
+
"""
|
|
29
|
+
# Distances from start to all other nodes are initially infinity
|
|
30
|
+
distances = {vertex: float('inf') for vertex in self._adj_list}
|
|
31
|
+
distances[start] = 0
|
|
32
|
+
|
|
33
|
+
# Priority Queue: (distance, vertex)
|
|
34
|
+
pq = [(0, start)]
|
|
35
|
+
|
|
36
|
+
while pq:
|
|
37
|
+
current_distance, current_vertex = heapq.heappop(pq)
|
|
38
|
+
|
|
39
|
+
# If we found a longer path than we already recorded, skip it
|
|
40
|
+
if current_distance > distances[current_vertex]:
|
|
41
|
+
continue
|
|
42
|
+
|
|
43
|
+
for neighbor, weight in self._adj_list[current_vertex].items():
|
|
44
|
+
distance = current_distance + weight
|
|
45
|
+
|
|
46
|
+
# If this path is shorter, update and push to PQ
|
|
47
|
+
if distance < distances[neighbor]:
|
|
48
|
+
distances[neighbor] = distance
|
|
49
|
+
heapq.heappush(pq, (distance, neighbor))
|
|
50
|
+
|
|
51
|
+
return distances
|
|
52
|
+
def kruskal_mst(self) -> List[Tuple[T, T, float]]:
|
|
53
|
+
"""
|
|
54
|
+
Finds the Minimum Spanning Tree using the library's DSU module.
|
|
55
|
+
Returns a list of edges (v1, v2, weight) in the MST.
|
|
56
|
+
"""
|
|
57
|
+
from pure_python_ds.graphs import DSU
|
|
58
|
+
|
|
59
|
+
edges = []
|
|
60
|
+
# Gather all unique edges
|
|
61
|
+
for u in self._adj_list:
|
|
62
|
+
for v, weight in self._adj_list[u].items():
|
|
63
|
+
if self.directed or (v, u, weight) not in edges:
|
|
64
|
+
edges.append((u, v, weight))
|
|
65
|
+
|
|
66
|
+
# Sort edges by weight
|
|
67
|
+
edges.sort(key=lambda x: x[2])
|
|
68
|
+
|
|
69
|
+
# Map vertices to integers for DSU
|
|
70
|
+
nodes = list(self._adj_list.keys())
|
|
71
|
+
node_to_idx = {node: i for i, node in enumerate(nodes)}
|
|
72
|
+
|
|
73
|
+
dsu = DSU(len(nodes))
|
|
74
|
+
mst = []
|
|
75
|
+
|
|
76
|
+
for u, v, weight in edges:
|
|
77
|
+
if dsu.find(node_to_idx[u]) != dsu.find(node_to_idx[v]):
|
|
78
|
+
dsu.union(node_to_idx[u], node_to_idx[v])
|
|
79
|
+
mst.append((u, v, weight))
|
|
80
|
+
|
|
81
|
+
return mst
|
|
82
|
+
def bellman_ford(self, start: T) -> Dict[T, float]:
|
|
83
|
+
"""
|
|
84
|
+
Shortest path algorithm that handles negative weights.
|
|
85
|
+
Returns distances or raises ValueError if a negative cycle is found.
|
|
86
|
+
"""
|
|
87
|
+
distances = {vertex: float('inf') for vertex in self._adj_list}
|
|
88
|
+
distances[start] = 0
|
|
89
|
+
|
|
90
|
+
# Relax edges |V| - 1 times
|
|
91
|
+
for _ in range(len(self._adj_list) - 1):
|
|
92
|
+
for u in self._adj_list:
|
|
93
|
+
for v, weight in self._adj_list[u].items():
|
|
94
|
+
if distances[u] + weight < distances[v]:
|
|
95
|
+
distances[v] = distances[u] + weight
|
|
96
|
+
|
|
97
|
+
# Check for negative cycles
|
|
98
|
+
for u in self._adj_list:
|
|
99
|
+
for v, weight in self._adj_list[u].items():
|
|
100
|
+
if distances[u] + weight < distances[v]:
|
|
101
|
+
raise ValueError("Graph contains a negative weight cycle")
|
|
102
|
+
|
|
103
|
+
return distances
|
|
104
|
+
def has_edge(self, u: T, v: T) -> bool:
|
|
105
|
+
"""Returns True if there is an edge from u to v."""
|
|
106
|
+
return u in self._adj_list and v in self._adj_list[u]
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional, Generator
|
|
2
|
+
from pure_python_ds.nodes import ListNode
|
|
3
|
+
|
|
4
|
+
T = TypeVar('T')
|
|
5
|
+
|
|
6
|
+
class DoublyLinkedList(Generic[T]):
|
|
7
|
+
"""A strictly typed, memory-optimized Doubly Linked List."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.head: Optional[ListNode[T]] = None
|
|
11
|
+
self.tail: Optional[ListNode[T]] = None
|
|
12
|
+
self._length: int = 0
|
|
13
|
+
|
|
14
|
+
def __len__(self) -> int:
|
|
15
|
+
return self._length
|
|
16
|
+
|
|
17
|
+
def append(self, value: T) -> None:
|
|
18
|
+
"""Adds a node to the end in O(1) time."""
|
|
19
|
+
new_node = ListNode(value)
|
|
20
|
+
if not self.head or not self.tail:
|
|
21
|
+
self.head = new_node
|
|
22
|
+
self.tail = new_node
|
|
23
|
+
else:
|
|
24
|
+
self.tail.next = new_node
|
|
25
|
+
new_node.prev = self.tail
|
|
26
|
+
self.tail = new_node
|
|
27
|
+
self._length += 1
|
|
28
|
+
|
|
29
|
+
def prepend(self, value: T) -> None:
|
|
30
|
+
"""Adds a node to the beginning in O(1) time."""
|
|
31
|
+
new_node = ListNode(value)
|
|
32
|
+
if not self.head or not self.tail:
|
|
33
|
+
self.head = new_node
|
|
34
|
+
self.tail = new_node
|
|
35
|
+
else:
|
|
36
|
+
new_node.next = self.head
|
|
37
|
+
self.head.prev = new_node
|
|
38
|
+
self.head = new_node
|
|
39
|
+
self._length += 1
|
|
40
|
+
|
|
41
|
+
def remove_tail(self) -> Optional[T]:
|
|
42
|
+
"""Removes the last node in O(1) time (Impossible in Singly Linked Lists!)."""
|
|
43
|
+
if not self.tail:
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
value = self.tail.value
|
|
47
|
+
if self.head is self.tail: # Only one element
|
|
48
|
+
self.head = None
|
|
49
|
+
self.tail = None
|
|
50
|
+
else:
|
|
51
|
+
self.tail = self.tail.prev
|
|
52
|
+
self.tail.next = None # Sever the tie
|
|
53
|
+
|
|
54
|
+
self._length -= 1
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
def __iter__(self) -> Generator[T, None, None]:
|
|
58
|
+
"""Forward generator traversal."""
|
|
59
|
+
current = self.head
|
|
60
|
+
while current:
|
|
61
|
+
yield current.value
|
|
62
|
+
current = current.next
|
|
63
|
+
|
|
64
|
+
def __str__(self) -> str:
|
|
65
|
+
"""Visual representation showing bidirectional pointers."""
|
|
66
|
+
values = [str(val) for val in self]
|
|
67
|
+
return "None <- " + " <-> ".join(values) + " -> None" if values else "Empty List"
|
|
68
|
+
def remove(self, value: T):
|
|
69
|
+
"""Removes the first occurrence of value from the list."""
|
|
70
|
+
current = self.head
|
|
71
|
+
while current:
|
|
72
|
+
if current.value == value:
|
|
73
|
+
# If it's the head
|
|
74
|
+
if current == self.head:
|
|
75
|
+
self.head = current.next
|
|
76
|
+
if self.head:
|
|
77
|
+
self.head.prev = None
|
|
78
|
+
else:
|
|
79
|
+
self.tail = None
|
|
80
|
+
# If it's the tail
|
|
81
|
+
elif current == self.tail:
|
|
82
|
+
self.tail = current.prev
|
|
83
|
+
if self.tail:
|
|
84
|
+
self.tail.next = None
|
|
85
|
+
# If it's in the middle
|
|
86
|
+
else:
|
|
87
|
+
current.prev.next = current.next
|
|
88
|
+
current.next.prev = current.prev
|
|
89
|
+
return True
|
|
90
|
+
current = current.next
|
|
91
|
+
return False
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Any, List, Optional
|
|
2
|
+
from pure_python_ds.linear.singly_linked_list import SinglyLinkedList
|
|
3
|
+
|
|
4
|
+
class HashTable:
|
|
5
|
+
def __init__(self, size: int = 10):
|
|
6
|
+
self.size = size
|
|
7
|
+
self.table: List[Optional[SinglyLinkedList]] = [None] * size
|
|
8
|
+
|
|
9
|
+
def _hash(self, key: str) -> int:
|
|
10
|
+
return sum(ord(char) for char in key) % self.size
|
|
11
|
+
|
|
12
|
+
def set(self, key: str, value: Any):
|
|
13
|
+
index = self._hash(key)
|
|
14
|
+
if not self.table[index]:
|
|
15
|
+
self.table[index] = SinglyLinkedList()
|
|
16
|
+
|
|
17
|
+
# Check if key exists to update, else append
|
|
18
|
+
current = self.table[index].head
|
|
19
|
+
while current:
|
|
20
|
+
if current.value[0] == key:
|
|
21
|
+
current.value = (key, value)
|
|
22
|
+
return
|
|
23
|
+
current = current.next
|
|
24
|
+
self.table[index].append((key, value))
|
|
25
|
+
|
|
26
|
+
def get(self, key: str) -> Any:
|
|
27
|
+
index = self._hash(key)
|
|
28
|
+
if not self.table[index]: return None
|
|
29
|
+
current = self.table[index].head
|
|
30
|
+
while current:
|
|
31
|
+
if current.value[0] == key: return current.value[1]
|
|
32
|
+
current = current.next
|
|
33
|
+
return None
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
from typing import List, TypeVar, Generic, Optional
|
|
2
|
+
|
|
3
|
+
T = TypeVar('T')
|
|
4
|
+
|
|
5
|
+
class MinHeap(Generic[T]):
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.heap: List[T] = []
|
|
8
|
+
|
|
9
|
+
def push(self, val: T) -> None:
|
|
10
|
+
self.heap.append(val)
|
|
11
|
+
self._bubble_up(len(self.heap) - 1)
|
|
12
|
+
|
|
13
|
+
def pop(self) -> Optional[T]:
|
|
14
|
+
if len(self.heap) == 0: return None
|
|
15
|
+
if len(self.heap) == 1: return self.heap.pop()
|
|
16
|
+
|
|
17
|
+
root = self.heap[0]
|
|
18
|
+
self.heap[0] = self.heap.pop()
|
|
19
|
+
self._bubble_down(0)
|
|
20
|
+
return root
|
|
21
|
+
|
|
22
|
+
def _bubble_up(self, index: int):
|
|
23
|
+
parent = (index - 1) // 2
|
|
24
|
+
# Min-Heap check: Child < Parent
|
|
25
|
+
if index > 0 and self.heap[index] < self.heap[parent]:
|
|
26
|
+
self.heap[index], self.heap[parent] = self.heap[parent], self.heap[index]
|
|
27
|
+
self._bubble_up(parent)
|
|
28
|
+
|
|
29
|
+
def _bubble_down(self, index: int):
|
|
30
|
+
smallest = index
|
|
31
|
+
left, right = 2 * index + 1, 2 * index + 2
|
|
32
|
+
|
|
33
|
+
for child in [left, right]:
|
|
34
|
+
if child < len(self.heap) and self.heap[child] < self.heap[smallest]:
|
|
35
|
+
smallest = child
|
|
36
|
+
|
|
37
|
+
if smallest != index:
|
|
38
|
+
self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
|
|
39
|
+
self._bubble_down(smallest)
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def size(self) -> int:
|
|
43
|
+
return len(self.heap)
|
|
44
|
+
@classmethod
|
|
45
|
+
def heapify(cls, data: List[T]) -> 'MinHeap[T]':
|
|
46
|
+
"""Transforms an unsorted list into a MinHeap in O(n) time."""
|
|
47
|
+
instance = cls()
|
|
48
|
+
instance.heap = list(data)
|
|
49
|
+
for i in range(len(instance.heap) // 2 - 1, -1, -1):
|
|
50
|
+
instance._bubble_down(i)
|
|
51
|
+
return instance
|
|
52
|
+
|
|
53
|
+
class MaxHeap(Generic[T]):
|
|
54
|
+
"""Native Max-Heap to avoid negating values for heapq-like logic."""
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self.heap: List[T] = []
|
|
57
|
+
|
|
58
|
+
def push(self, val: T) -> None:
|
|
59
|
+
self.heap.append(val)
|
|
60
|
+
self._bubble_up(len(self.heap) - 1)
|
|
61
|
+
|
|
62
|
+
def pop(self) -> Optional[T]:
|
|
63
|
+
if len(self.heap) == 0: return None
|
|
64
|
+
if len(self.heap) == 1: return self.heap.pop()
|
|
65
|
+
|
|
66
|
+
root = self.heap[0]
|
|
67
|
+
self.heap[0] = self.heap.pop()
|
|
68
|
+
self._bubble_down(0)
|
|
69
|
+
return root
|
|
70
|
+
|
|
71
|
+
def _bubble_up(self, index: int):
|
|
72
|
+
parent = (index - 1) // 2
|
|
73
|
+
# Max-Heap check: Child > Parent
|
|
74
|
+
if index > 0 and self.heap[index] > self.heap[parent]:
|
|
75
|
+
self.heap[index], self.heap[parent] = self.heap[parent], self.heap[index]
|
|
76
|
+
self._bubble_up(parent)
|
|
77
|
+
|
|
78
|
+
def _bubble_down(self, index: int):
|
|
79
|
+
largest = index
|
|
80
|
+
left, right = 2 * index + 1, 2 * index + 2
|
|
81
|
+
|
|
82
|
+
for child in [left, right]:
|
|
83
|
+
# Max-Heap check: Child > Parent
|
|
84
|
+
if child < len(self.heap) and self.heap[child] > self.heap[largest]:
|
|
85
|
+
largest = child
|
|
86
|
+
|
|
87
|
+
if largest != index:
|
|
88
|
+
self.heap[index], self.heap[largest] = self.heap[largest], self.heap[index]
|
|
89
|
+
self._bubble_down(largest)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def size(self) -> int:
|
|
93
|
+
return len(self.heap)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def heapify(cls, data: List[T]) -> 'MaxHeap[T]':
|
|
97
|
+
"""Transforms an unsorted list into a MaxHeap in O(n) time."""
|
|
98
|
+
instance = cls()
|
|
99
|
+
instance.heap = list(data)
|
|
100
|
+
for i in range(len(instance.heap) // 2 - 1, -1, -1):
|
|
101
|
+
instance._bubble_down(i)
|
|
102
|
+
return instance
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional
|
|
2
|
+
from pure_python_ds.linear.singly_linked_list import SinglyLinkedList
|
|
3
|
+
|
|
4
|
+
T = TypeVar('T')
|
|
5
|
+
|
|
6
|
+
class Queue(Generic[T]):
|
|
7
|
+
"""A strictly typed, memory-optimized Queue (FIFO)."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._container = SinglyLinkedList[T]()
|
|
11
|
+
|
|
12
|
+
def __len__(self) -> int:
|
|
13
|
+
return len(self._container)
|
|
14
|
+
|
|
15
|
+
def enqueue(self, value: T) -> None:
|
|
16
|
+
"""Adds an item to the back of the queue in O(1) time."""
|
|
17
|
+
self._container.append(value)
|
|
18
|
+
|
|
19
|
+
def dequeue(self) -> Optional[T]:
|
|
20
|
+
"""Removes and returns the front item in O(1) time."""
|
|
21
|
+
return self._container.remove_head()
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
return f"Queue Front -> {self._container}"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional, Generator
|
|
2
|
+
from pure_python_ds.nodes import ListNode
|
|
3
|
+
|
|
4
|
+
# Define the generic type variable
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
|
|
7
|
+
class SinglyLinkedList(Generic[T]):
|
|
8
|
+
"""
|
|
9
|
+
A strictly typed, memory-optimized Singly Linked List.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.head: Optional[ListNode[T]] = None
|
|
13
|
+
self.tail: Optional[ListNode[T]] = None
|
|
14
|
+
self._length: int = 0
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Provides a visual representation of the Linked List.
|
|
19
|
+
Example: Future -> Systems -> Architect -> None
|
|
20
|
+
"""
|
|
21
|
+
# We leverage our own generator to seamlessly loop through the values
|
|
22
|
+
values = [str(node_val) for node_val in self]
|
|
23
|
+
values.append("None")
|
|
24
|
+
return " -> ".join(values)
|
|
25
|
+
def __len__(self) -> int:
|
|
26
|
+
"""Allows the user to call len(linked_list) in O(1) time."""
|
|
27
|
+
return self._length
|
|
28
|
+
|
|
29
|
+
def append(self, value: T) -> None:
|
|
30
|
+
"""Adds a new node to the end of the list in O(1) time."""
|
|
31
|
+
new_node = ListNode(value)
|
|
32
|
+
if not self.head or not self.tail:
|
|
33
|
+
self.head = new_node
|
|
34
|
+
self.tail = new_node
|
|
35
|
+
else:
|
|
36
|
+
self.tail.next = new_node
|
|
37
|
+
self.tail = new_node
|
|
38
|
+
self._length += 1
|
|
39
|
+
|
|
40
|
+
def prepend(self, value: T) -> None:
|
|
41
|
+
"""Adds a new node to the beginning of the list in O(1) time."""
|
|
42
|
+
new_node = ListNode(value)
|
|
43
|
+
if not self.head or not self.tail:
|
|
44
|
+
self.head = new_node
|
|
45
|
+
self.tail = new_node
|
|
46
|
+
else:
|
|
47
|
+
new_node.next = self.head
|
|
48
|
+
self.head = new_node
|
|
49
|
+
self._length += 1
|
|
50
|
+
|
|
51
|
+
def __iter__(self) -> Generator[T, None, None]:
|
|
52
|
+
"""
|
|
53
|
+
Generator-based traversal.
|
|
54
|
+
Allows users to run: `for val in linked_list:` with O(1) memory overhead.
|
|
55
|
+
"""
|
|
56
|
+
current = self.head
|
|
57
|
+
while current:
|
|
58
|
+
yield current.value
|
|
59
|
+
current = current.next
|
|
60
|
+
def remove_head(self) -> Optional[T]:
|
|
61
|
+
"""Removes and returns the first node's value in O(1) time."""
|
|
62
|
+
if not self.head:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
value = self.head.value
|
|
66
|
+
self.head = self.head.next
|
|
67
|
+
self._length -= 1
|
|
68
|
+
|
|
69
|
+
if self._length == 0:
|
|
70
|
+
self.tail = None
|
|
71
|
+
|
|
72
|
+
return value
|
|
73
|
+
def reverse(self):
|
|
74
|
+
"""Reverses the list in O(n) time and O(1) space."""
|
|
75
|
+
prev = None
|
|
76
|
+
current = self.head
|
|
77
|
+
while current:
|
|
78
|
+
next_node = current.next
|
|
79
|
+
current.next = prev
|
|
80
|
+
prev = current
|
|
81
|
+
current = next_node
|
|
82
|
+
self.head = prev
|
|
83
|
+
def search(self, value: T) -> bool:
|
|
84
|
+
"""Returns True if value exists in the list, else False."""
|
|
85
|
+
current = self.head
|
|
86
|
+
while current:
|
|
87
|
+
if current.value == value:
|
|
88
|
+
return True
|
|
89
|
+
current = current.next
|
|
90
|
+
return False
|
|
91
|
+
def remove(self, value: T) -> bool:
|
|
92
|
+
"""Removes the first occurrence of value from the list."""
|
|
93
|
+
if not self.head:
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
if self.head.value == value:
|
|
97
|
+
self.head = self.head.next
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
current = self.head
|
|
101
|
+
while current.next:
|
|
102
|
+
if current.next.value == value:
|
|
103
|
+
current.next = current.next.next
|
|
104
|
+
return True
|
|
105
|
+
current = current.next
|
|
106
|
+
return False
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional
|
|
2
|
+
from pure_python_ds.linear.singly_linked_list import SinglyLinkedList
|
|
3
|
+
|
|
4
|
+
T = TypeVar('T')
|
|
5
|
+
|
|
6
|
+
class Stack(Generic[T]):
|
|
7
|
+
"""A strictly typed, memory-optimized Stack (LIFO)."""
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self._container = SinglyLinkedList[T]()
|
|
11
|
+
|
|
12
|
+
def __len__(self) -> int:
|
|
13
|
+
return len(self._container)
|
|
14
|
+
|
|
15
|
+
def push(self, value: T) -> None:
|
|
16
|
+
"""Pushes an item onto the top of the stack in O(1) time."""
|
|
17
|
+
self._container.prepend(value)
|
|
18
|
+
|
|
19
|
+
def pop(self) -> Optional[T]:
|
|
20
|
+
"""Removes and returns the top item in O(1) time."""
|
|
21
|
+
return self._container.remove_head()
|
|
22
|
+
|
|
23
|
+
def __str__(self) -> str:
|
|
24
|
+
return f"Stack Top -> {self._container}"
|
|
25
|
+
def peek(self) -> Optional[T]:
|
|
26
|
+
if not self._container.head:
|
|
27
|
+
return None
|
|
28
|
+
return self._container.head.value
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from typing import Generic, TypeVar, Optional, Dict
|
|
2
|
+
|
|
3
|
+
T = TypeVar('T')
|
|
4
|
+
|
|
5
|
+
class ListNode(Generic[T]):
|
|
6
|
+
__slots__ = ['value', 'next', 'prev']
|
|
7
|
+
def __init__(self, value: T):
|
|
8
|
+
self.value: T = value
|
|
9
|
+
self.next: Optional['ListNode[T]'] = None
|
|
10
|
+
self.prev: Optional['ListNode[T]'] = None
|
|
11
|
+
|
|
12
|
+
class TreeNode(Generic[T]):
|
|
13
|
+
__slots__ = ['value', 'left', 'right', 'height']
|
|
14
|
+
def __init__(self, value: T):
|
|
15
|
+
self.value: T = value
|
|
16
|
+
self.left: Optional['TreeNode[T]'] = None
|
|
17
|
+
self.right: Optional['TreeNode[T]'] = None
|
|
18
|
+
self.height: int = 1
|
|
19
|
+
|
|
20
|
+
class TrieNode:
|
|
21
|
+
# FIXED: Added is_end_of_word to slots and init
|
|
22
|
+
__slots__ = ['children', 'is_end_of_word']
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.children: Dict[str, 'TrieNode'] = {}
|
|
25
|
+
self.is_end_of_word: bool = False
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
class Color(Enum):
|
|
5
|
+
RED = 0
|
|
6
|
+
BLACK = 1
|
|
7
|
+
|
|
8
|
+
class RBNode:
|
|
9
|
+
__slots__ = ['value', 'left', 'right', 'parent', 'color']
|
|
10
|
+
|
|
11
|
+
def __init__(self, value: Any):
|
|
12
|
+
self.value = value
|
|
13
|
+
self.left: Optional['RBNode'] = None
|
|
14
|
+
self.right: Optional['RBNode'] = None
|
|
15
|
+
self.parent: Optional['RBNode'] = None
|
|
16
|
+
self.color = Color.RED
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from .binary_tree import BinaryTree # Add this line
|
|
2
|
+
from .binary_search_tree import BinarySearchTree
|
|
3
|
+
from .avl_tree import AVLTree
|
|
4
|
+
from .trie import Trie
|
|
5
|
+
from .segment_tree import SegmentTree
|
|
6
|
+
from .red_black_tree import RedBlackTree
|
|
7
|
+
from .b_tree import BTree
|