core-data-structures 1.2.0__tar.gz → 1.3.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.
Files changed (24) hide show
  1. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/PKG-INFO +18 -4
  2. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/README.rst +15 -2
  3. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/core_data_structures.egg-info/PKG-INFO +18 -4
  4. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/core_data_structures.egg-info/SOURCES.txt +5 -0
  5. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/core_data_structures.egg-info/requires.txt +2 -1
  6. core_data_structures-1.3.0/data_structures/base.py +37 -0
  7. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/graphs/graphs.py +0 -0
  8. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/graphs/vertex.py +1 -1
  9. core_data_structures-1.3.0/data_structures/lists/base.py +233 -0
  10. core_data_structures-1.3.0/data_structures/lists/double_linked_list.py +231 -0
  11. core_data_structures-1.3.0/data_structures/lists/singly_linked_list.py +180 -0
  12. core_data_structures-1.3.0/data_structures/py.typed +0 -0
  13. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/trees/binary_tree.py +0 -0
  14. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/pyproject.toml +3 -2
  15. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/LICENSE +0 -0
  16. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/core_data_structures.egg-info/dependency_links.txt +0 -0
  17. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/core_data_structures.egg-info/top_level.txt +0 -0
  18. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/__init__.py +0 -0
  19. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/graphs/__init__.py +0 -0
  20. /core_data_structures-1.2.0/data_structures/py.typed → /core_data_structures-1.3.0/data_structures/lists/__init__.py +0 -0
  21. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/trees/__init__.py +0 -0
  22. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/data_structures/trees/simple_tree.py +0 -0
  23. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/setup.cfg +0 -0
  24. {core_data_structures-1.2.0 → core_data_structures-1.3.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-data-structures
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: This project/library contains commons data structures...
5
5
  Author-email: Alejandro Cora González <alek.cora.glez@gmail.com>
6
6
  Maintainer: Alejandro Cora González
@@ -25,20 +25,25 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Requires-Python: >=3.9
26
26
  Description-Content-Type: text/x-rst
27
27
  License-File: LICENSE
28
+ Requires-Dist: core-mixins>=3.2.0
28
29
  Requires-Dist: typing-extensions>=4.8.0
29
30
  Provides-Extra: dev
30
- Requires-Dist: core-dev-tools>=2.0.0; extra == "dev"
31
+ Requires-Dist: core-dev-tools>=2.1.0; extra == "dev"
31
32
  Requires-Dist: core-tests>=2.1.0; extra == "dev"
32
33
  Dynamic: license-file
33
34
 
34
35
  core-data-structures
35
36
  ===============================================================================
36
37
 
37
- A Python library providing common data structures including graphs and trees,
38
- with built-in traversal and path-finding algorithms.
38
+ A Python library providing common data structures including graphs, trees, and
39
+ lists, with built-in traversal, path-finding, and search algorithms.
39
40
 
40
41
  ===============================================================================
41
42
 
43
+ .. image:: https://static.pepy.tech/personalized-badge/core-data-structures?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads
44
+ :target: https://pepy.tech/projects/core-data-structures
45
+ :alt: PyPI Downloads
46
+
42
47
  .. image:: https://img.shields.io/pypi/pyversions/core-data-structures.svg
43
48
  :target: https://pypi.org/project/core-data-structures/
44
49
  :alt: Python Versions
@@ -77,6 +82,15 @@ Features
77
82
  - Recursive and iterative variants
78
83
  - Tree depth calculation
79
84
 
85
+ - **Lists** — singly and doubly linked lists sharing a common ``IList``/``ILinkedList`` interface.
86
+
87
+ - Value-based CRUD: ``append``, ``prepend``, ``insert``, ``get``/``__getitem__``,
88
+ ``index``, ``pop``/``pop_front``/``pop_back``, ``remove`` (single or all occurrences)
89
+ - ``reverse``, ``extend``, ``clear``, ``to_list``/``from_list``
90
+ - ``__iter__``/``__contains__`` support, plus O(1) ``head``/``end`` node access
91
+ - ``DoubleLinkedList`` additionally maintains ``.prev`` links for backward traversal
92
+ and locates positions from whichever end (``head``/``end``) is closer
93
+
80
94
 
81
95
  Quick Start
82
96
  ===============================================================================
@@ -1,11 +1,15 @@
1
1
  core-data-structures
2
2
  ===============================================================================
3
3
 
4
- A Python library providing common data structures including graphs and trees,
5
- with built-in traversal and path-finding algorithms.
4
+ A Python library providing common data structures including graphs, trees, and
5
+ lists, with built-in traversal, path-finding, and search algorithms.
6
6
 
7
7
  ===============================================================================
8
8
 
9
+ .. image:: https://static.pepy.tech/personalized-badge/core-data-structures?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads
10
+ :target: https://pepy.tech/projects/core-data-structures
11
+ :alt: PyPI Downloads
12
+
9
13
  .. image:: https://img.shields.io/pypi/pyversions/core-data-structures.svg
10
14
  :target: https://pypi.org/project/core-data-structures/
11
15
  :alt: Python Versions
@@ -44,6 +48,15 @@ Features
44
48
  - Recursive and iterative variants
45
49
  - Tree depth calculation
46
50
 
51
+ - **Lists** — singly and doubly linked lists sharing a common ``IList``/``ILinkedList`` interface.
52
+
53
+ - Value-based CRUD: ``append``, ``prepend``, ``insert``, ``get``/``__getitem__``,
54
+ ``index``, ``pop``/``pop_front``/``pop_back``, ``remove`` (single or all occurrences)
55
+ - ``reverse``, ``extend``, ``clear``, ``to_list``/``from_list``
56
+ - ``__iter__``/``__contains__`` support, plus O(1) ``head``/``end`` node access
57
+ - ``DoubleLinkedList`` additionally maintains ``.prev`` links for backward traversal
58
+ and locates positions from whichever end (``head``/``end``) is closer
59
+
47
60
 
48
61
  Quick Start
49
62
  ===============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-data-structures
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: This project/library contains commons data structures...
5
5
  Author-email: Alejandro Cora González <alek.cora.glez@gmail.com>
6
6
  Maintainer: Alejandro Cora González
@@ -25,20 +25,25 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
25
25
  Requires-Python: >=3.9
26
26
  Description-Content-Type: text/x-rst
27
27
  License-File: LICENSE
28
+ Requires-Dist: core-mixins>=3.2.0
28
29
  Requires-Dist: typing-extensions>=4.8.0
29
30
  Provides-Extra: dev
30
- Requires-Dist: core-dev-tools>=2.0.0; extra == "dev"
31
+ Requires-Dist: core-dev-tools>=2.1.0; extra == "dev"
31
32
  Requires-Dist: core-tests>=2.1.0; extra == "dev"
32
33
  Dynamic: license-file
33
34
 
34
35
  core-data-structures
35
36
  ===============================================================================
36
37
 
37
- A Python library providing common data structures including graphs and trees,
38
- with built-in traversal and path-finding algorithms.
38
+ A Python library providing common data structures including graphs, trees, and
39
+ lists, with built-in traversal, path-finding, and search algorithms.
39
40
 
40
41
  ===============================================================================
41
42
 
43
+ .. image:: https://static.pepy.tech/personalized-badge/core-data-structures?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads
44
+ :target: https://pepy.tech/projects/core-data-structures
45
+ :alt: PyPI Downloads
46
+
42
47
  .. image:: https://img.shields.io/pypi/pyversions/core-data-structures.svg
43
48
  :target: https://pypi.org/project/core-data-structures/
44
49
  :alt: Python Versions
@@ -77,6 +82,15 @@ Features
77
82
  - Recursive and iterative variants
78
83
  - Tree depth calculation
79
84
 
85
+ - **Lists** — singly and doubly linked lists sharing a common ``IList``/``ILinkedList`` interface.
86
+
87
+ - Value-based CRUD: ``append``, ``prepend``, ``insert``, ``get``/``__getitem__``,
88
+ ``index``, ``pop``/``pop_front``/``pop_back``, ``remove`` (single or all occurrences)
89
+ - ``reverse``, ``extend``, ``clear``, ``to_list``/``from_list``
90
+ - ``__iter__``/``__contains__`` support, plus O(1) ``head``/``end`` node access
91
+ - ``DoubleLinkedList`` additionally maintains ``.prev`` links for backward traversal
92
+ and locates positions from whichever end (``head``/``end``) is closer
93
+
80
94
 
81
95
  Quick Start
82
96
  ===============================================================================
@@ -8,10 +8,15 @@ core_data_structures.egg-info/dependency_links.txt
8
8
  core_data_structures.egg-info/requires.txt
9
9
  core_data_structures.egg-info/top_level.txt
10
10
  data_structures/__init__.py
11
+ data_structures/base.py
11
12
  data_structures/py.typed
12
13
  data_structures/graphs/__init__.py
13
14
  data_structures/graphs/graphs.py
14
15
  data_structures/graphs/vertex.py
16
+ data_structures/lists/__init__.py
17
+ data_structures/lists/base.py
18
+ data_structures/lists/double_linked_list.py
19
+ data_structures/lists/singly_linked_list.py
15
20
  data_structures/trees/__init__.py
16
21
  data_structures/trees/binary_tree.py
17
22
  data_structures/trees/simple_tree.py
@@ -1,5 +1,6 @@
1
+ core-mixins>=3.2.0
1
2
  typing-extensions>=4.8.0
2
3
 
3
4
  [dev]
4
- core-dev-tools>=2.0.0
5
+ core-dev-tools>=2.1.0
5
6
  core-tests>=2.1.0
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Base node used as a building block across data structures."""
4
+
5
+ from typing import Generic
6
+ from typing import Optional
7
+ from typing import TypeVar
8
+
9
+
10
+ __all__ = [
11
+ "Node",
12
+ "T",
13
+ ]
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class Node(Generic[T]): # pylint: disable=too-few-public-methods
19
+ """
20
+ Generic container for a single value, meant to be
21
+ subclassed by concrete node types.
22
+ """
23
+
24
+ def __init__(self, value: Optional[T] = None) -> None:
25
+ self._value = value
26
+
27
+ def __repr__(self):
28
+ return f"[{self._value}]"
29
+
30
+ @property
31
+ def value(self) -> Optional[T]:
32
+ """ The value held by the node. """
33
+ return self._value
34
+
35
+ @value.setter
36
+ def value(self, value: T) -> None:
37
+ self._value = value
@@ -7,7 +7,7 @@ from typing import Dict, List
7
7
 
8
8
  if sys.version_info >= (3, 11):
9
9
  from typing import Self
10
- else:
10
+ else: # pragma: no cover
11
11
  from typing_extensions import Self
12
12
 
13
13
 
@@ -0,0 +1,233 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Abstract interface shared by every list implementation in this package."""
4
+
5
+ from abc import ABC
6
+ from abc import abstractmethod
7
+ from contextlib import suppress
8
+ from typing import Generic, Iterator, Optional, TypeVar, cast
9
+
10
+ from core_mixins.compatibility import Self
11
+
12
+ from data_structures.base import Node as BaseNode
13
+ from data_structures.base import T
14
+
15
+
16
+ class Node(BaseNode[T]):
17
+ """Node with a reference to the next node, shared by every linked list."""
18
+
19
+ def __init__(
20
+ self,
21
+ value: Optional[T] = None,
22
+ next_node: Optional[Self] = None,
23
+ ) -> None:
24
+ super().__init__(value=value)
25
+ self._next = next_node
26
+
27
+ @property
28
+ def next(self) -> Optional[Self]:
29
+ """The next node in the list, if any."""
30
+ return self._next
31
+
32
+ @next.setter
33
+ def next(self, next_node: Optional[Self]) -> None:
34
+ self._next = next_node
35
+
36
+
37
+ NodeT = TypeVar("NodeT", bound=Node)
38
+
39
+
40
+ class IList(Generic[T], ABC):
41
+ """Abstract base class (interface) for all list implementations."""
42
+
43
+ def __init__(self, length: int = 0):
44
+ self._length = length
45
+
46
+ def __len__(self) -> int:
47
+ """Returns the length of the list."""
48
+ return self._length
49
+
50
+ def __getitem__(self, item: int) -> T:
51
+ """Returns the value at the given position."""
52
+ return self.get(item)
53
+
54
+ @abstractmethod
55
+ def __iter__(self) -> Iterator[T]:
56
+ """Iterates over the values in the list, starting from the head."""
57
+
58
+ def __contains__(self, item: T) -> bool:
59
+ """Returns whether the value is present in the list."""
60
+
61
+ with suppress(ValueError):
62
+ self.index(item)
63
+ return True
64
+
65
+ return False
66
+
67
+ @property
68
+ def length(self) -> int:
69
+ """The number of elements in the list."""
70
+ return self._length
71
+
72
+ @property
73
+ def is_empty(self) -> bool:
74
+ """Whether the list is empty."""
75
+ return self._length == 0
76
+
77
+ @classmethod
78
+ def from_list(cls, a_list: list) -> Self:
79
+ """Creates the list from a python list."""
80
+
81
+ new_list = cls()
82
+ for value in a_list:
83
+ new_list.append(value)
84
+
85
+ return new_list
86
+
87
+ def to_list(self) -> list:
88
+ """Materializes the list's values into a python list."""
89
+ return list(self)
90
+
91
+ def index(self, value: T) -> int:
92
+ """Returns the position of the first occurrence of the given value."""
93
+
94
+ for pos, value_ in enumerate(self):
95
+ if value_ == value:
96
+ return pos
97
+
98
+ raise ValueError(f"{value!r} is not in list")
99
+
100
+ @abstractmethod
101
+ def get(self, pos: int) -> T:
102
+ """Returns the value at the given position."""
103
+
104
+ @abstractmethod
105
+ def clear(self) -> None:
106
+ """Removes all nodes from the list."""
107
+
108
+ @abstractmethod
109
+ def prepend(self, value: T) -> None:
110
+ """Adds a value to the front of the list."""
111
+
112
+ @abstractmethod
113
+ def append(self, value: T) -> None:
114
+ """Adds a value to the end of the list."""
115
+
116
+ @abstractmethod
117
+ def insert(self, value: T, pos: int) -> None:
118
+ """Inserts a value at the given position."""
119
+
120
+ def extend(self, a_list: Self) -> None:
121
+ """Appends a copy of each value from another list."""
122
+
123
+ for value in list(a_list):
124
+ self.append(value)
125
+
126
+ def pop_front(self) -> T:
127
+ """Removes and returns the value at the front of the list."""
128
+
129
+ if self.is_empty:
130
+ raise IndexError("Empty list!")
131
+
132
+ return self._pop_front()
133
+
134
+ @abstractmethod
135
+ def _pop_front(self) -> T:
136
+ """Removes and returns the value at the front of a non-empty list."""
137
+
138
+ def pop_back(self) -> T:
139
+ """Removes and returns the value at the end of the list."""
140
+
141
+ if self.is_empty:
142
+ raise IndexError("Empty list!")
143
+
144
+ return self._pop_back()
145
+
146
+ @abstractmethod
147
+ def _pop_back(self) -> T:
148
+ """Removes and returns the value at the end of a non-empty list."""
149
+
150
+ def pop(self, index: int) -> T:
151
+ """Removes and returns the value at the given position."""
152
+
153
+ if index < 0 or index >= self._length:
154
+ raise IndexError("Index error!")
155
+
156
+ if index == 0:
157
+ return self.pop_front()
158
+
159
+ if index == self._length - 1:
160
+ return self.pop_back()
161
+
162
+ return self._pop_middle(index)
163
+
164
+ @abstractmethod
165
+ def _pop_middle(self, index: int) -> T:
166
+ """Removes and returns the value at an interior position (not the first or last)."""
167
+
168
+ @abstractmethod
169
+ def remove(self, value: T, all_values: bool = False) -> None:
170
+ """
171
+ Removes the first occurrence of the value, or all occurrences
172
+ if `all_values` is set.
173
+ """
174
+
175
+ @abstractmethod
176
+ def reverse(self) -> None:
177
+ """Reverses the list in place."""
178
+
179
+
180
+ class ILinkedList(IList[T], Generic[T, NodeT], ABC):
181
+ """Interface for linked list implementations, parametrized by their node type."""
182
+
183
+ def __init__(self, value: Optional[T] = None) -> None:
184
+ self._head: Optional[NodeT] = (
185
+ None if value is None else self._create_node(value)
186
+ )
187
+
188
+ super().__init__(0 if value is None else 1)
189
+ self._end = self._head
190
+
191
+ def __iter__(self) -> Iterator[T]:
192
+ """Iterates over the values in the list, starting from the head."""
193
+
194
+ pointer = self._head
195
+ while pointer:
196
+ yield cast(T, pointer.value)
197
+ pointer = pointer.next
198
+
199
+ @classmethod
200
+ @abstractmethod
201
+ def _create_node(cls, value: T) -> NodeT:
202
+ """Creates a new, unlinked node of the concrete node type used by this list."""
203
+
204
+ @property
205
+ def head(self) -> Optional[NodeT]:
206
+ """The first node in the list."""
207
+ return self._head
208
+
209
+ @property
210
+ def end(self) -> Optional[NodeT]:
211
+ """The end node in the list."""
212
+ return self._end
213
+
214
+ def get(self, pos: int) -> T:
215
+ """Returns the value at the given position."""
216
+ return cast(T, self._node_at(pos).value)
217
+
218
+ def _node_at(self, pos: int) -> NodeT:
219
+ """Returns the actual node at the given position."""
220
+
221
+ if pos < 0 or pos >= self._length:
222
+ raise IndexError("Invalid position!")
223
+
224
+ return self._locate(pos)
225
+
226
+ @abstractmethod
227
+ def _locate(self, pos: int) -> NodeT:
228
+ """Returns the node at an already-validated position."""
229
+
230
+ def clear(self) -> None:
231
+ """Removes all nodes from the list."""
232
+ self._head = self._end = None
233
+ self._length = 0
@@ -0,0 +1,231 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ A doubly linked list is a linear data structure composed of a sequence
5
+ of nodes where each node contains data and pointers (or references) to
6
+ both the next and the previous node, allowing traversal in either
7
+ direction.
8
+ """
9
+
10
+ # pylint: disable=duplicate-code
11
+ # SinglyLinkedList and DoubleLinkedList are independent siblings under
12
+ # ILinkedList rather than parent/child, so their mutators intentionally
13
+ # don't share an implementation, even where the resulting code overlaps.
14
+
15
+ from typing import Optional
16
+ from typing import cast
17
+
18
+ from core_mixins.compatibility import Self
19
+
20
+ from data_structures.base import T
21
+ from data_structures.lists.base import ILinkedList
22
+ from data_structures.lists.base import Node as LinkedNode
23
+
24
+
25
+ class Node(LinkedNode[T]):
26
+ """
27
+ Node in a double linked list, holding a value and the
28
+ references to the next and previous nodes.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ value: Optional[T] = None,
34
+ next_node: Optional[Self] = None,
35
+ prev_node: Optional[Self] = None,
36
+ ) -> None:
37
+ super().__init__(value=value, next_node=next_node)
38
+ self._prev = prev_node
39
+
40
+ @property
41
+ def prev(self) -> Optional[Self]:
42
+ """The previous node in the list, if any."""
43
+ return self._prev
44
+
45
+ @prev.setter
46
+ def prev(self, prev_node: Optional[Self]) -> None:
47
+ self._prev = prev_node
48
+
49
+
50
+ class DoubleLinkedList(ILinkedList[T, Node[T]]):
51
+ """
52
+ Double linked list that tracks its head node and supports
53
+ forward and backward iteration. Positions are 0-indexed.
54
+ """
55
+
56
+ @classmethod
57
+ def _create_node(cls, value: T) -> Node[T]:
58
+ return Node(value)
59
+
60
+ def _locate(self, pos: int) -> Node[T]:
61
+ """Returns the node at an already-validated position."""
62
+
63
+ if self._length - 1 - pos <= pos:
64
+ pointer = cast(Node[T], self._end)
65
+ index = self._length - 1
66
+
67
+ while index > pos:
68
+ pointer = cast(Node[T], pointer.prev)
69
+ index -= 1
70
+
71
+ else:
72
+ pointer = cast(Node[T], self._head)
73
+ index = 0
74
+
75
+ while index < pos:
76
+ pointer = cast(Node[T], pointer.next)
77
+ index += 1
78
+
79
+ return pointer
80
+
81
+ def prepend(self, value: T) -> None:
82
+ """Adds a value to the front of the list."""
83
+
84
+ if self._head is None:
85
+ self.append(value)
86
+
87
+ else:
88
+ new_head = self._create_node(value)
89
+ new_head.next = self._head
90
+ self._head.prev = new_head
91
+ self._head = new_head
92
+ self._length += 1
93
+
94
+ def append(self, value: T) -> None:
95
+ """Adds a value to the end of the list."""
96
+
97
+ new_node = self._create_node(value)
98
+
99
+ if self._end is None:
100
+ self._head = new_node
101
+ else:
102
+ self._end.next = new_node
103
+ new_node.prev = self._end
104
+
105
+ self._end = new_node
106
+ self._length += 1
107
+
108
+ def insert(self, value: T, pos: int) -> None:
109
+ """Inserts a value at the given position."""
110
+
111
+ if pos == 0:
112
+ self.prepend(value)
113
+
114
+ elif pos == self._length:
115
+ self.append(value)
116
+
117
+ else:
118
+ pointer = self._node_at(pos - 1)
119
+ old_next = pointer.next
120
+
121
+ new_node = self._create_node(value)
122
+ new_node.next = old_next
123
+ new_node.prev = pointer
124
+ cast(Node[T], old_next).prev = new_node
125
+
126
+ pointer.next = new_node
127
+ self._length += 1
128
+
129
+ def _pop_front(self) -> T:
130
+ """Removes and returns the value at the front of a non-empty list."""
131
+
132
+ head = cast(Node[T], self._head)
133
+ value = cast(T, head.value)
134
+
135
+ if self._length == 1:
136
+ self._head = None
137
+ self._end = None
138
+ else:
139
+ self._head = head.next
140
+ cast(Node[T], self._head).prev = None
141
+
142
+ self._length -= 1
143
+ return value
144
+
145
+ def _pop_back(self) -> T:
146
+ """Removes and returns the value at the end of a non-empty list."""
147
+
148
+ end = cast(Node[T], self._end)
149
+ value = cast(T, end.value)
150
+
151
+ if self._length == 1:
152
+ self.clear()
153
+ return value
154
+
155
+ self._end = end.prev
156
+ cast(Node[T], self._end).next = None
157
+ self._length -= 1
158
+ return value
159
+
160
+ def _pop_middle(self, index: int) -> T:
161
+ """Removes and returns the value at an interior position (not the first or last)."""
162
+
163
+ removed = self._node_at(index)
164
+ prev_node = cast(Node[T], removed.prev)
165
+ next_node = cast(Node[T], removed.next)
166
+
167
+ prev_node.next = next_node
168
+ next_node.prev = prev_node
169
+
170
+ self._length -= 1
171
+ return cast(T, removed.value)
172
+
173
+ def remove(self, value: T, all_values: bool = False) -> None:
174
+ """
175
+ Removes the first occurrence of the value, or all occurrences
176
+ if `all_values` is set.
177
+ """
178
+
179
+ was_found = False
180
+
181
+ while self._head is not None and self._head.value == value:
182
+ self._head = self._head.next
183
+ self._length -= 1
184
+ was_found = True
185
+
186
+ if self._head is None:
187
+ self._end = None
188
+ else:
189
+ self._head.prev = None
190
+
191
+ if not all_values:
192
+ return
193
+
194
+ pointer = self._head
195
+ while pointer is not None:
196
+ if pointer.value == value:
197
+ prev_node = cast(Node[T], pointer.prev)
198
+ next_node = pointer.next
199
+
200
+ prev_node.next = next_node
201
+ if next_node is not None:
202
+ next_node.prev = prev_node
203
+
204
+ if pointer is self._end:
205
+ self._end = prev_node
206
+
207
+ self._length -= 1
208
+ was_found = True
209
+ pointer = next_node
210
+
211
+ if not all_values:
212
+ return
213
+
214
+ else:
215
+ pointer = pointer.next
216
+
217
+ if not was_found:
218
+ raise ValueError(f"{value!r} is not in list")
219
+
220
+ def reverse(self) -> None:
221
+ """Reverses the list in place."""
222
+
223
+ if self.is_empty or self._length == 1:
224
+ return
225
+
226
+ pointer = self._head
227
+ while pointer is not None:
228
+ pointer.next, pointer.prev = pointer.prev, pointer.next
229
+ pointer = pointer.prev
230
+
231
+ self._head, self._end = self._end, self._head
@@ -0,0 +1,180 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ A linked list is a linear data structure composed of a sequence
5
+ of nodes where each node contains data and a pointer (or reference)
6
+ to the next node. Unlike arrays, elements in a linked list are not
7
+ stored in contiguous memory locations, meaning they can be scattered
8
+ across your system's RAM.
9
+ """
10
+
11
+ from typing import cast
12
+
13
+ from data_structures.base import T
14
+ from data_structures.lists.base import ILinkedList
15
+ from data_structures.lists.base import Node
16
+
17
+
18
+ class SinglyLinkedList(ILinkedList[T, Node[T]]):
19
+ """
20
+ Singly linked list that tracks its head node and supports
21
+ forward iteration. Positions are 0-indexed.
22
+ """
23
+
24
+ @classmethod
25
+ def _create_node(cls, value: T) -> Node[T]:
26
+ return Node(value)
27
+
28
+ def _locate(self, pos: int) -> Node[T]:
29
+ """Returns the node at an already-validated position."""
30
+
31
+ index, pointer = 0, cast(Node[T], self._head)
32
+ while index < pos:
33
+ pointer = cast(Node[T], pointer.next)
34
+ index += 1
35
+
36
+ return pointer
37
+
38
+ def prepend(self, value: T) -> None:
39
+ """Adds a value to the front of the list."""
40
+
41
+ if self._head is None:
42
+ self.append(value)
43
+
44
+ else:
45
+ new_head = self._create_node(value)
46
+ new_head.next = self._head
47
+ self._head = new_head
48
+ self._length += 1
49
+
50
+ def append(self, value: T) -> None:
51
+ """Adds a value to the end of the list."""
52
+
53
+ new_node = self._create_node(value)
54
+
55
+ if self._end is None:
56
+ self._head = new_node
57
+ else:
58
+ self._end.next = new_node
59
+
60
+ self._end = new_node
61
+ self._length += 1
62
+
63
+ def insert(self, value: T, pos: int) -> None:
64
+ """Inserts a value at the given position."""
65
+
66
+ if pos == 0:
67
+ self.prepend(value)
68
+
69
+ elif pos == self._length:
70
+ self.append(value)
71
+
72
+ else:
73
+ pointer = self._node_at(pos - 1)
74
+ new_node = self._create_node(value)
75
+ new_node.next = pointer.next
76
+ pointer.next = new_node
77
+ self._length += 1
78
+
79
+ def _pop_front(self) -> T:
80
+ """Removes and returns the value at the front of a non-empty list."""
81
+
82
+ head = cast(Node[T], self._head)
83
+ value = cast(T, head.value)
84
+
85
+ if self._length == 1:
86
+ self._head = None
87
+ self._end = None
88
+ else:
89
+ self._head = head.next
90
+
91
+ self._length -= 1
92
+ return value
93
+
94
+ def _pop_back(self) -> T:
95
+ """Removes and returns the value at the end of a non-empty list."""
96
+
97
+ value = cast(T, cast(Node[T], self._end).value)
98
+ if self._length == 1:
99
+ self.clear()
100
+ return value
101
+
102
+ pointer = self._node_at(self._length - 2)
103
+ pointer.next = None
104
+ self._end = pointer
105
+ self._length -= 1
106
+ return value
107
+
108
+ def _pop_middle(self, index: int) -> T:
109
+ """Removes and returns the value at an interior position (not the first or last)."""
110
+
111
+ pointer = self._node_at(index - 1)
112
+ removed = cast(Node[T], pointer.next)
113
+ pointer.next = removed.next
114
+ self._length -= 1
115
+ return cast(T, removed.value)
116
+
117
+ def remove(self, value: T, all_values: bool = False) -> None:
118
+ """
119
+ Removes the first occurrence of the value, or all occurrences
120
+ if `all_values` is set.
121
+ """
122
+
123
+ was_found = False
124
+
125
+ while self._head is not None and self._head.value == value:
126
+ self._head = self._head.next
127
+ self._length -= 1
128
+ was_found = True
129
+
130
+ if self._head is None:
131
+ self._end = None
132
+
133
+ if not all_values:
134
+ return
135
+
136
+ if self._head is not None:
137
+ left = self._head
138
+ right = left.next
139
+
140
+ while right:
141
+ if right.value == value:
142
+ left.next = right.next
143
+ self._length -= 1
144
+ was_found = True
145
+
146
+ if right is self._end:
147
+ self._end = left
148
+
149
+ right = left.next
150
+
151
+ if not all_values:
152
+ return
153
+
154
+ else:
155
+ left = right
156
+ right = right.next
157
+
158
+ if not was_found:
159
+ raise ValueError(f"{value!r} is not in list")
160
+
161
+ def reverse(self) -> None:
162
+ """Reverses the list in place."""
163
+
164
+ if self.is_empty or self._length == 1:
165
+ return
166
+
167
+ new_end = left = cast(Node[T], self._head)
168
+ new_head = cast(Node[T], new_end.next)
169
+ right = new_head.next
170
+
171
+ while new_head is not self._end:
172
+ new_head.next = left
173
+ left = new_head
174
+ new_head = cast(Node[T], right)
175
+ right = new_head.next
176
+
177
+ new_head.next = left
178
+ new_end.next = None
179
+ self._end = new_end
180
+ self._head = new_head
File without changes
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
9
9
  [project]
10
10
  name = "core-data-structures"
11
11
  description = "This project/library contains commons data structures..."
12
- version = "1.2.0"
12
+ version = "1.3.0"
13
13
 
14
14
  authors = [
15
15
  {name = "Alejandro Cora González", email = "alek.cora.glez@gmail.com"}
@@ -40,12 +40,13 @@ classifiers = [
40
40
  ]
41
41
 
42
42
  dependencies = [
43
+ "core-mixins>=3.2.0",
43
44
  "typing-extensions>=4.8.0",
44
45
  ]
45
46
 
46
47
  [project.optional-dependencies]
47
48
  dev = [
48
- "core-dev-tools>=2.0.0",
49
+ "core-dev-tools>=2.1.0",
49
50
  "core-tests>=2.1.0",
50
51
  ]
51
52