pure-python-ds 1.0.0__tar.gz
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-1.0.0/LICENSE +21 -0
- pure_python_ds-1.0.0/PKG-INFO +69 -0
- pure_python_ds-1.0.0/README.md +52 -0
- pure_python_ds-1.0.0/pure_python_ds/__init__.py +0 -0
- pure_python_ds-1.0.0/pure_python_ds/algorithms/__init__.py +3 -0
- pure_python_ds-1.0.0/pure_python_ds/algorithms/dp.py +16 -0
- pure_python_ds-1.0.0/pure_python_ds/algorithms/searching.py +22 -0
- pure_python_ds-1.0.0/pure_python_ds/algorithms/sorting.py +36 -0
- pure_python_ds-1.0.0/pure_python_ds/graphs/__init__.py +2 -0
- pure_python_ds-1.0.0/pure_python_ds/graphs/dsu.py +22 -0
- pure_python_ds-1.0.0/pure_python_ds/graphs/graph.py +106 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/__init__.py +6 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/doubly_linked_list.py +91 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/hash_table.py +33 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/heap.py +102 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/queue.py +24 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/singly_linked_list.py +106 -0
- pure_python_ds-1.0.0/pure_python_ds/linear/stack.py +28 -0
- pure_python_ds-1.0.0/pure_python_ds/nodes/__init__.py +25 -0
- pure_python_ds-1.0.0/pure_python_ds/nodes/b_tree_node.py +9 -0
- pure_python_ds-1.0.0/pure_python_ds/nodes/rb_node.py +16 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/__init__.py +7 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/avl_tree.py +142 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/b_tree.py +55 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/binary_search_tree.py +88 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/binary_tree.py +69 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/red_black_tree.py +111 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/segment_tree.py +35 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/trie.py +50 -0
- pure_python_ds-1.0.0/pure_python_ds/trees/utils.py +26 -0
- pure_python_ds-1.0.0/pure_python_ds.egg-info/PKG-INFO +69 -0
- pure_python_ds-1.0.0/pure_python_ds.egg-info/SOURCES.txt +37 -0
- pure_python_ds-1.0.0/pure_python_ds.egg-info/dependency_links.txt +1 -0
- pure_python_ds-1.0.0/pure_python_ds.egg-info/top_level.txt +1 -0
- pure_python_ds-1.0.0/pyproject.toml +28 -0
- pure_python_ds-1.0.0/setup.cfg +4 -0
- pure_python_ds-1.0.0/tests/test_avl_guaranteed.py +21 -0
- pure_python_ds-1.0.0/tests/test_core.py +458 -0
- pure_python_ds-1.0.0/tests/test_coverage_completion.py +171 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Parjad Minooei
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pure-python-ds
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A strictly typed, 100% test-covered data structures and algorithms library.
|
|
5
|
+
Author: Parjad Minooei
|
|
6
|
+
Project-URL: Documentation, https://ParjadM.github.io/pure-python-ds/
|
|
7
|
+
Project-URL: Source, https://github.com/ParjadM/pure-python-ds
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# pure-python-ds 🚀
|
|
19
|
+
|
|
20
|
+
A high-performance, strictly-typed, and memory-optimized Data Structures and Algorithms library built in Pure Python.
|
|
21
|
+
|
|
22
|
+
## 🏗️ Architecture Overview
|
|
23
|
+
|
|
24
|
+
This library was designed with a focus on **Systems Architecture**. By utilizing Python's `__slots__`, every node in this library is memory-optimized to reduce overhead, making it suitable for educational deep-dives and performance-sensitive prototyping.
|
|
25
|
+
|
|
26
|
+
[Image of a software architecture diagram showing layers of data structures and algorithms]
|
|
27
|
+
|
|
28
|
+
## 🛠️ Key Features
|
|
29
|
+
|
|
30
|
+
### 1. Linear Data Structures
|
|
31
|
+
* **Linked Lists:** Singly and Doubly Linked Lists with $O(1)$ head/tail operations.
|
|
32
|
+
* **Stacks & Queues:** Built on top of optimized linked nodes for strict LIFO/FIFO behavior.
|
|
33
|
+
|
|
34
|
+
### 2. Hierarchical & Network Structures
|
|
35
|
+
* **Binary Search Trees (BST):** Standard ordered trees with recursive and iterative implementations.
|
|
36
|
+
* **AVL Trees:** Self-balancing trees using rotation logic to guarantee $O(\log n)$ performance.
|
|
37
|
+
* **Graphs:** Adjacency-list based implementation supporting both Directed and Undirected networks.
|
|
38
|
+
|
|
39
|
+
### 3. Advanced Algorithms
|
|
40
|
+
* **Pathfinding:** Dijkstra's Algorithm for finding shortest paths in weighted graphs.
|
|
41
|
+
* **Sorting:** $O(n \log n)$ Merge Sort implementation.
|
|
42
|
+
* **Searching:** $O(\log n)$ Binary Search.
|
|
43
|
+
* **Dynamic Programming:** Memoized Fibonacci and sub-problem optimization.
|
|
44
|
+
|
|
45
|
+
[Image of a perfectly balanced Binary Search Tree demonstrating O(log n) structure]
|
|
46
|
+
|
|
47
|
+
## 🚀 Installation
|
|
48
|
+
|
|
49
|
+
Since the package is in "Editable" mode, you can install it locally to use across your entire system:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone [https://github.com/ParjadM/pure-python-ds.git](https://github.com/ParjadM/pure-python-ds.git)
|
|
53
|
+
cd pure-python-ds
|
|
54
|
+
pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
🚀 Technical Milestone: 83% Unit Test Coverage Reached
|
|
58
|
+
Core Engineering Accomplishments:
|
|
59
|
+
|
|
60
|
+
Robustness Verification: Engineered a comprehensive regression suite achieving 83% total coverage across 15+ complex data structures.
|
|
61
|
+
|
|
62
|
+
Algorithm Integration: Successfully implemented and verified Kruskal’s MST (using custom DSU), Bellman-Ford, and Dijkstra’s algorithms.
|
|
63
|
+
|
|
64
|
+
Recursive Tree Logic: Developed and tested memory-safe recursive deletion with rebalancing for AVL and Binary Search Trees.
|
|
65
|
+
|
|
66
|
+
Performance Optimization: Leveraged __slots__ and Python Generators to ensure O(1) space complexity for tree traversals and minimal memory footprint.
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+

|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# pure-python-ds 🚀
|
|
2
|
+
|
|
3
|
+
A high-performance, strictly-typed, and memory-optimized Data Structures and Algorithms library built in Pure Python.
|
|
4
|
+
|
|
5
|
+
## 🏗️ Architecture Overview
|
|
6
|
+
|
|
7
|
+
This library was designed with a focus on **Systems Architecture**. By utilizing Python's `__slots__`, every node in this library is memory-optimized to reduce overhead, making it suitable for educational deep-dives and performance-sensitive prototyping.
|
|
8
|
+
|
|
9
|
+
[Image of a software architecture diagram showing layers of data structures and algorithms]
|
|
10
|
+
|
|
11
|
+
## 🛠️ Key Features
|
|
12
|
+
|
|
13
|
+
### 1. Linear Data Structures
|
|
14
|
+
* **Linked Lists:** Singly and Doubly Linked Lists with $O(1)$ head/tail operations.
|
|
15
|
+
* **Stacks & Queues:** Built on top of optimized linked nodes for strict LIFO/FIFO behavior.
|
|
16
|
+
|
|
17
|
+
### 2. Hierarchical & Network Structures
|
|
18
|
+
* **Binary Search Trees (BST):** Standard ordered trees with recursive and iterative implementations.
|
|
19
|
+
* **AVL Trees:** Self-balancing trees using rotation logic to guarantee $O(\log n)$ performance.
|
|
20
|
+
* **Graphs:** Adjacency-list based implementation supporting both Directed and Undirected networks.
|
|
21
|
+
|
|
22
|
+
### 3. Advanced Algorithms
|
|
23
|
+
* **Pathfinding:** Dijkstra's Algorithm for finding shortest paths in weighted graphs.
|
|
24
|
+
* **Sorting:** $O(n \log n)$ Merge Sort implementation.
|
|
25
|
+
* **Searching:** $O(\log n)$ Binary Search.
|
|
26
|
+
* **Dynamic Programming:** Memoized Fibonacci and sub-problem optimization.
|
|
27
|
+
|
|
28
|
+
[Image of a perfectly balanced Binary Search Tree demonstrating O(log n) structure]
|
|
29
|
+
|
|
30
|
+
## 🚀 Installation
|
|
31
|
+
|
|
32
|
+
Since the package is in "Editable" mode, you can install it locally to use across your entire system:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone [https://github.com/ParjadM/pure-python-ds.git](https://github.com/ParjadM/pure-python-ds.git)
|
|
36
|
+
cd pure-python-ds
|
|
37
|
+
pip install -e .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
🚀 Technical Milestone: 83% Unit Test Coverage Reached
|
|
41
|
+
Core Engineering Accomplishments:
|
|
42
|
+
|
|
43
|
+
Robustness Verification: Engineered a comprehensive regression suite achieving 83% total coverage across 15+ complex data structures.
|
|
44
|
+
|
|
45
|
+
Algorithm Integration: Successfully implemented and verified Kruskal’s MST (using custom DSU), Bellman-Ford, and Dijkstra’s algorithms.
|
|
46
|
+
|
|
47
|
+
Recursive Tree Logic: Developed and tested memory-safe recursive deletion with rebalancing for AVL and Binary Search Trees.
|
|
48
|
+
|
|
49
|
+
Performance Optimization: Leveraged __slots__ and Python Generators to ensure O(1) space complexity for tree traversals and minimal memory footprint.
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+

|
|
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}"
|