dsa-study 0.2.0__tar.gz → 0.4.2__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.
Files changed (45) hide show
  1. dsa_study-0.4.2/.github/workflows/ci.yml +38 -0
  2. dsa_study-0.4.2/.github/workflows/publish.yml +26 -0
  3. {dsa_study-0.2.0 → dsa_study-0.4.2}/.gitignore +3 -0
  4. dsa_study-0.4.2/.pre-commit-config.yaml +30 -0
  5. {dsa_study-0.2.0 → dsa_study-0.4.2}/PKG-INFO +5 -2
  6. {dsa_study-0.2.0 → dsa_study-0.4.2}/README.md +4 -1
  7. {dsa_study-0.2.0 → dsa_study-0.4.2}/justfile +9 -0
  8. {dsa_study-0.2.0 → dsa_study-0.4.2}/pyproject.toml +18 -1
  9. dsa_study-0.4.2/src/dsa/data_structures/doubly_linked_list.py +320 -0
  10. {dsa_study-0.2.0 → dsa_study-0.4.2}/src/dsa/data_structures/linked_list.py +7 -3
  11. dsa_study-0.4.2/tests/data_structures/test_doubly_linked_list.py +270 -0
  12. {dsa_study-0.2.0 → dsa_study-0.4.2}/tests/data_structures/test_linked_list.py +29 -29
  13. dsa_study-0.4.2/uv.lock +401 -0
  14. dsa_study-0.2.0/algorithms/al01_binary_search.py +0 -60
  15. dsa_study-0.2.0/algorithms/al02_selection_sort.py +0 -60
  16. dsa_study-0.2.0/algorithms/al03_quicksort.py +0 -138
  17. dsa_study-0.2.0/algorithms/al04_bfs.py +0 -55
  18. dsa_study-0.2.0/algorithms/al05_dfs.py +0 -52
  19. dsa_study-0.2.0/algorithms/al06_dijkstra.py +0 -193
  20. dsa_study-0.2.0/algorithms/al07_tree.py +0 -545
  21. dsa_study-0.2.0/algorithms/al08_bst.py +0 -239
  22. dsa_study-0.2.0/algorithms/al09_heap.py +0 -148
  23. dsa_study-0.2.0/algorithms/al10_dijkstra_heap.py +0 -175
  24. dsa_study-0.2.0/algorithms/al11_topological_sort.py +0 -143
  25. dsa_study-0.2.0/algorithms/al12_kruskal.py +0 -123
  26. dsa_study-0.2.0/algorithms/al13_prim.py +0 -73
  27. dsa_study-0.2.0/algorithms/bubblesort.py +0 -30
  28. dsa_study-0.2.0/data_structures/ds01_array.py +0 -0
  29. dsa_study-0.2.0/data_structures/ds02_linked_list.py +0 -0
  30. dsa_study-0.2.0/data_structures/ds03_union_find.py +0 -91
  31. dsa_study-0.2.0/dsa_legacy/binary_search.py +0 -35
  32. dsa_study-0.2.0/dsa_legacy/bs.py +0 -37
  33. dsa_study-0.2.0/dsa_legacy/linked_list.py +0 -44
  34. dsa_study-0.2.0/dsa_legacy/recursion.py +0 -42
  35. dsa_study-0.2.0/dsa_legacy/selection_sort.py +0 -32
  36. dsa_study-0.2.0/neetcode/arrays_&_hasing/1. contains_duplicate.py +0 -60
  37. dsa_study-0.2.0/neetcode/arrays_&_hasing/2. valid_anagram.py +0 -108
  38. dsa_study-0.2.0/neetcode/arrays_&_hasing/3. two_sum.py +0 -67
  39. dsa_study-0.2.0/neetcode/arrays_&_hasing/4. group_anagrams.py +0 -70
  40. dsa_study-0.2.0/uv.lock +0 -79
  41. {dsa_study-0.2.0 → dsa_study-0.4.2}/.python-version +0 -0
  42. {dsa_study-0.2.0 → dsa_study-0.4.2}/LICENSE +0 -0
  43. {dsa_study-0.2.0 → dsa_study-0.4.2}/src/dsa/__init__.py +0 -0
  44. {dsa_study-0.2.0 → dsa_study-0.4.2}/src/dsa/algorithms/__init__.py +0 -0
  45. {dsa_study-0.2.0 → dsa_study-0.4.2}/src/dsa/data_structures/__init__.py +0 -0
@@ -0,0 +1,38 @@
1
+ # .github/workflows/ci.yml
2
+
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ pull_request:
10
+
11
+ jobs:
12
+ test:
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v5
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v6
21
+
22
+ - name: Set up Python
23
+ run: uv python install
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --all-extras --dev
27
+
28
+ - name: Ruff lint
29
+ run: uv run ruff check .
30
+
31
+ - name: Ruff format check
32
+ run: uv run ruff format --check .
33
+
34
+ - name: MyPy
35
+ run: uv run mypy src
36
+
37
+ - name: Pytest
38
+ run: uv run pytest
@@ -0,0 +1,26 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+
14
+ environment:
15
+ name: pypi
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: astral-sh/setup-uv@v6
21
+
22
+ - name: Build package
23
+ run: uv build
24
+
25
+ - name: Publish to PyPI
26
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -16,3 +16,6 @@ htmlcov/
16
16
 
17
17
  # Type hints
18
18
  .mypy_cache/
19
+
20
+ # Vscode
21
+ .vscode
@@ -0,0 +1,30 @@
1
+ repos:
2
+ - repo: local
3
+ hooks:
4
+ - id: ruff-format
5
+ name: Ruff Format
6
+ entry: uv run ruff format
7
+ language: system
8
+ pass_filenames: false
9
+ files: ^(src|tests)/
10
+
11
+ - id: ruff-check
12
+ name: Ruff Check
13
+ entry: uv run ruff check
14
+ language: system
15
+ pass_filenames: false
16
+ files: ^(src|tests)/
17
+
18
+ - id: mypy
19
+ name: MyPy
20
+ entry: uv run mypy src
21
+ language: system
22
+ pass_filenames: false
23
+ files: ^(src|tests)/
24
+
25
+ - id: pytest
26
+ name: Pytest
27
+ entry: uv run pytest
28
+ language: system
29
+ pass_filenames: false
30
+ files: ^(src|tests)/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsa-study
3
- Version: 0.2.0
3
+ Version: 0.4.2
4
4
  Summary: A project to implement various data structures and algorithms in Python.
5
5
  Project-URL: Repository, https://github.com/pablohernandezdo/dsa-study-library
6
6
  Author: Pablo Hernandez
@@ -11,6 +11,9 @@ Description-Content-Type: text/markdown
11
11
 
12
12
  # DSA Study
13
13
 
14
+ [![CI](https://github.com/pablohernandezdo/dsa-study-library/actions/workflows/ci.yml/badge.svg)](https://github.com/pablohernandezdo/dsa-study-library/actions/workflows/ci.yml)
15
+
16
+
14
17
  Educational implementations of common data structures and algorithms written in clean, typed, idiomatic Python.
15
18
 
16
19
  > ⚠️ This project is currently under active development and is primarily intended for learning and educational purposes.
@@ -115,7 +118,7 @@ src/
115
118
  ### Linear Structures
116
119
 
117
120
  - [X] Linked List
118
- - [ ] Doubly Linked List
121
+ - [X] Doubly Linked List
119
122
  - [ ] Stack
120
123
  - [ ] Queue
121
124
  - [ ] Priority Queue
@@ -1,5 +1,8 @@
1
1
  # DSA Study
2
2
 
3
+ [![CI](https://github.com/pablohernandezdo/dsa-study-library/actions/workflows/ci.yml/badge.svg)](https://github.com/pablohernandezdo/dsa-study-library/actions/workflows/ci.yml)
4
+
5
+
3
6
  Educational implementations of common data structures and algorithms written in clean, typed, idiomatic Python.
4
7
 
5
8
  > ⚠️ This project is currently under active development and is primarily intended for learning and educational purposes.
@@ -104,7 +107,7 @@ src/
104
107
  ### Linear Structures
105
108
 
106
109
  - [X] Linked List
107
- - [ ] Doubly Linked List
110
+ - [X] Doubly Linked List
108
111
  - [ ] Stack
109
112
  - [ ] Queue
110
113
  - [ ] Priority Queue
@@ -1,6 +1,15 @@
1
+ ruff:
2
+ uv run ruff check src/ tests/
3
+
4
+ mypy:
5
+ uv run mypy src/ tests/
6
+
1
7
  test:
2
8
  uv run pytest
3
9
 
10
+ pre-commit:
11
+ uv run pre-commit run --all-files
12
+
4
13
  publish:
5
14
  uv build
6
15
  uv publish --token $(python3 -c "import configparser, pathlib; c = configparser.ConfigParser(); c.read(pathlib.Path.home() / '.pypirc'); print(c['pypi']['password'])")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "dsa-study"
3
- version = "0.2.0"
3
+ version = "0.4.2"
4
4
  description = "A project to implement various data structures and algorithms in Python."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -13,7 +13,10 @@ dependencies = []
13
13
 
14
14
  [dependency-groups]
15
15
  dev = [
16
+ "mypy>=2.1.0",
17
+ "pre-commit>=4.6.0",
16
18
  "pytest>=9.0.3",
19
+ "ruff>=0.15.17",
17
20
  ]
18
21
 
19
22
  [build-system]
@@ -23,5 +26,19 @@ build-backend = "hatchling.build"
23
26
  [tool.hatch.build.targets.wheel]
24
27
  packages = ["src/dsa"]
25
28
 
29
+ [tool.mypy]
30
+ python_version = "3.13"
31
+ strict = true
32
+
33
+ [tool.ruff]
34
+ line-length = 88
35
+
36
+ [tool.ruff.lint]
37
+ select = [
38
+ "E",
39
+ "F",
40
+ "I",
41
+ ]
42
+
26
43
  [project.urls]
27
44
  Repository = "https://github.com/pablohernandezdo/dsa-study-library"
@@ -0,0 +1,320 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterator
4
+ from dataclasses import dataclass
5
+
6
+
7
+ @dataclass
8
+ class _Node:
9
+ value: int
10
+ prev: _Node | None = None
11
+ next: _Node | None = None
12
+
13
+
14
+ class DoublyLinkedList:
15
+ """
16
+ A doubly linked list of integers.
17
+
18
+ Elements are stored in nodes connected through references to the previous
19
+ and next nodes. This implementation stores both the head and tail pointers.
20
+
21
+ Operations:
22
+ - insert_front: O(1)
23
+ - insert_back: O(1)
24
+ - find: O(n)
25
+ - delete: O(n)
26
+ - clear: O(1)
27
+ - pop_front: O(1)
28
+ - pop_back: O(1)
29
+ """
30
+
31
+ def __init__(self, values: list[int] | None = None) -> None:
32
+ """
33
+ Build a doubly linked list from a list of integers.
34
+
35
+ If no values are passed an empty list is created.
36
+
37
+ Args:
38
+ values: Initial values to populate the list.
39
+ """
40
+
41
+ if not values:
42
+ self.head = None
43
+ self.tail = None
44
+ return
45
+
46
+ self.head = _Node(values[0])
47
+ prev: _Node = self.head
48
+
49
+ for value in values[1:]:
50
+ prev.next = _Node(value, prev)
51
+ prev = prev.next
52
+
53
+ self.tail = prev
54
+
55
+ def __len__(self) -> int:
56
+ """
57
+ Return the number of elements in the list.
58
+
59
+ Time Complexity:
60
+ O(n)
61
+ """
62
+
63
+ length = 0
64
+ node = self.head
65
+
66
+ while node:
67
+ length += 1
68
+ node = node.next
69
+
70
+ return length
71
+
72
+ def __iter__(self) -> Iterator[int]:
73
+ """
74
+ Iterate over the values of the list from head to tail.
75
+
76
+ Time Complexity:
77
+ O(n)
78
+ """
79
+ node = self.head
80
+
81
+ while node:
82
+ yield node.value
83
+ node = node.next
84
+
85
+ def __reversed__(self) -> Iterator[int]:
86
+ """
87
+ Iterate over the values of the list from tail to head.
88
+
89
+ Time Complexity:
90
+ O(n)
91
+ """
92
+
93
+ node = self.tail
94
+
95
+ while node:
96
+ yield node.value
97
+ node = node.prev
98
+
99
+ def __repr__(self) -> str:
100
+ return f"DoublyLinkedList({list(self)})"
101
+
102
+ def __str__(self) -> str:
103
+
104
+ if self.head is None:
105
+ return ""
106
+
107
+ representation: str = ""
108
+
109
+ for idx, value in enumerate(self):
110
+ if idx == 0:
111
+ representation += f"{value}"
112
+ else:
113
+ representation += f" <-> {value}"
114
+
115
+ return representation
116
+
117
+ def insert_front(self, value: int) -> None:
118
+ """
119
+ Insert a value at the beginning of the list.
120
+
121
+ If the list is empty, the inserted node is assigned to both the head and tail.
122
+
123
+ Args:
124
+ value: The value to be inserted.
125
+
126
+ Time Complexity:
127
+ O(1)
128
+ """
129
+
130
+ if self.head is None:
131
+ node = _Node(value)
132
+ self.head = node
133
+ self.tail = node
134
+ return
135
+
136
+ new_node = _Node(value, None, self.head)
137
+ self.head.prev = new_node
138
+ self.head = new_node
139
+
140
+ def insert_back(self, value: int) -> None:
141
+ """
142
+ Insert a value at the end of the list.
143
+
144
+ If the list is empty, the inserted node is assigned to both the head and tail.
145
+
146
+ Args:
147
+ value: The value to be inserted.
148
+
149
+ Time Complexity:
150
+ O(1)
151
+ """
152
+
153
+ if self.tail is None:
154
+ node = _Node(value)
155
+ self.head = node
156
+ self.tail = node
157
+ return
158
+
159
+ new_node = _Node(value, self.tail, None)
160
+ self.tail.next = new_node
161
+ self.tail = new_node
162
+
163
+ def find(self, value: int) -> int:
164
+ """
165
+ Return the index of the first occurrence of a value in the list, traversed from
166
+ head to tail.
167
+
168
+ Args:
169
+ value: The value to search for.
170
+
171
+ Returns:
172
+ The zero-based index of the value's first occurrence.
173
+
174
+ Raises:
175
+ ValueError: If the value is not present in the list.
176
+
177
+ Time Complexity:
178
+ O(n)
179
+ """
180
+
181
+ idx = 0
182
+ node = self.head
183
+
184
+ while node:
185
+ if node.value == value:
186
+ return idx
187
+
188
+ idx += 1
189
+ node = node.next
190
+
191
+ raise ValueError(f"{value} not found in list")
192
+
193
+ def delete(self, value: int) -> None:
194
+ """
195
+ Delete the first occurrence of a value in the list.
196
+
197
+ If the value is not present the list is left unchanged.
198
+
199
+ If the value appears multiple times, only the first occurrence is removed.
200
+
201
+ Args:
202
+ value: The value to be removed.
203
+
204
+ Time Complexity:
205
+ O(n)
206
+ """
207
+
208
+ if self.head is None:
209
+ return
210
+
211
+ node: _Node | None = self.head
212
+
213
+ while node:
214
+ if node.value == value:
215
+ # deleting head
216
+ if node is self.head:
217
+ # head = tail
218
+ if self.head is self.tail:
219
+ self.head = None
220
+ self.tail = None
221
+
222
+ # other
223
+ else:
224
+ self.head = self.head.next
225
+
226
+ assert self.head is not None
227
+ self.head.prev = None
228
+
229
+ # deleting tail
230
+ elif node is self.tail:
231
+ assert self.tail is not None
232
+ assert self.tail.prev is not None
233
+
234
+ self.tail.prev.next = None
235
+ self.tail = self.tail.prev
236
+
237
+ # deleting in the middle
238
+ else:
239
+ assert node.prev is not None
240
+ assert node.next is not None
241
+
242
+ node.prev.next = node.next
243
+ node.next.prev = node.prev
244
+
245
+ return
246
+
247
+ node = node.next
248
+
249
+ def clear(self) -> None:
250
+ """
251
+ Remove all nodes from the list.
252
+
253
+ Time Complexity:
254
+ O(1)
255
+ """
256
+
257
+ self.head = None
258
+ self.tail = None
259
+
260
+ def pop_front(self) -> int:
261
+ """
262
+ Remove the element at the head of the list.
263
+
264
+ Returns:
265
+ The value of the element at the head of the list.
266
+
267
+ Raises:
268
+ IndexError: If the list is empty.
269
+
270
+ Time Complexity:
271
+ O(1)
272
+ """
273
+
274
+ if self.head is None:
275
+ raise IndexError("pop from empty list")
276
+
277
+ if self.head is self.tail:
278
+ value = self.head.value
279
+ self.head = None
280
+ self.tail = None
281
+ return value
282
+
283
+ first_value: int = self.head.value
284
+
285
+ assert self.head.next is not None
286
+ self.head.next.prev = None
287
+ self.head = self.head.next
288
+
289
+ return first_value
290
+
291
+ def pop_back(self) -> int:
292
+ """
293
+ Remove the element at the tail of the list.
294
+
295
+ Returns:
296
+ The value of the element at the tail of the list.
297
+
298
+ Raises:
299
+ IndexError: If the list is empty.
300
+
301
+ Time Complexity:
302
+ O(1)
303
+ """
304
+
305
+ if self.tail is None:
306
+ raise IndexError("pop from empty list")
307
+
308
+ if self.head is self.tail:
309
+ value = self.tail.value
310
+ self.head = None
311
+ self.tail = None
312
+ return value
313
+
314
+ last_value: int = self.tail.value
315
+
316
+ assert self.tail.prev is not None
317
+ self.tail.prev.next = None
318
+ self.tail = self.tail.prev
319
+
320
+ return last_value
@@ -93,6 +93,8 @@ class LinkedList:
93
93
  """
94
94
  Insert a value at the beginning of the list.
95
95
 
96
+ If the list is empty, the inserted node is assigned to the head.
97
+
96
98
  Args:
97
99
  value: The value to be inserted.
98
100
 
@@ -109,6 +111,8 @@ class LinkedList:
109
111
  """
110
112
  Insert a value at the end of the list.
111
113
 
114
+ If the list is empty, the inserted node is assigned to the head.
115
+
112
116
  Args:
113
117
  value: The value to be inserted.
114
118
 
@@ -133,7 +137,7 @@ class LinkedList:
133
137
  value: Value to be found.
134
138
 
135
139
  Returns:
136
- The zero-based index of the value.
140
+ The zero-based index of the value's first occurrence.
137
141
 
138
142
  Raises:
139
143
  ValueError: If the value is not present in the list.
@@ -171,8 +175,8 @@ class LinkedList:
171
175
  if self.head is None:
172
176
  return
173
177
 
174
- prev = None
175
- current = self.head
178
+ prev: _Node | None = None
179
+ current: _Node | None = self.head
176
180
 
177
181
  while current:
178
182
  if current.value == value: