pydasa 0.4.7__py3-none-any.whl
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.
- pydasa/__init__.py +103 -0
- pydasa/_version.py +6 -0
- pydasa/analysis/__init__.py +0 -0
- pydasa/analysis/scenario.py +584 -0
- pydasa/analysis/simulation.py +1158 -0
- pydasa/context/__init__.py +0 -0
- pydasa/context/conversion.py +11 -0
- pydasa/context/system.py +17 -0
- pydasa/context/units.py +15 -0
- pydasa/core/__init__.py +15 -0
- pydasa/core/basic.py +287 -0
- pydasa/core/cfg/default.json +136 -0
- pydasa/core/constants.py +27 -0
- pydasa/core/io.py +102 -0
- pydasa/core/setup.py +269 -0
- pydasa/dimensional/__init__.py +0 -0
- pydasa/dimensional/buckingham.py +728 -0
- pydasa/dimensional/fundamental.py +146 -0
- pydasa/dimensional/model.py +1077 -0
- pydasa/dimensional/vaschy.py +633 -0
- pydasa/elements/__init__.py +19 -0
- pydasa/elements/parameter.py +218 -0
- pydasa/elements/specs/__init__.py +22 -0
- pydasa/elements/specs/conceptual.py +161 -0
- pydasa/elements/specs/numerical.py +469 -0
- pydasa/elements/specs/statistical.py +229 -0
- pydasa/elements/specs/symbolic.py +394 -0
- pydasa/serialization/__init__.py +27 -0
- pydasa/serialization/parser.py +133 -0
- pydasa/structs/__init__.py +0 -0
- pydasa/structs/lists/__init__.py +0 -0
- pydasa/structs/lists/arlt.py +578 -0
- pydasa/structs/lists/dllt.py +18 -0
- pydasa/structs/lists/ndlt.py +262 -0
- pydasa/structs/lists/sllt.py +746 -0
- pydasa/structs/tables/__init__.py +0 -0
- pydasa/structs/tables/htme.py +182 -0
- pydasa/structs/tables/scht.py +774 -0
- pydasa/structs/tools/__init__.py +0 -0
- pydasa/structs/tools/hashing.py +53 -0
- pydasa/structs/tools/math.py +149 -0
- pydasa/structs/tools/memory.py +54 -0
- pydasa/structs/types/__init__.py +0 -0
- pydasa/structs/types/functions.py +131 -0
- pydasa/structs/types/generics.py +54 -0
- pydasa/validations/__init__.py +0 -0
- pydasa/validations/decorators.py +510 -0
- pydasa/validations/error.py +100 -0
- pydasa/validations/patterns.py +32 -0
- pydasa/workflows/__init__.py +1 -0
- pydasa/workflows/influence.py +497 -0
- pydasa/workflows/phenomena.py +529 -0
- pydasa/workflows/practical.py +765 -0
- pydasa-0.4.7.dist-info/METADATA +320 -0
- pydasa-0.4.7.dist-info/RECORD +58 -0
- pydasa-0.4.7.dist-info/WHEEL +5 -0
- pydasa-0.4.7.dist-info/licenses/LICENSE +674 -0
- pydasa-0.4.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module sllt.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module for the custom **SingleLinkedList** data structure in *PyDASA*. Essential for Dimensional Analysis and Data Science operations.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
**SingleLinkedList**: Implements a single linked list with methods for insertion, deletion, and traversal.
|
|
10
|
+
|
|
11
|
+
*IMPORTANT:* based on the implementations proposed by the following authors/books:
|
|
12
|
+
|
|
13
|
+
#. Algorithms, 4th Edition, Robert Sedgewick and Kevin Wayne.
|
|
14
|
+
#. Data Structure and Algorithms in Python, M.T. Goodrich, R. Tamassia, M.H. Goldwasser.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# native python modules
|
|
18
|
+
# dataclass imports
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
# data type imports
|
|
21
|
+
from typing import List, Optional, Callable, Generic, Iterator, Any
|
|
22
|
+
# code inspection imports
|
|
23
|
+
import inspect
|
|
24
|
+
|
|
25
|
+
# custom modules
|
|
26
|
+
# linked list node implementation
|
|
27
|
+
from pydasa.structs.lists.ndlt import SLNode
|
|
28
|
+
|
|
29
|
+
# generic types and global variables
|
|
30
|
+
from pydasa.structs.types.generics import T
|
|
31
|
+
from pydasa.structs.types.generics import DFLT_DICT_KEY
|
|
32
|
+
from pydasa.structs.types.generics import VLD_IOTYPE_LT
|
|
33
|
+
from pydasa.structs.types.functions import dflt_cmp_function_lt
|
|
34
|
+
# generic error handling and type checking
|
|
35
|
+
from pydasa.validations.error import handle_error as error
|
|
36
|
+
|
|
37
|
+
# checking custom modules
|
|
38
|
+
assert T
|
|
39
|
+
assert DFLT_DICT_KEY
|
|
40
|
+
assert VLD_IOTYPE_LT
|
|
41
|
+
assert dflt_cmp_function_lt
|
|
42
|
+
assert error
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class SingleLinkedList(Generic[T]):
|
|
47
|
+
"""**SingleLinkedList** implements a single linked list data structure for PyDASA.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
Generic (T): Generic type for a Python data structure.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
SingleLinkedList: a generic single linked list data structure with the following attributes:
|
|
54
|
+
- **cmp_function**: function to compare elements in the list.
|
|
55
|
+
- **key**: key to identify the elements in the list.
|
|
56
|
+
- **first**: reference to the first node of the list.
|
|
57
|
+
- **last**: reference to the last node of the list.
|
|
58
|
+
- **_size**: size of the list.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# the cmp_function is used to compare elements, not defined by default
|
|
62
|
+
# :attr: cmp_function
|
|
63
|
+
cmp_function: Optional[Callable[[T, T], int]] = None
|
|
64
|
+
"""
|
|
65
|
+
Customizable comparison function for *SingleLinkedList* elements. Defaults to *dflt_cmp_function_lt()* from *PyDASA*, but can be overridden by the user.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# reference to the first node of the list
|
|
69
|
+
# :attr: _first
|
|
70
|
+
_first: Optional[SLNode[T]] = None
|
|
71
|
+
"""
|
|
72
|
+
Reference to the first node of the *SingleLinkedList*.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# reference to the last node of the list
|
|
76
|
+
# :attr: _last
|
|
77
|
+
_last: Optional[SLNode[T]] = None
|
|
78
|
+
"""
|
|
79
|
+
Reference to the last node of the *SingleLinkedList*.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# the key is used to compare elements, not defined by default
|
|
83
|
+
# :attr: key
|
|
84
|
+
key: Optional[str] = DFLT_DICT_KEY
|
|
85
|
+
"""
|
|
86
|
+
Customizable key name for identifying elements in the *SingleLinkedList*. Defaults to *DFLT_DICT_KEY = '_id'* from *PyDASA*, but can be overridden by the user.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# by default, the list is empty
|
|
90
|
+
# :attr: _size
|
|
91
|
+
_size: int = 0
|
|
92
|
+
"""
|
|
93
|
+
Size of the *SingleLinkedList*, starting at 0 and updated with each modification.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# input elements from python list
|
|
97
|
+
# :attr: iodata
|
|
98
|
+
iodata: Optional[List[T]] = None
|
|
99
|
+
"""
|
|
100
|
+
Optional Python list for loading external data intho the *SingleLinkedList*. Defaults to *None* but can be provided during creation.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __post_init__(self) -> None:
|
|
104
|
+
"""*__post_init__()* Initializes the *SingleLinkedList* after creation by setting attributes like *cmp_function*, *key*, *first*, *last*, and *iodata*.
|
|
105
|
+
|
|
106
|
+
*NOTE:* Special method called automatically after object creation.
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
# if the key is not defined, use the default
|
|
110
|
+
if self.key is None:
|
|
111
|
+
self.key = DFLT_DICT_KEY
|
|
112
|
+
|
|
113
|
+
# if the compare function is not defined, use the default
|
|
114
|
+
if self.cmp_function is None:
|
|
115
|
+
self.cmp_function = self.default_compare
|
|
116
|
+
|
|
117
|
+
# if elements are provided, add them to the ArrayList
|
|
118
|
+
if self.iodata is not None:
|
|
119
|
+
if not isinstance(self.iodata, VLD_IOTYPE_LT):
|
|
120
|
+
raise TypeError(f"iodata must be a valid iterable type, got {type(self.iodata)}")
|
|
121
|
+
|
|
122
|
+
for elm in self.iodata:
|
|
123
|
+
self.append(elm)
|
|
124
|
+
|
|
125
|
+
# Clear iodata after processing
|
|
126
|
+
self.iodata = None
|
|
127
|
+
|
|
128
|
+
except Exception as err:
|
|
129
|
+
self._error_handler(err)
|
|
130
|
+
|
|
131
|
+
def default_compare(self, elm1: Any, elm2: Any) -> int:
|
|
132
|
+
"""*default_compare()* Default comparison function for *SingleLinkedList* elements. Compares two elements and returns:
|
|
133
|
+
- 0 if they are equal,
|
|
134
|
+
- 1 if the first is greater,
|
|
135
|
+
- -1 if the first is smaller.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
elm1 (Any): First element to compare.
|
|
139
|
+
elm2 (Any): Second element to compare.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
int: Comparison result.
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
# default comparison needs the key to be defined
|
|
146
|
+
if self.key is None:
|
|
147
|
+
raise ValueError("Key must be set before comparison")
|
|
148
|
+
return dflt_cmp_function_lt(elm1, elm2, self.key)
|
|
149
|
+
except Exception as err:
|
|
150
|
+
self._error_handler(err)
|
|
151
|
+
raise # Re-raise the exception after handling
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def size(self) -> int:
|
|
155
|
+
"""*size()* Property to retrieve the number of elements in the *SingleLinkedList*.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
int: number of elements in the *SingleLinkedList*.
|
|
159
|
+
"""
|
|
160
|
+
return self._size
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def empty(self) -> bool:
|
|
164
|
+
"""*empty()* Property to check if the *SingleLinkedList* is empty.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
bool: True if the *SingleLinkedList* is empty, False otherwise.
|
|
168
|
+
"""
|
|
169
|
+
return self._size == 0
|
|
170
|
+
|
|
171
|
+
def clear(self) -> None:
|
|
172
|
+
"""*clear()* clears the *SingleLinkedList* by removing all elements and resetting the size to 0.
|
|
173
|
+
|
|
174
|
+
NOTE: This method is used to empty the *SingleLinkedList* without deleting the object itself.
|
|
175
|
+
"""
|
|
176
|
+
self._first = None
|
|
177
|
+
self._last = None
|
|
178
|
+
self._size = 0
|
|
179
|
+
|
|
180
|
+
def prepend(self, elm: T) -> None:
|
|
181
|
+
"""*prepend()* adds an element to the beginning of the *SingleLinkedList*.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
elm (T): element to be added to the beginning of the structure.
|
|
185
|
+
"""
|
|
186
|
+
# if the element type is valid, add it to the list
|
|
187
|
+
if self._validate_type(elm):
|
|
188
|
+
# create a new node
|
|
189
|
+
_new = SLNode(elm)
|
|
190
|
+
_new.next = self._first
|
|
191
|
+
self._first = _new
|
|
192
|
+
if self.size == 0:
|
|
193
|
+
self._last = self._first
|
|
194
|
+
self._size += 1
|
|
195
|
+
|
|
196
|
+
def append(self, elm: T) -> None:
|
|
197
|
+
"""*append()* adds an element to the end of the *SingleLinkedList*.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
elm (T): element to be added to the end of the structure.
|
|
201
|
+
"""
|
|
202
|
+
# if the element type is valid, add it to the list
|
|
203
|
+
if self._validate_type(elm):
|
|
204
|
+
# create a new node
|
|
205
|
+
_new = SLNode(elm)
|
|
206
|
+
if self._last is None:
|
|
207
|
+
self._first = _new
|
|
208
|
+
else:
|
|
209
|
+
self._last.next = _new
|
|
210
|
+
self._last = _new
|
|
211
|
+
self._size += 1
|
|
212
|
+
|
|
213
|
+
def insert(self, elm: T, pos: int) -> None:
|
|
214
|
+
"""*insert()* adds an element to the *SingleLinkedList* at a specific position.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
elm (T): element to be added to the structure.
|
|
218
|
+
pos (int): position where the element will be added.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
IndexError: error if the structure is empty.
|
|
222
|
+
IndexError: error if the position is invalid.
|
|
223
|
+
TypeError: error if the element type is invalid.
|
|
224
|
+
"""
|
|
225
|
+
if not self.empty and self._validate_type(elm):
|
|
226
|
+
if pos < 0 or pos > self.size:
|
|
227
|
+
raise IndexError("Position is out of range")
|
|
228
|
+
|
|
229
|
+
# create a new node
|
|
230
|
+
_new = SLNode(elm)
|
|
231
|
+
|
|
232
|
+
# if the position is the first, add it to the first
|
|
233
|
+
if pos == 0:
|
|
234
|
+
_new.next = self._first
|
|
235
|
+
self._first = _new
|
|
236
|
+
if self._last is None: # Empty list case
|
|
237
|
+
self._last = _new
|
|
238
|
+
|
|
239
|
+
# if the position is the last (append), add it to the end
|
|
240
|
+
elif pos == self.size:
|
|
241
|
+
if self._last is not None:
|
|
242
|
+
self._last.next = _new
|
|
243
|
+
self._last = _new
|
|
244
|
+
|
|
245
|
+
# otherwise, insert in the middle
|
|
246
|
+
else:
|
|
247
|
+
i = 0
|
|
248
|
+
_cur = self._first
|
|
249
|
+
_prev: Optional[SLNode[T]] = None
|
|
250
|
+
while i < pos and _cur is not None:
|
|
251
|
+
_prev = _cur
|
|
252
|
+
_cur = _cur.next
|
|
253
|
+
i += 1
|
|
254
|
+
if _prev is not None:
|
|
255
|
+
_new.next = _cur
|
|
256
|
+
_prev.next = _new
|
|
257
|
+
# increment the size
|
|
258
|
+
self._size += 1
|
|
259
|
+
else:
|
|
260
|
+
raise IndexError("Empty data structure")
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def first(self) -> T:
|
|
264
|
+
"""*first* Property to read the first element of the *SingleLinkedList*.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
IndexError: error if the structure is empty.
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
T: the first element of the *SingleLinkedList*.
|
|
271
|
+
"""
|
|
272
|
+
if self.empty or self._first is None:
|
|
273
|
+
raise IndexError("Empty data structure")
|
|
274
|
+
return self._first.data
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def last(self) -> T:
|
|
278
|
+
"""*last* Property to read the last element of the *SingleLinkedList*.
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
Exception: error if the structure is empty.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
T: the last element of the *SingleLinkedList*.
|
|
285
|
+
"""
|
|
286
|
+
if self.empty or self._last is None:
|
|
287
|
+
raise IndexError("Empty data structure")
|
|
288
|
+
return self._last.data
|
|
289
|
+
|
|
290
|
+
def get(self, pos: int) -> T:
|
|
291
|
+
"""*get()* retrieves an element from the *SingleLinkedList* at a specific position.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
pos (int): position of the element to be retrieved.
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
IndexError: error if the structure is empty.
|
|
298
|
+
IndexError: error if the position is invalid.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
T: the element at the specified position in the *SingleLinkedList*.
|
|
302
|
+
"""
|
|
303
|
+
if self.empty:
|
|
304
|
+
raise IndexError("Empty data structure")
|
|
305
|
+
if pos < 0 or pos > self.size - 1:
|
|
306
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
307
|
+
|
|
308
|
+
# current node starting at the first node
|
|
309
|
+
_cur = self._first
|
|
310
|
+
i = 0
|
|
311
|
+
# iterate to the desired position
|
|
312
|
+
while i != pos and _cur is not None:
|
|
313
|
+
_cur = _cur.next
|
|
314
|
+
i += 1
|
|
315
|
+
|
|
316
|
+
if _cur is None:
|
|
317
|
+
raise IndexError(f"Corrupted list structure at position {pos}")
|
|
318
|
+
|
|
319
|
+
return _cur.data
|
|
320
|
+
|
|
321
|
+
def __getitem__(self, pos: int) -> Optional[T]:
|
|
322
|
+
"""*__getitem__()* retrieves an element from the *SingleLinkedList* at a specific position.
|
|
323
|
+
|
|
324
|
+
NOTE: This method is used to access the elements of the *SingleLinkedList* using the square brackets notation.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
pos (int): position of the element to be retrieved.
|
|
328
|
+
|
|
329
|
+
Raises:
|
|
330
|
+
IndexError: error if the structure is empty.
|
|
331
|
+
IndexError: error if the position is invalid.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Optional[T]: the element at the specified position in the *SingleLinkedList*.
|
|
335
|
+
"""
|
|
336
|
+
return self.get(pos)
|
|
337
|
+
|
|
338
|
+
def pop_first(self) -> T:
|
|
339
|
+
"""*pop_first()* removes the first element from the *SingleLinkedList*.
|
|
340
|
+
|
|
341
|
+
Raises:
|
|
342
|
+
IndexError: error if the structure is empty.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
T: the first element of the *SingleLinkedList*.
|
|
346
|
+
"""
|
|
347
|
+
# check if the list is empty
|
|
348
|
+
if self.empty or self._first is None:
|
|
349
|
+
raise IndexError("Empty data structure")
|
|
350
|
+
|
|
351
|
+
# save the data before removing the node
|
|
352
|
+
_data = self._first.data
|
|
353
|
+
|
|
354
|
+
# move first pointer to the next node
|
|
355
|
+
self._first = self._first.next
|
|
356
|
+
self._size -= 1
|
|
357
|
+
|
|
358
|
+
# if the list is now empty, set last to None
|
|
359
|
+
if self._first is None:
|
|
360
|
+
self._last = None
|
|
361
|
+
|
|
362
|
+
return _data
|
|
363
|
+
|
|
364
|
+
def pop_last(self) -> T:
|
|
365
|
+
"""*pop_last()* removes the last element from the *SingleLinkedList*.
|
|
366
|
+
|
|
367
|
+
Raises:
|
|
368
|
+
IndexError: error if the structure is empty.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
T: the last element of the *SingleLinkedList*.
|
|
372
|
+
"""
|
|
373
|
+
# Check if the list is empty
|
|
374
|
+
if self.empty or self._last is None:
|
|
375
|
+
raise IndexError("Empty data structure")
|
|
376
|
+
|
|
377
|
+
# Save the data before removing the node
|
|
378
|
+
_data = self._last.data
|
|
379
|
+
|
|
380
|
+
# if the list has only one element, set the first and last to None
|
|
381
|
+
if self._first == self._last:
|
|
382
|
+
self._first = None
|
|
383
|
+
self._last = None
|
|
384
|
+
|
|
385
|
+
# otherwise, remove the last element
|
|
386
|
+
else:
|
|
387
|
+
_cur = self._first
|
|
388
|
+
# traverse the list to find the second-to-last element
|
|
389
|
+
while _cur is not None and _cur.next != self._last:
|
|
390
|
+
_cur = _cur.next
|
|
391
|
+
|
|
392
|
+
# Ensure we found the second-to-last node
|
|
393
|
+
if _cur is None:
|
|
394
|
+
raise IndexError("Corrupted list structure")
|
|
395
|
+
|
|
396
|
+
# rearrange the last element
|
|
397
|
+
self._last = _cur
|
|
398
|
+
self._last.next = None
|
|
399
|
+
|
|
400
|
+
self._size -= 1
|
|
401
|
+
return _data
|
|
402
|
+
|
|
403
|
+
def remove(self, pos: int) -> T:
|
|
404
|
+
"""*remove()* removes an element from the *SingleLinkedList* at a specific position.
|
|
405
|
+
|
|
406
|
+
Args:
|
|
407
|
+
pos (int): position of the element to be removed.
|
|
408
|
+
|
|
409
|
+
Raises:
|
|
410
|
+
IndexError: error if the structure is empty.
|
|
411
|
+
IndexError: error if the position is invalid.
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
T: the element removed from the *SingleLinkedList*.
|
|
415
|
+
"""
|
|
416
|
+
# check if the list is empty
|
|
417
|
+
if self.empty or self._first is None:
|
|
418
|
+
raise IndexError("Empty data structure")
|
|
419
|
+
# check if the position is valid
|
|
420
|
+
if pos < 0 or pos > self.size - 1:
|
|
421
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
422
|
+
|
|
423
|
+
# if removing the first element
|
|
424
|
+
if pos == 0:
|
|
425
|
+
_data = self._first.data
|
|
426
|
+
self._first = self._first.next
|
|
427
|
+
# if list is now empty, update last
|
|
428
|
+
if self._first is None:
|
|
429
|
+
self._last = None
|
|
430
|
+
# if removing from middle or end
|
|
431
|
+
else:
|
|
432
|
+
_cur = self._first
|
|
433
|
+
_prev = self._first
|
|
434
|
+
i = 0
|
|
435
|
+
# traverse to the position
|
|
436
|
+
while i != pos and _cur is not None:
|
|
437
|
+
_prev = _cur
|
|
438
|
+
_cur = _cur.next
|
|
439
|
+
i += 1
|
|
440
|
+
|
|
441
|
+
# Check if we found the node
|
|
442
|
+
if _cur is None:
|
|
443
|
+
raise IndexError(f"Corrupted list structure at position {pos}")
|
|
444
|
+
|
|
445
|
+
_data = _cur.data
|
|
446
|
+
_prev.next = _cur.next
|
|
447
|
+
|
|
448
|
+
# if removing the last element, update last pointer
|
|
449
|
+
if _cur == self._last:
|
|
450
|
+
self._last = _prev
|
|
451
|
+
|
|
452
|
+
self._size -= 1
|
|
453
|
+
return _data
|
|
454
|
+
|
|
455
|
+
def compare(self, elem1: T, elem2: T) -> int:
|
|
456
|
+
"""*compare()* compares two elements using the comparison function defined in the *SingleLinkedList*.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
elem1 (T): first element to compare.
|
|
460
|
+
elem2 (T): second element to compare.
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
TypeError: error if the *cmp_function* is not defined.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
int: -1 if elem1 < elem2, 0 if elem1 == elem2, 1 if elem1 > elem2.
|
|
467
|
+
"""
|
|
468
|
+
if self.cmp_function is None:
|
|
469
|
+
# raise an exception if the cmp function is not defined
|
|
470
|
+
raise TypeError("Undefined compare function!!!")
|
|
471
|
+
# use the structure cmp function
|
|
472
|
+
return self.cmp_function(elem1, elem2)
|
|
473
|
+
|
|
474
|
+
def index_of(self, elm: T) -> int:
|
|
475
|
+
"""*index_of()* searches for the first occurrence of an element in the *SingleLinkedList*. If the element is found, it returns its index; otherwise, it returns -1.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
elm (T): element to search for in the *SingleLinkedList*.
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
int: index of the element in the *SingleLinkedList* or -1 if not found.
|
|
482
|
+
"""
|
|
483
|
+
if self.empty:
|
|
484
|
+
raise IndexError("Empty data structure")
|
|
485
|
+
|
|
486
|
+
_idx = -1
|
|
487
|
+
_node = self._first
|
|
488
|
+
found = False
|
|
489
|
+
i = 0
|
|
490
|
+
|
|
491
|
+
# iterate through the list to find the element
|
|
492
|
+
while not found and _node is not None and i < self.size:
|
|
493
|
+
# using the structure cmp function
|
|
494
|
+
if self.compare(elm, _node.data) == 0:
|
|
495
|
+
found = True
|
|
496
|
+
_idx = i
|
|
497
|
+
else:
|
|
498
|
+
_node = _node.next
|
|
499
|
+
i += 1
|
|
500
|
+
|
|
501
|
+
return _idx
|
|
502
|
+
|
|
503
|
+
def update(self, new_data: T, pos: int) -> None:
|
|
504
|
+
"""*update()* updates an element in the *SingleLinkedList* at a specific position.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
new_data (T): new data to be updated in the structure.
|
|
508
|
+
pos (int): position of the element to be updated.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
IndexError: error if the structure is empty.
|
|
512
|
+
IndexError: error if the position is invalid.
|
|
513
|
+
"""
|
|
514
|
+
if self.empty:
|
|
515
|
+
raise IndexError("Empty data structure")
|
|
516
|
+
elif pos < 0 or pos > self.size - 1:
|
|
517
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
518
|
+
# if the element type is valid, update the element
|
|
519
|
+
elif self._validate_type(new_data):
|
|
520
|
+
_cur = self._first
|
|
521
|
+
i = 0
|
|
522
|
+
while i != pos and _cur is not None:
|
|
523
|
+
_cur = _cur.next
|
|
524
|
+
i += 1
|
|
525
|
+
|
|
526
|
+
if _cur is None:
|
|
527
|
+
raise IndexError(f"Corrupted list structure at position {pos}")
|
|
528
|
+
|
|
529
|
+
_cur.data = new_data
|
|
530
|
+
|
|
531
|
+
def swap(self, pos1: int, pos2: int) -> None:
|
|
532
|
+
"""*swap()* swaps two elements in the *SingleLinkedList* at specific positions.
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
pos1 (int): position of the first element to swap.
|
|
537
|
+
pos2 (int): position of the second element to swap.
|
|
538
|
+
|
|
539
|
+
Raises:
|
|
540
|
+
IndexError: error if the structure is empty.
|
|
541
|
+
IndexError: error if the first position is invalid.
|
|
542
|
+
IndexError: error if the second position is invalid.
|
|
543
|
+
"""
|
|
544
|
+
if self.empty:
|
|
545
|
+
raise IndexError("Empty data structure")
|
|
546
|
+
elif pos1 < 0 or pos1 > self.size - 1:
|
|
547
|
+
raise IndexError("Index", pos1, "is out of range")
|
|
548
|
+
elif pos2 < 0 or pos2 > self.size - 1:
|
|
549
|
+
raise IndexError("Index", pos2, "is out of range")
|
|
550
|
+
info_pos1 = self.get(pos1)
|
|
551
|
+
info_pos2 = self.get(pos2)
|
|
552
|
+
self.update(info_pos2, pos1)
|
|
553
|
+
self.update(info_pos1, pos2)
|
|
554
|
+
|
|
555
|
+
def sublist(self, start: int, end: int) -> "SingleLinkedList[T]":
|
|
556
|
+
"""*sublist()* creates a new *SingleLinkedList* containing a sublist of elements from the original *SingleLinkedList*. The sublist is defined by the start and end indices.
|
|
557
|
+
|
|
558
|
+
NOTE: The start index is inclusive, and the end index is inclusive.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
start (int): start index of the sublist.
|
|
562
|
+
end (int): end index of the sublist.
|
|
563
|
+
|
|
564
|
+
Raises:
|
|
565
|
+
IndexError: error if the structure is empty.
|
|
566
|
+
IndexError: error if the start or end index are invalid.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
SingleLinkedList[T]: a new *SingleLinkedList* containing the sublist of elements.
|
|
570
|
+
"""
|
|
571
|
+
if self.empty:
|
|
572
|
+
raise IndexError("Empty data structure")
|
|
573
|
+
elif start < 0 or end > self.size - 1 or start > end:
|
|
574
|
+
raise IndexError(f"Invalid range: between [{start}, {end}]")
|
|
575
|
+
|
|
576
|
+
sub_lt = SingleLinkedList(cmp_function=self.cmp_function,
|
|
577
|
+
key=self.key)
|
|
578
|
+
i = 0
|
|
579
|
+
_cur = self._first
|
|
580
|
+
|
|
581
|
+
while i != end + 1 and _cur is not None:
|
|
582
|
+
if i >= start:
|
|
583
|
+
sub_lt.append(_cur.data)
|
|
584
|
+
_cur = _cur.next
|
|
585
|
+
i += 1
|
|
586
|
+
return sub_lt
|
|
587
|
+
|
|
588
|
+
def concat(self, other: "SingleLinkedList[T]") -> "SingleLinkedList[T]":
|
|
589
|
+
"""*concat()* concatenates two *SingleLinkedList* objects. The elements of the second list are added to the end of the first list.
|
|
590
|
+
|
|
591
|
+
NOTE: The *cmp_function* and *key* attributes of the two lists must be the same.
|
|
592
|
+
|
|
593
|
+
Args:
|
|
594
|
+
other (SingleLinkedList[T]): the second *SingleLinkedList* to be concatenated.
|
|
595
|
+
|
|
596
|
+
Raises:
|
|
597
|
+
TypeError: error if the *other* argument is not an *SingleLinkedList*.
|
|
598
|
+
TypeError: error if the *key* attributes are not the same.
|
|
599
|
+
TypeError: error if the *cmp_function* are not the same.
|
|
600
|
+
|
|
601
|
+
Returns:
|
|
602
|
+
SingleLinkedList[T]: the concatenated *SingleLinkedList* in the first list.
|
|
603
|
+
"""
|
|
604
|
+
if not isinstance(other, SingleLinkedList):
|
|
605
|
+
_msg = f"Structure is not an SingleLinkedList: {type(other)}"
|
|
606
|
+
raise TypeError(_msg)
|
|
607
|
+
if self.key != other.key:
|
|
608
|
+
raise TypeError(f"Invalid key: {self.key} != {other.key}")
|
|
609
|
+
# checking functional code of the cmp function
|
|
610
|
+
if self.cmp_function is not None and other.cmp_function is not None:
|
|
611
|
+
code1 = self.cmp_function.__code__.co_code
|
|
612
|
+
code2 = other.cmp_function.__code__.co_code
|
|
613
|
+
if code1 != code2:
|
|
614
|
+
_msg = f"Invalid compare function: {self.cmp_function}"
|
|
615
|
+
_msg += f" != {other.cmp_function}"
|
|
616
|
+
raise TypeError(_msg)
|
|
617
|
+
|
|
618
|
+
# Handle empty lists
|
|
619
|
+
if other.empty:
|
|
620
|
+
return self # Nothing to concatenate
|
|
621
|
+
|
|
622
|
+
# If self is empty, just copy other's strucure
|
|
623
|
+
if self.empty:
|
|
624
|
+
self._first = other._first
|
|
625
|
+
self._last = other._last
|
|
626
|
+
|
|
627
|
+
# concatenate the two lists
|
|
628
|
+
elif self._last is not None:
|
|
629
|
+
self._last.next = other._first
|
|
630
|
+
self._last = other._last
|
|
631
|
+
|
|
632
|
+
# update the size
|
|
633
|
+
self._size = self.size + other.size
|
|
634
|
+
return self
|
|
635
|
+
|
|
636
|
+
def clone(self) -> "SingleLinkedList[T]":
|
|
637
|
+
"""*clone()* creates a copy of the *SingleLinkedList*. The new list is independent of the original list.
|
|
638
|
+
|
|
639
|
+
NOTE: The elements of the new list are the same as the original list, but they are not references to the same objects.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
SingleLinkedList[T]: a new *SingleLinkedList* with the same elements as the original list.
|
|
643
|
+
"""
|
|
644
|
+
# create new list
|
|
645
|
+
copy_lt = SingleLinkedList(cmp_function=self.cmp_function,
|
|
646
|
+
key=self.key)
|
|
647
|
+
# get the first node of the original list
|
|
648
|
+
_cur = self._first
|
|
649
|
+
# traverse the list and add the elements to the new list
|
|
650
|
+
while _cur is not None:
|
|
651
|
+
copy_lt.append(_cur.data)
|
|
652
|
+
_cur = _cur.next
|
|
653
|
+
return copy_lt
|
|
654
|
+
|
|
655
|
+
def _error_handler(self, err: Exception) -> None:
|
|
656
|
+
"""*_error_handler()* to process the context (package/class), function name (method), and the error (exception) that was raised to format a detailed error message and traceback.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
err (Exception): Python raised exception.
|
|
660
|
+
"""
|
|
661
|
+
_context = self.__class__.__name__
|
|
662
|
+
_function_name = "unknown"
|
|
663
|
+
frame = inspect.currentframe()
|
|
664
|
+
if frame is not None:
|
|
665
|
+
if frame.f_back is not None:
|
|
666
|
+
_function_name = frame.f_back.f_code.co_name
|
|
667
|
+
else:
|
|
668
|
+
_function_name = "unknown"
|
|
669
|
+
error(_context, _function_name, err)
|
|
670
|
+
|
|
671
|
+
def _validate_type(self, elm: T) -> bool:
|
|
672
|
+
"""*_validate_type()* checks if the type of the element is valid. If the structure is empty, the type is valid. If the structure is not empty, the type must be the same as the first element in the list.
|
|
673
|
+
This is used to check the type of the element before adding it to the list.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
elm (T): element to be added to the structure.
|
|
677
|
+
|
|
678
|
+
Raises:
|
|
679
|
+
TypeError: error if the type of the element is not valid.
|
|
680
|
+
|
|
681
|
+
Returns:
|
|
682
|
+
bool: True if the type is valid, False otherwise.
|
|
683
|
+
"""
|
|
684
|
+
# if the structure is not empty, check the first element type
|
|
685
|
+
if not self.empty and self._first is not None:
|
|
686
|
+
# raise an exception if the type is not valid at the first element
|
|
687
|
+
if not isinstance(elm, type(self._first.data)):
|
|
688
|
+
_msg = f"Invalid data type: {type(elm)} "
|
|
689
|
+
_msg += f"!= {type(self._first.data)}"
|
|
690
|
+
raise TypeError(_msg)
|
|
691
|
+
# otherwise, any type is valid
|
|
692
|
+
return True
|
|
693
|
+
|
|
694
|
+
def __iter__(self) -> Iterator[T]:
|
|
695
|
+
"""*__iter__()* to iterate over the elements of the *SingleLinkedList*. This method returns an iterator object that can be used to iterate over the elements of the list.
|
|
696
|
+
|
|
697
|
+
NOTE: This is used to iterate over the nodes of the list using a for loop.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
Iterator[T]: an iterator object that can be used to iterate over the nodes of the list.
|
|
701
|
+
"""
|
|
702
|
+
try:
|
|
703
|
+
# TODO do I need the try/except block?
|
|
704
|
+
_cur = self._first
|
|
705
|
+
while _cur is not None:
|
|
706
|
+
yield _cur.data
|
|
707
|
+
_cur = _cur.next
|
|
708
|
+
except Exception as err:
|
|
709
|
+
self._error_handler(err)
|
|
710
|
+
|
|
711
|
+
def __len__(self) -> int:
|
|
712
|
+
"""*__len__()* to get the number of elements in the *SingleLinkedList*. This method returns the size of the list.
|
|
713
|
+
|
|
714
|
+
Returns:
|
|
715
|
+
int: the number of elements in the *SingleLinkedList*.
|
|
716
|
+
"""
|
|
717
|
+
return self._size
|
|
718
|
+
|
|
719
|
+
def __str__(self) -> str:
|
|
720
|
+
"""*__str__()* to get the string representation of the *SingleLinkedList*. This method returns a string with the elements of the list separated by commas.
|
|
721
|
+
|
|
722
|
+
Returns:
|
|
723
|
+
str: string representation of the *SingleLinkedList*.
|
|
724
|
+
"""
|
|
725
|
+
_attr_lt = []
|
|
726
|
+
for attr, value in vars(self).items():
|
|
727
|
+
# Skip private attributes starting with "__"
|
|
728
|
+
if attr.startswith("__"):
|
|
729
|
+
continue
|
|
730
|
+
# Format callable attributes
|
|
731
|
+
if callable(value):
|
|
732
|
+
value = f"{value.__name__}{inspect.signature(value)}"
|
|
733
|
+
# Format attribute name and value
|
|
734
|
+
_attr_name = attr.lstrip("_")
|
|
735
|
+
_attr_lt.append(f"{_attr_name}={repr(value)}")
|
|
736
|
+
# format the string with the SingleLinkedList class name and the attributes
|
|
737
|
+
_str = f"{self.__class__.__name__}({', '.join(_attr_lt)})"
|
|
738
|
+
return _str
|
|
739
|
+
|
|
740
|
+
def __repr__(self) -> str:
|
|
741
|
+
"""*__repr__()* get the string representation of the *SingleLinkedList*. This method returns a string representation,
|
|
742
|
+
|
|
743
|
+
Returns:
|
|
744
|
+
str: string representation of the *SingleLinkedList*.
|
|
745
|
+
"""
|
|
746
|
+
return self.__str__()
|