dsa-study 0.2.0__tar.gz → 0.4.4__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 (46) hide show
  1. dsa_study-0.4.4/.github/workflows/ci.yml +38 -0
  2. dsa_study-0.4.4/.github/workflows/publish.yml +29 -0
  3. {dsa_study-0.2.0 → dsa_study-0.4.4}/.gitignore +3 -0
  4. dsa_study-0.4.4/.pre-commit-config.yaml +30 -0
  5. dsa_study-0.2.0/README.md → dsa_study-0.4.4/PKG-INFO +28 -2
  6. dsa_study-0.2.0/PKG-INFO → dsa_study-0.4.4/README.md +13 -13
  7. dsa_study-0.4.4/justfile +21 -0
  8. {dsa_study-0.2.0 → dsa_study-0.4.4}/pyproject.toml +27 -2
  9. dsa_study-0.4.4/src/dsa/data_structures/doubly_linked_list.py +320 -0
  10. {dsa_study-0.2.0 → dsa_study-0.4.4}/src/dsa/data_structures/linked_list.py +7 -3
  11. dsa_study-0.4.4/tests/data_structures/test_doubly_linked_list.py +270 -0
  12. {dsa_study-0.2.0 → dsa_study-0.4.4}/tests/data_structures/test_linked_list.py +29 -29
  13. dsa_study-0.4.4/uv.lock +486 -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/justfile +0 -6
  37. dsa_study-0.2.0/neetcode/arrays_&_hasing/1. contains_duplicate.py +0 -60
  38. dsa_study-0.2.0/neetcode/arrays_&_hasing/2. valid_anagram.py +0 -108
  39. dsa_study-0.2.0/neetcode/arrays_&_hasing/3. two_sum.py +0 -67
  40. dsa_study-0.2.0/neetcode/arrays_&_hasing/4. group_anagrams.py +0 -70
  41. dsa_study-0.2.0/uv.lock +0 -79
  42. {dsa_study-0.2.0 → dsa_study-0.4.4}/.python-version +0 -0
  43. {dsa_study-0.2.0 → dsa_study-0.4.4}/LICENSE +0 -0
  44. {dsa_study-0.2.0 → dsa_study-0.4.4}/src/dsa/__init__.py +0 -0
  45. {dsa_study-0.2.0 → dsa_study-0.4.4}/src/dsa/algorithms/__init__.py +0 -0
  46. {dsa_study-0.2.0 → dsa_study-0.4.4}/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 3.13
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,29 @@
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: Set up Python
23
+ run: uv python install 3.13
24
+
25
+ - name: Build package
26
+ run: uv build
27
+
28
+ - name: Publish to PyPI
29
+ 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,4 +1,24 @@
1
- # DSA Study
1
+ Metadata-Version: 2.4
2
+ Name: dsa-study
3
+ Version: 0.4.4
4
+ Summary: A project to implement various data structures and algorithms in Python.
5
+ Project-URL: Repository, https://github.com/pablohernandezdo/dsa-study-library
6
+ Author: Pablo Hernandez
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: <3.14,>=3.13
14
+ Description-Content-Type: text/markdown
15
+
16
+ # DSA Learning Library
17
+
18
+ [![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)
19
+ [![PyPI](https://img.shields.io/pypi/v/dsa-study)](https://pypi.org/project/dsa-study/)
20
+ [![Python](https://img.shields.io/pypi/pyversions/dsa-study)](https://pypi.org/project/dsa-study/)
21
+ [![License](https://img.shields.io/github/license/pablohernandezdo/dsa-study-library)](https://github.com/pablohernandezdo/dsa-study-library/blob/main/LICENSE)
2
22
 
3
23
  Educational implementations of common data structures and algorithms written in clean, typed, idiomatic Python.
4
24
 
@@ -104,7 +124,7 @@ src/
104
124
  ### Linear Structures
105
125
 
106
126
  - [X] Linked List
107
- - [ ] Doubly Linked List
127
+ - [X] Doubly Linked List
108
128
  - [ ] Stack
109
129
  - [ ] Queue
110
130
  - [ ] Priority Queue
@@ -158,6 +178,12 @@ Every implementation should include tests covering:
158
178
 
159
179
  ## Usage
160
180
 
181
+ Installation:
182
+
183
+ ```
184
+ pip install dsa-study
185
+ ```
186
+
161
187
  Installation from source:
162
188
 
163
189
  ```bash
@@ -1,15 +1,9 @@
1
- Metadata-Version: 2.4
2
- Name: dsa-study
3
- Version: 0.2.0
4
- Summary: A project to implement various data structures and algorithms in Python.
5
- Project-URL: Repository, https://github.com/pablohernandezdo/dsa-study-library
6
- Author: Pablo Hernandez
7
- License-Expression: MIT
8
- License-File: LICENSE
9
- Requires-Python: >=3.13
10
- Description-Content-Type: text/markdown
11
-
12
- # DSA Study
1
+ # DSA Learning Library
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
+ [![PyPI](https://img.shields.io/pypi/v/dsa-study)](https://pypi.org/project/dsa-study/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/dsa-study)](https://pypi.org/project/dsa-study/)
6
+ [![License](https://img.shields.io/github/license/pablohernandezdo/dsa-study-library)](https://github.com/pablohernandezdo/dsa-study-library/blob/main/LICENSE)
13
7
 
14
8
  Educational implementations of common data structures and algorithms written in clean, typed, idiomatic Python.
15
9
 
@@ -115,7 +109,7 @@ src/
115
109
  ### Linear Structures
116
110
 
117
111
  - [X] Linked List
118
- - [ ] Doubly Linked List
112
+ - [X] Doubly Linked List
119
113
  - [ ] Stack
120
114
  - [ ] Queue
121
115
  - [ ] Priority Queue
@@ -169,6 +163,12 @@ Every implementation should include tests covering:
169
163
 
170
164
  ## Usage
171
165
 
166
+ Installation:
167
+
168
+ ```
169
+ pip install dsa-study
170
+ ```
171
+
172
172
  Installation from source:
173
173
 
174
174
  ```bash
@@ -0,0 +1,21 @@
1
+ ruff:
2
+ uv run ruff check src/ tests/
3
+
4
+ mypy:
5
+ uv run mypy src/ tests/
6
+
7
+ test:
8
+ uv run pytest
9
+
10
+ cov:
11
+ uv run pytest --cov=dsa --cov-report=term-missing
12
+
13
+ cov-html:
14
+ uv run pytest --cov=dsa --cov-report=html
15
+
16
+ pre-commit:
17
+ uv run pre-commit run --all-files
18
+
19
+ publish:
20
+ uv build
21
+ uv publish --token $(python3 -c "import configparser, pathlib; c = configparser.ConfigParser(); c.read(pathlib.Path.home() / '.pypirc'); print(c['pypi']['password'])")
@@ -1,11 +1,18 @@
1
1
  [project]
2
2
  name = "dsa-study"
3
- version = "0.2.0"
3
+ version = "0.4.4"
4
4
  description = "A project to implement various data structures and algorithms in Python."
5
5
  readme = "README.md"
6
- requires-python = ">=3.13"
6
+ requires-python = ">=3.13,<3.14"
7
7
  license = "MIT"
8
8
 
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3.13",
12
+ "Typing :: Typed",
13
+ "License :: OSI Approved :: MIT License",
14
+ ]
15
+
9
16
  authors = [
10
17
  { name = "Pablo Hernandez" }
11
18
  ]
@@ -13,7 +20,11 @@ dependencies = []
13
20
 
14
21
  [dependency-groups]
15
22
  dev = [
23
+ "mypy>=2.1.0",
24
+ "pre-commit>=4.6.0",
16
25
  "pytest>=9.0.3",
26
+ "pytest-cov>=7.1.0",
27
+ "ruff>=0.15.17",
17
28
  ]
18
29
 
19
30
  [build-system]
@@ -23,5 +34,19 @@ build-backend = "hatchling.build"
23
34
  [tool.hatch.build.targets.wheel]
24
35
  packages = ["src/dsa"]
25
36
 
37
+ [tool.mypy]
38
+ python_version = "3.13"
39
+ strict = true
40
+
41
+ [tool.ruff]
42
+ line-length = 88
43
+
44
+ [tool.ruff.lint]
45
+ select = [
46
+ "E",
47
+ "F",
48
+ "I",
49
+ ]
50
+
26
51
  [project.urls]
27
52
  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: