core-data-structures 1.2.1__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 (25) hide show
  1. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/PKG-INFO +12 -3
  2. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/README.rst +11 -2
  3. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/core_data_structures.egg-info/PKG-INFO +12 -3
  4. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/core_data_structures.egg-info/SOURCES.txt +2 -0
  5. core_data_structures-1.3.0/data_structures/lists/base.py +233 -0
  6. core_data_structures-1.3.0/data_structures/lists/double_linked_list.py +231 -0
  7. core_data_structures-1.3.0/data_structures/lists/singly_linked_list.py +180 -0
  8. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/pyproject.toml +1 -1
  9. core_data_structures-1.2.1/data_structures/lists/singly_linked_list.py +0 -308
  10. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/LICENSE +0 -0
  11. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/core_data_structures.egg-info/dependency_links.txt +0 -0
  12. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/core_data_structures.egg-info/requires.txt +0 -0
  13. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/core_data_structures.egg-info/top_level.txt +0 -0
  14. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/__init__.py +0 -0
  15. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/base.py +0 -0
  16. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/graphs/__init__.py +0 -0
  17. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/graphs/graphs.py +0 -0
  18. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/graphs/vertex.py +0 -0
  19. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/lists/__init__.py +0 -0
  20. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/py.typed +0 -0
  21. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/trees/__init__.py +0 -0
  22. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/trees/binary_tree.py +0 -0
  23. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/data_structures/trees/simple_tree.py +0 -0
  24. {core_data_structures-1.2.1 → core_data_structures-1.3.0}/setup.cfg +0 -0
  25. {core_data_structures-1.2.1 → 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.1
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
@@ -35,8 +35,8 @@ Dynamic: license-file
35
35
  core-data-structures
36
36
  ===============================================================================
37
37
 
38
- A Python library providing common data structures including graphs and trees,
39
- 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.
40
40
 
41
41
  ===============================================================================
42
42
 
@@ -82,6 +82,15 @@ Features
82
82
  - Recursive and iterative variants
83
83
  - Tree depth calculation
84
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
+
85
94
 
86
95
  Quick Start
87
96
  ===============================================================================
@@ -1,8 +1,8 @@
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
 
@@ -48,6 +48,15 @@ Features
48
48
  - Recursive and iterative variants
49
49
  - Tree depth calculation
50
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
+
51
60
 
52
61
  Quick Start
53
62
  ===============================================================================
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-data-structures
3
- Version: 1.2.1
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
@@ -35,8 +35,8 @@ Dynamic: license-file
35
35
  core-data-structures
36
36
  ===============================================================================
37
37
 
38
- A Python library providing common data structures including graphs and trees,
39
- 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.
40
40
 
41
41
  ===============================================================================
42
42
 
@@ -82,6 +82,15 @@ Features
82
82
  - Recursive and iterative variants
83
83
  - Tree depth calculation
84
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
+
85
94
 
86
95
  Quick Start
87
96
  ===============================================================================
@@ -14,6 +14,8 @@ data_structures/graphs/__init__.py
14
14
  data_structures/graphs/graphs.py
15
15
  data_structures/graphs/vertex.py
16
16
  data_structures/lists/__init__.py
17
+ data_structures/lists/base.py
18
+ data_structures/lists/double_linked_list.py
17
19
  data_structures/lists/singly_linked_list.py
18
20
  data_structures/trees/__init__.py
19
21
  data_structures/trees/binary_tree.py
@@ -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
@@ -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.1"
12
+ version = "1.3.0"
13
13
 
14
14
  authors = [
15
15
  {name = "Alejandro Cora González", email = "alek.cora.glez@gmail.com"}
@@ -1,308 +0,0 @@
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 contextlib import suppress
12
- from typing import Generic, Optional, Iterator
13
- from typing import cast
14
-
15
- from core_mixins.compatibility import Self
16
-
17
- from data_structures.base import Node as BaseNode
18
- from data_structures.base import T
19
-
20
-
21
- class Node(BaseNode[T]):
22
- """
23
- Node in a singly linked list, holding a value and a
24
- reference to the next node.
25
- """
26
-
27
- def __init__(
28
- self,
29
- value: Optional[T] = None,
30
- next_node: Optional[Self] = None,
31
- ) -> None:
32
- super().__init__(value=value)
33
- self._next = next_node
34
-
35
- @property
36
- def next(self) -> Optional[Self]:
37
- """The next node in the list, if any."""
38
- return self._next
39
-
40
- @next.setter
41
- def next(self, next_node: Optional[Self]) -> None:
42
- self._next = next_node
43
-
44
-
45
- class SinglyLinkedList(Generic[T]):
46
- """
47
- Singly linked list that tracks its head node and supports
48
- forward iteration. Positions are 0-indexed.
49
- """
50
-
51
- def __init__(self, value: Optional[T] = None) -> None:
52
- self._head: Optional[Node[T]] = None if value is None else Node(value)
53
- self._length = 0 if value is None else 1
54
- self._end = self._head
55
-
56
- def __len__(self):
57
- """Returns the length of the list."""
58
- return self._length
59
-
60
- def __getitem__(self, item: int) -> T:
61
- """Returns the value at the given position."""
62
- return self.get(item)
63
-
64
- def __iter__(self) -> Iterator[T]:
65
- """Iterates over the values in the list, starting from the head."""
66
-
67
- pointer = self._head
68
- while pointer:
69
- yield cast(T, pointer.value)
70
- pointer = pointer.next
71
-
72
- def __contains__(self, item: T) -> bool:
73
- with suppress(ValueError):
74
- self.index(item)
75
- return True
76
- return False
77
-
78
- @classmethod
79
- def from_list(cls, a_list: list) -> Self:
80
- """Creates the singly linked list from a python list."""
81
-
82
- new_list = cls()
83
- for value in a_list:
84
- new_list.append(value)
85
- return new_list
86
-
87
- @property
88
- def length(self) -> int:
89
- """The number of nodes in the list."""
90
- return self._length
91
-
92
- @property
93
- def is_empty(self) -> bool:
94
- """Whether the list has no nodes."""
95
- return self._head is None
96
-
97
- @property
98
- def head(self) -> Optional[Node[T]]:
99
- """The first node in the list."""
100
- return self._head
101
-
102
- @property
103
- def end(self) -> Optional[Node[T]]:
104
- """The end node in the list."""
105
- return self._end
106
-
107
- def clear(self) -> None:
108
- """Removes all nodes from the list."""
109
- self._head = self._end = None
110
- self._length = 0
111
-
112
- def to_list(self) -> list:
113
- """Materializes the list's values into a python list."""
114
- return list(self)
115
-
116
- def _node_at(self, pos: int) -> Node[T]:
117
- """Returns the actual node at the given position."""
118
-
119
- if pos < 0 or pos >= self._length:
120
- raise IndexError("Invalid position!")
121
-
122
- index, pointer = 0, cast(Node[T], self._head)
123
- while index < pos:
124
- pointer = cast(Node[T], pointer.next)
125
- index += 1
126
-
127
- return pointer
128
-
129
- def get(self, pos: int) -> T:
130
- """Returns the value at the given position."""
131
- return cast(T, self._node_at(pos).value)
132
-
133
- def index(self, value: T) -> int:
134
- """Returns the position of the first occurrence of the given value."""
135
-
136
- for pos, value_ in enumerate(self):
137
- if value_ == value:
138
- return pos
139
-
140
- raise ValueError(f"{value!r} is not in list")
141
-
142
- def prepend(self, value: T) -> None:
143
- """Adds a value to the front of the list."""
144
-
145
- if self._head is None:
146
- self.append(value)
147
-
148
- else:
149
- self._head = Node(value, next_node=self._head)
150
- self._length += 1
151
-
152
- def append(self, value: T) -> None:
153
- """Adds a value to the end of the list."""
154
-
155
- new_node = Node(value)
156
-
157
- if self._end is None:
158
- self._head = new_node
159
- else:
160
- self._end.next = new_node
161
-
162
- self._end = new_node
163
- self._length += 1
164
-
165
- def insert(self, value: T, pos: int) -> None:
166
- """Inserts a value at the given position."""
167
-
168
- if pos == 0:
169
- self.prepend(value)
170
-
171
- elif pos == self._length:
172
- self.append(value)
173
-
174
- else:
175
- pointer = self._node_at(pos - 1)
176
- pointer.next = Node(value, next_node=pointer.next)
177
- self._length += 1
178
-
179
- def extend(self, a_list: Self) -> None:
180
- """Appends a copy of each value from another list."""
181
-
182
- for value in a_list:
183
- self.append(value)
184
-
185
- def pop_front(self) -> T:
186
- """Removes and return the value at the front of the list."""
187
-
188
- if self.is_empty:
189
- raise IndexError("Empty list!")
190
-
191
- head = cast(Node[T], self._head)
192
- value = cast(T, head.value)
193
-
194
- if self._length == 1:
195
- self._head = None
196
- self._end = None
197
- else:
198
- self._head = head.next
199
-
200
- self._length -= 1
201
- return value
202
-
203
- def pop_back(self) -> T:
204
- """Removes and returns the value at the end of the list."""
205
-
206
- if self.is_empty:
207
- raise IndexError("Empty list!")
208
-
209
- value = cast(T, cast(Node[T], self._end).value)
210
- if self._length == 1:
211
- self.clear()
212
- return value
213
-
214
- pointer = cast(Node[T], self._head)
215
- while pointer.next is not self._end:
216
- pointer = cast(Node[T], pointer.next)
217
-
218
- pointer.next = None
219
- self._end = pointer
220
- self._length -= 1
221
- return value
222
-
223
- def pop(self, index: int) -> T:
224
- """Removes and returns the value at the given position."""
225
-
226
- if index < 0 or index >= self._length:
227
- raise IndexError("Index error!")
228
-
229
- if index == 0:
230
- return self.pop_front()
231
-
232
- if index == self._length - 1:
233
- return self.pop_back()
234
-
235
- pointer = self._node_at(index - 1)
236
- removed = cast(Node[T], pointer.next)
237
- pointer.next = removed.next
238
- self._length -= 1
239
- return cast(T, removed.value)
240
-
241
- def remove(self, value: T, all_values: bool = False) -> None:
242
- """Removes the first occurrence of the value, or all occurrences if `all_values` is set."""
243
-
244
- was_found = False
245
-
246
- while self._head is not None and self._head.value == value:
247
- self._head = self._head.next
248
- self._length -= 1
249
- was_found = True
250
-
251
- if self._head is None:
252
- self._end = None
253
-
254
- if not all_values:
255
- return
256
-
257
- if self._head is not None:
258
- left = self._head
259
- right = left.next
260
-
261
- while right:
262
- if right.value == value:
263
- left.next = right.next
264
- self._length -= 1
265
- was_found = True
266
-
267
- if right is self._end:
268
- self._end = left
269
-
270
- right = left.next
271
-
272
- if not all_values:
273
- return
274
-
275
- else:
276
- left = right
277
- right = right.next
278
-
279
- if not was_found:
280
- raise ValueError(f"{value!r} is not in list")
281
-
282
- def reverse(self) -> None:
283
- """Reverses the list in place."""
284
-
285
- if self.is_empty or self._length == 1:
286
- return
287
-
288
- if self._length == 2:
289
- head, end = cast(Node[T], self._head), cast(Node[T], self._end)
290
- self._head, self._end = end, head
291
- self._head.next = self._end
292
- self._end.next = None
293
- return
294
-
295
- new_end = left = cast(Node[T], self._head)
296
- new_head = cast(Node[T], new_end.next)
297
- right = new_head.next
298
-
299
- while new_head is not self._end:
300
- new_head.next = left
301
- left = new_head
302
- new_head = cast(Node[T], right)
303
- right = new_head.next
304
-
305
- new_head.next = left
306
- new_end.next = None
307
- self._end = new_end
308
- self._head = new_head