core-data-structures 1.2.1__tar.gz → 1.4.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 (27) hide show
  1. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/PKG-INFO +12 -3
  2. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/README.rst +11 -2
  3. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/core_data_structures.egg-info/PKG-INFO +12 -3
  4. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/core_data_structures.egg-info/SOURCES.txt +3 -0
  5. core_data_structures-1.4.0/data_structures/lists/base.py +233 -0
  6. core_data_structures-1.4.0/data_structures/lists/double_linked_list.py +231 -0
  7. core_data_structures-1.4.0/data_structures/lists/singly_linked_list.py +180 -0
  8. core_data_structures-1.4.0/data_structures/trees/base.py +46 -0
  9. core_data_structures-1.4.0/data_structures/trees/binary_tree.py +246 -0
  10. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/pyproject.toml +1 -1
  11. core_data_structures-1.2.1/data_structures/lists/singly_linked_list.py +0 -308
  12. core_data_structures-1.2.1/data_structures/trees/binary_tree.py +0 -230
  13. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/LICENSE +0 -0
  14. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/core_data_structures.egg-info/dependency_links.txt +0 -0
  15. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/core_data_structures.egg-info/requires.txt +0 -0
  16. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/core_data_structures.egg-info/top_level.txt +0 -0
  17. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/__init__.py +0 -0
  18. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/base.py +0 -0
  19. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/graphs/__init__.py +0 -0
  20. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/graphs/graphs.py +0 -0
  21. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/graphs/vertex.py +0 -0
  22. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/lists/__init__.py +0 -0
  23. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/py.typed +0 -0
  24. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/trees/__init__.py +0 -0
  25. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/data_structures/trees/simple_tree.py +0 -0
  26. {core_data_structures-1.2.1 → core_data_structures-1.4.0}/setup.cfg +0 -0
  27. {core_data_structures-1.2.1 → core_data_structures-1.4.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.4.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.4.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,7 +14,10 @@ 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
21
+ data_structures/trees/base.py
19
22
  data_structures/trees/binary_tree.py
20
23
  data_structures/trees/simple_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