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,578 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module arlt.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module to represent the **ArrayList** data structure in *PyDASA*. Fundamental for the rest of the Dimensional Analysis and Data Science Library.
|
|
7
|
+
|
|
8
|
+
classes:
|
|
9
|
+
**ArrayList**: Implements a dynamic array data structure with customizable comparison and key functions.
|
|
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, field
|
|
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
|
+
# generic types and global variables
|
|
27
|
+
from pydasa.structs.types.generics import T
|
|
28
|
+
from pydasa.structs.types.generics import DFLT_DICT_KEY
|
|
29
|
+
from pydasa.structs.types.generics import VLD_IOTYPE_LT
|
|
30
|
+
from pydasa.structs.types.functions import dflt_cmp_function_lt
|
|
31
|
+
# generic error handling and type checking
|
|
32
|
+
from pydasa.validations.error import handle_error as error
|
|
33
|
+
|
|
34
|
+
# checking custom modules
|
|
35
|
+
assert T
|
|
36
|
+
assert DFLT_DICT_KEY
|
|
37
|
+
assert VLD_IOTYPE_LT
|
|
38
|
+
assert dflt_cmp_function_lt
|
|
39
|
+
assert error
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ArrayList(Generic[T]):
|
|
44
|
+
"""*ArrayList* implements a dynamic array data structure for PyDASA.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
Generic (T): Generic type for a Python data structure.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
ArrayList: a generic data structure of type *ArrayList* or Dynamic Array with the following attributes:
|
|
51
|
+
- `cmp_function`: Customizable comparison function for the elements in the *ArrayList*.
|
|
52
|
+
- `_elements`: Native Python list that contains the elements of the structure.
|
|
53
|
+
- `key`: Customizable key name for the elements in the *ArrayList*.
|
|
54
|
+
- `_size`: Number of elements in the structure.
|
|
55
|
+
- `iodata`: Customizable native Python list to initialize the structure.
|
|
56
|
+
"""
|
|
57
|
+
# the cmp_function is used to compare elements, not defined by default
|
|
58
|
+
# :attr: cmp_function
|
|
59
|
+
cmp_function: Optional[Callable[[T, T], int]] = None
|
|
60
|
+
"""
|
|
61
|
+
Customizable comparison function for *ArrayList* elements. Defaults to *dflt_cmp_function_lt()* from *PyDASA*, but can be overridden by the user.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# using default_factory to generate an empty list
|
|
65
|
+
# :attr: _elements
|
|
66
|
+
_elements: List[T] = field(default_factory=list)
|
|
67
|
+
"""
|
|
68
|
+
Native Python list storing the elements of the ArrayList.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
# the key is used to compare elements, not defined by default
|
|
72
|
+
# :attr: key
|
|
73
|
+
key: Optional[str] = DFLT_DICT_KEY
|
|
74
|
+
"""
|
|
75
|
+
Customizable key name for identifying elements in the *ArrayList*. Defaults to *DFLT_DICT_KEY = '_id'* from *PyDASA*, but can be overridden by the user.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
# by default, the list is empty
|
|
79
|
+
# :attr: _size
|
|
80
|
+
_size: int = 0
|
|
81
|
+
"""
|
|
82
|
+
Size of the *ArrayList*, starting at 0 and updated with each modification.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
# input elements from python list
|
|
86
|
+
# :attr: iodata
|
|
87
|
+
iodata: Optional[List[T]] = None
|
|
88
|
+
"""
|
|
89
|
+
Optional Python list for loading external data intho the *ArrayList*. Defaults to *None* but can be provided during creation.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __post_init__(self) -> None:
|
|
93
|
+
"""*__post_init__()* Initializes the *ArrayList* after creation by setting attributes like *cmp_function*, *key*, and *iodata*.
|
|
94
|
+
|
|
95
|
+
*NOTE:* Special method called automatically after object creation.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
# if the key is not defined, use the default
|
|
99
|
+
if self.key is None:
|
|
100
|
+
self.key = DFLT_DICT_KEY
|
|
101
|
+
|
|
102
|
+
# if the compare function is not defined, use the default
|
|
103
|
+
if self.cmp_function is None:
|
|
104
|
+
self.cmp_function = self.default_compare
|
|
105
|
+
|
|
106
|
+
# if elements are provided, add them to the ArrayList
|
|
107
|
+
if self.iodata is not None:
|
|
108
|
+
if not isinstance(self.iodata, VLD_IOTYPE_LT):
|
|
109
|
+
_msg = "iodata must be a valid iterable type, "
|
|
110
|
+
_msg += f"got {type(self.iodata)}"
|
|
111
|
+
raise TypeError(_msg)
|
|
112
|
+
|
|
113
|
+
for elm in self.iodata:
|
|
114
|
+
self.append(elm)
|
|
115
|
+
|
|
116
|
+
# Clear iodata after processing
|
|
117
|
+
self.iodata = None
|
|
118
|
+
|
|
119
|
+
except Exception as err:
|
|
120
|
+
self._error_handler(err)
|
|
121
|
+
|
|
122
|
+
def default_compare(self, elm1: Any, elm2: Any) -> int:
|
|
123
|
+
"""*default_compare()* Default comparison function for *ArrayList* elements. Compares two elements and returns:
|
|
124
|
+
- 0 if they are equal,
|
|
125
|
+
- 1 if the first is greater,
|
|
126
|
+
- -1 if the first is smaller.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
elm1 (Any): First element to compare.
|
|
130
|
+
elm2 (Any): Second element to compare.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
int: Comparison result.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
# default comparison needs the key to be defined
|
|
137
|
+
if self.key is None:
|
|
138
|
+
raise ValueError("Key must be set before comparison")
|
|
139
|
+
return dflt_cmp_function_lt(elm1, elm2, self.key)
|
|
140
|
+
except Exception as err:
|
|
141
|
+
self._error_handler(err)
|
|
142
|
+
raise # Re-raise the exception after handling
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def size(self) -> int:
|
|
146
|
+
"""*size()* Property to retrieve the number of elements in the *ArrayList*.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
int: number of elements in the *ArrayList*.
|
|
150
|
+
"""
|
|
151
|
+
return self._size
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def empty(self) -> bool:
|
|
155
|
+
"""*empty()* Property to check if the *ArrayList* is empty.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
bool: True if the *ArrayList* is empty, False otherwise.
|
|
159
|
+
"""
|
|
160
|
+
return self._size == 0
|
|
161
|
+
|
|
162
|
+
def clear(self) -> None:
|
|
163
|
+
"""*clear()* reset the *ArrayList* by removing all elements and resetting the size to 0.
|
|
164
|
+
|
|
165
|
+
NOTE: This method is used to empty the *ArrayList* without deleting the object itself.
|
|
166
|
+
"""
|
|
167
|
+
self._elements.clear()
|
|
168
|
+
self._size = 0
|
|
169
|
+
|
|
170
|
+
def prepend(self, elm: T) -> None:
|
|
171
|
+
"""*prepend()* adds an element to the beginning of the *ArrayList*.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
elm (T): element to be added to the beginning of the structure.
|
|
175
|
+
"""
|
|
176
|
+
# if the element type is valid, add it to the list
|
|
177
|
+
if self._validate_type(elm):
|
|
178
|
+
self._elements.insert(0, elm)
|
|
179
|
+
self._size += 1
|
|
180
|
+
|
|
181
|
+
def append(self, elm: T) -> None:
|
|
182
|
+
"""*append()* adds an element to the end of the *ArrayList*.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
elm (T): element to be added to the end of the structure.
|
|
186
|
+
"""
|
|
187
|
+
# if the element type is valid, add it to the list
|
|
188
|
+
if self._validate_type(elm):
|
|
189
|
+
self._elements.append(elm)
|
|
190
|
+
self._size += 1
|
|
191
|
+
|
|
192
|
+
def insert(self, elm: T, pos: int) -> None:
|
|
193
|
+
"""*insert()* adds an element to a specific position in the *ArrayList*.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
elm (T): element to be added to the structure.
|
|
197
|
+
pos (int): position where the element will be added.
|
|
198
|
+
|
|
199
|
+
Raises:
|
|
200
|
+
IndexError: error if the structure is empty.
|
|
201
|
+
IndexError: error if the position is invalid.
|
|
202
|
+
TypeError: error if the element type is invalid.
|
|
203
|
+
"""
|
|
204
|
+
if not self.empty and self._validate_type(elm):
|
|
205
|
+
if pos < 0 or pos > self.size:
|
|
206
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
207
|
+
self._elements.insert(pos, elm)
|
|
208
|
+
self._size += 1
|
|
209
|
+
else:
|
|
210
|
+
raise IndexError("Empty data structure")
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def first(self) -> T:
|
|
214
|
+
"""*first* Property to read the first element of the *ArrayList*.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
Exception: error if the structure is empty.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
T: the first element of the *ArrayList*.
|
|
221
|
+
"""
|
|
222
|
+
if self.empty:
|
|
223
|
+
raise IndexError("Empty data structure")
|
|
224
|
+
return self._elements[0]
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def last(self) -> T:
|
|
228
|
+
"""*last* Property to read the last element of the *ArrayList*.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
Exception: error if the structure is empty.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
T: the last element of the *ArrayList*.
|
|
235
|
+
"""
|
|
236
|
+
if self.empty:
|
|
237
|
+
raise IndexError("Empty data structure")
|
|
238
|
+
return self._elements[self.size - 1]
|
|
239
|
+
|
|
240
|
+
def get(self, pos: int) -> T:
|
|
241
|
+
"""*get()* reads an element from a specific position in the *ArrayList*.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
pos (int): position of the element to be read.
|
|
245
|
+
|
|
246
|
+
Raises:
|
|
247
|
+
IndexError: error if the structure is empty.
|
|
248
|
+
IndexError: error if the position is invalid.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
T: the element at the specified position in the *ArrayList*.
|
|
252
|
+
"""
|
|
253
|
+
if self.empty:
|
|
254
|
+
raise IndexError("Empty data structure")
|
|
255
|
+
elif pos < 0 or pos > self.size - 1:
|
|
256
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
257
|
+
return self._elements[pos]
|
|
258
|
+
|
|
259
|
+
def __getitem__(self, pos: int) -> T:
|
|
260
|
+
"""*__getitem__()* reads an element from a specific position in the *ArrayList*. Equivelent to *get()* method.
|
|
261
|
+
|
|
262
|
+
NOTE: This method is used to access the elements of the *ArrayList* using the square brackets notation.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
pos (int): position of the element to be read.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
IndexError: error if the structure is empty.
|
|
269
|
+
IndexError: error if the position is invalid.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
T: the element at the specified position in the *ArrayList*.
|
|
273
|
+
"""
|
|
274
|
+
return self.get(pos)
|
|
275
|
+
|
|
276
|
+
def pop_first(self) -> T:
|
|
277
|
+
"""*pop_first()* removes the first element from the *ArrayList*.
|
|
278
|
+
|
|
279
|
+
Raises:
|
|
280
|
+
IndexError: error if the structure is empty.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
T: the first element removed from the *ArrayList*.
|
|
284
|
+
"""
|
|
285
|
+
if self.empty:
|
|
286
|
+
raise IndexError("Empty data structure")
|
|
287
|
+
elm = self._elements.pop(0)
|
|
288
|
+
self._size -= 1
|
|
289
|
+
return elm
|
|
290
|
+
|
|
291
|
+
def pop_last(self) -> T:
|
|
292
|
+
"""*pop_last()* removes the last element from the *ArrayList*.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
IndexError: error if the structure is empty.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
T: the last element removed from the *ArrayList*.
|
|
299
|
+
"""
|
|
300
|
+
if self.empty:
|
|
301
|
+
raise IndexError("Empty data structure")
|
|
302
|
+
elm = self._elements.pop(self.size - 1)
|
|
303
|
+
self._size -= 1
|
|
304
|
+
return elm
|
|
305
|
+
|
|
306
|
+
def remove(self, pos: int) -> T:
|
|
307
|
+
"""*remove()* removes an element from a specific position in the *ArrayList*.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
pos (int): position of the element to be removed.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
IndexError: error if the structure is empty.
|
|
314
|
+
IndexError: error if the position is invalid.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
T: the element removed from the *ArrayList*.
|
|
318
|
+
"""
|
|
319
|
+
if self.empty:
|
|
320
|
+
raise IndexError("Empty data structure")
|
|
321
|
+
elif pos < 0 or pos > self.size - 1:
|
|
322
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
323
|
+
elm = self._elements.pop(pos)
|
|
324
|
+
self._size -= 1
|
|
325
|
+
return elm
|
|
326
|
+
|
|
327
|
+
def compare(self, elem1: T, elem2: T) -> int:
|
|
328
|
+
"""*compare()* compares two elements using the *cmp_function* defined in the *ArrayList*.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
elem1 (T): first element to compare.
|
|
332
|
+
elem2 (T): second element to compare.
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
TypeError: error if the *cmp_function* is not defined.
|
|
336
|
+
|
|
337
|
+
Returns:
|
|
338
|
+
int: -1 if elem1 < elem2, 0 if elem1 == elem2, 1 if elem1 > elem2.
|
|
339
|
+
"""
|
|
340
|
+
if self.cmp_function is None:
|
|
341
|
+
# raise an exception if the cmp function is not defined
|
|
342
|
+
raise TypeError("Undefined compare function!!!")
|
|
343
|
+
# use the structure cmp function
|
|
344
|
+
return self.cmp_function(elem1, elem2)
|
|
345
|
+
|
|
346
|
+
def index_of(self, elm: T) -> int:
|
|
347
|
+
"""*index_of()* searches for the first occurrence of an element in the *ArrayList*. If the element is found, it returns its index; otherwise, it returns -1.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
elm (T): element to search for in the *ArrayList*.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
int: index of the element in the *ArrayList* or -1 if not found.
|
|
354
|
+
"""
|
|
355
|
+
if self.empty:
|
|
356
|
+
raise IndexError("Empty data structure")
|
|
357
|
+
_idx = -1
|
|
358
|
+
found = False
|
|
359
|
+
i = 0
|
|
360
|
+
while not found and i < self.size:
|
|
361
|
+
_telm = self._elements[i]
|
|
362
|
+
# using the structure cmp function
|
|
363
|
+
if self.compare(elm, _telm) == 0:
|
|
364
|
+
found = True
|
|
365
|
+
_idx = i
|
|
366
|
+
i += 1
|
|
367
|
+
return _idx
|
|
368
|
+
|
|
369
|
+
def update(self, new_data: T, pos: int) -> None:
|
|
370
|
+
"""*update()* updates an element in the *ArrayList* at a specific position.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
new_data (T): new data to be updated in the *ArrayList*.
|
|
374
|
+
pos (int): position of the element to be updated.
|
|
375
|
+
|
|
376
|
+
Raises:
|
|
377
|
+
IndexError: error if the structure is empty.
|
|
378
|
+
IndexError: error if the position is invalid.
|
|
379
|
+
"""
|
|
380
|
+
if self.empty:
|
|
381
|
+
raise IndexError("Empty data structure")
|
|
382
|
+
elif pos < 0 or pos > self.size - 1:
|
|
383
|
+
raise IndexError(f"Index {pos} is out of range")
|
|
384
|
+
# if not self._validate_type(new_data):
|
|
385
|
+
elif self._validate_type(new_data):
|
|
386
|
+
self._elements[pos] = new_data
|
|
387
|
+
|
|
388
|
+
def swap(self, pos1: int, pos2: int) -> None:
|
|
389
|
+
"""*swap()* swaps two elements in the *ArrayList* at specified positions.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
pos1 (int): position of the first element to swap.
|
|
393
|
+
pos2 (int): position of the second element to swap.
|
|
394
|
+
|
|
395
|
+
Raises:
|
|
396
|
+
IndexError: error if the structure is empty.
|
|
397
|
+
IndexError: error if the first position is invalid.
|
|
398
|
+
IndexError: error if the second position is invalid.
|
|
399
|
+
"""
|
|
400
|
+
if self.empty:
|
|
401
|
+
raise IndexError("Empty data structure")
|
|
402
|
+
elif pos1 < 0 or pos1 > self.size - 1:
|
|
403
|
+
raise IndexError(f"Index {pos1} is out of range")
|
|
404
|
+
elif pos2 < 0 or pos2 > self.size - 1:
|
|
405
|
+
raise IndexError(f"Index {pos2} is out of range")
|
|
406
|
+
_temp1 = self._elements[pos1]
|
|
407
|
+
_temp2 = self._elements[pos2]
|
|
408
|
+
self._elements[pos2] = _temp1
|
|
409
|
+
self._elements[pos1] = _temp2
|
|
410
|
+
|
|
411
|
+
def sublist(self, start: int, end: int) -> "ArrayList[T]":
|
|
412
|
+
"""*sublist()* creates a new *ArrayList* containing a sublist of elements from the original *ArrayList*. The sublist is defined by the start and end indices.
|
|
413
|
+
|
|
414
|
+
NOTE: The start index is inclusive, and the end index is inclusive.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
start (int): start index of the sublist.
|
|
418
|
+
end (int): end index of the sublist.
|
|
419
|
+
|
|
420
|
+
Raises:
|
|
421
|
+
IndexError: error if the structure is empty.
|
|
422
|
+
IndexError: error if the start or end index are invalid.
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
ArrayList[T]: a new *ArrayList* containing the sublist of elements.
|
|
426
|
+
"""
|
|
427
|
+
if self.empty:
|
|
428
|
+
raise IndexError("Empty data structure")
|
|
429
|
+
elif start < 0 or end > self.size - 1 or start > end:
|
|
430
|
+
raise IndexError(f"Invalid range: between [{start}, {end}]")
|
|
431
|
+
sub_lt = ArrayList(cmp_function=self.cmp_function,
|
|
432
|
+
key=self.key)
|
|
433
|
+
# add the elements of the sublist to the new list
|
|
434
|
+
sub_lt._elements = self._elements[start:end + 1]
|
|
435
|
+
sub_lt._size = end - start + 1
|
|
436
|
+
return sub_lt
|
|
437
|
+
|
|
438
|
+
def concat(self, other: "ArrayList[T]") -> "ArrayList[T]":
|
|
439
|
+
"""*concat()* concatenates two *ArrayList* objects. The elements of the second list are added to the end of the first list.
|
|
440
|
+
|
|
441
|
+
NOTE: The *cmp_function* and *key* attributes of the two lists must be the same.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
other (ArrayList[T]): the second *ArrayList* to be concatenated.
|
|
445
|
+
|
|
446
|
+
Raises:
|
|
447
|
+
TypeError: error if the *other* argument is not an *ArrayList*.
|
|
448
|
+
TypeError: error if the *key* attributes are not the same.
|
|
449
|
+
TypeError: error if the *cmp_function* are not the same.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
ArrayList[T]: the concatenated *ArrayList* in the first list.
|
|
453
|
+
"""
|
|
454
|
+
if not isinstance(other, ArrayList):
|
|
455
|
+
_msg = f"Structure is not an ArrayList: {type(other)}"
|
|
456
|
+
raise TypeError(_msg)
|
|
457
|
+
if self.key != other.key:
|
|
458
|
+
raise TypeError(f"Invalid key: {self.key} != {other.key}")
|
|
459
|
+
# checking functional code of the cmp function
|
|
460
|
+
if self.cmp_function is not None and other.cmp_function is not None:
|
|
461
|
+
code1 = self.cmp_function.__code__.co_code
|
|
462
|
+
code2 = other.cmp_function.__code__.co_code
|
|
463
|
+
if code1 != code2:
|
|
464
|
+
_msg = f"Invalid compare function: {self.cmp_function}"
|
|
465
|
+
_msg += f" != {other.cmp_function}"
|
|
466
|
+
raise TypeError(_msg)
|
|
467
|
+
|
|
468
|
+
# concatenate the elements of the two lists
|
|
469
|
+
self._elements = self._elements + other._elements
|
|
470
|
+
# update the size of the new list
|
|
471
|
+
self._size = self.size + other.size
|
|
472
|
+
return self
|
|
473
|
+
|
|
474
|
+
def clone(self) -> "ArrayList[T]":
|
|
475
|
+
"""*clone()* creates a new structure with the copy of the *ArrayList*. The new list is independent of the original list.
|
|
476
|
+
|
|
477
|
+
NOTE: we named the method *clone()* instead of *copy()* to avoid confusion with the native Python *copy()* method.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
ArrayList[T]: a new *ArrayList* with the same elements as the original list.
|
|
481
|
+
"""
|
|
482
|
+
# create a new list
|
|
483
|
+
copy_lt = ArrayList(cmp_function=self.cmp_function,
|
|
484
|
+
key=self.key)
|
|
485
|
+
# add all the elements of the current list
|
|
486
|
+
for elm in self._elements:
|
|
487
|
+
copy_lt.append(elm)
|
|
488
|
+
return copy_lt
|
|
489
|
+
|
|
490
|
+
def _error_handler(self, err: Exception) -> None:
|
|
491
|
+
"""*_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.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
err (Exception): Python raised exception.
|
|
495
|
+
"""
|
|
496
|
+
_context = self.__class__.__name__
|
|
497
|
+
_function_name = "unknown"
|
|
498
|
+
frame = inspect.currentframe()
|
|
499
|
+
if frame is not None:
|
|
500
|
+
if frame.f_back is not None:
|
|
501
|
+
_function_name = frame.f_back.f_code.co_name
|
|
502
|
+
else:
|
|
503
|
+
_function_name = "unknown"
|
|
504
|
+
error(_context, _function_name, err)
|
|
505
|
+
|
|
506
|
+
def _validate_type(self, elm: T) -> bool:
|
|
507
|
+
"""*_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.
|
|
508
|
+
This is used to check the type of the element before adding it to the list.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
elm (T): element to be added to the structure.
|
|
512
|
+
|
|
513
|
+
Raises:
|
|
514
|
+
TypeError: error if the type of the element is not valid.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
bool: True if the type is valid, False otherwise.
|
|
518
|
+
"""
|
|
519
|
+
# if the structure is not empty, check the first element type
|
|
520
|
+
if not self.empty:
|
|
521
|
+
# raise an exception if the type is not valid
|
|
522
|
+
if not isinstance(elm, type(self._elements[0])):
|
|
523
|
+
_msg = f"Invalid data type: {type(elm)} "
|
|
524
|
+
_msg += f"!= {type(self._elements[0])}"
|
|
525
|
+
raise TypeError(_msg)
|
|
526
|
+
# otherwise, the type is valid
|
|
527
|
+
return True
|
|
528
|
+
|
|
529
|
+
def __iter__(self) -> Iterator[T]:
|
|
530
|
+
"""*__iter__()* to iterate over the elements of the *ArrayList*. This method returns an iterator object that can be used to iterate over the elements of the list.
|
|
531
|
+
|
|
532
|
+
NOTE: This is used to iterate over the elements of the list using a for loop.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Iterator[T]: an iterator object that can be used to iterate over the elements of the list.
|
|
536
|
+
"""
|
|
537
|
+
try:
|
|
538
|
+
return iter(self._elements)
|
|
539
|
+
except Exception as err:
|
|
540
|
+
self._error_handler(err)
|
|
541
|
+
raise # Re-raise the exception after handling
|
|
542
|
+
|
|
543
|
+
def __len__(self) -> int:
|
|
544
|
+
"""*__len__()* to get the number of elements in the *ArrayList*. This method returns the size of the list.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
int: the number of elements in the *ArrayList*.
|
|
548
|
+
"""
|
|
549
|
+
return self._size
|
|
550
|
+
|
|
551
|
+
def __str__(self) -> str:
|
|
552
|
+
"""*__str__()* get the string representation of the *ArrayList*. This method returns a string with the elements of the list.
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
str: the string representation of the *ArrayList*.
|
|
556
|
+
"""
|
|
557
|
+
_attr_lt = []
|
|
558
|
+
for attr, value in vars(self).items():
|
|
559
|
+
# Skip private attributes starting with "__"
|
|
560
|
+
if attr.startswith("__"):
|
|
561
|
+
continue
|
|
562
|
+
# Format callable attributes
|
|
563
|
+
if callable(value):
|
|
564
|
+
value = f"{value.__name__}{inspect.signature(value)}"
|
|
565
|
+
# Format attribute name and value
|
|
566
|
+
_attr_name = attr.lstrip("_")
|
|
567
|
+
_attr_lt.append(f"{_attr_name}={repr(value)}")
|
|
568
|
+
# Format the string representation of the ArrayList class and its attributes
|
|
569
|
+
_str = f"{self.__class__.__name__}({', '.join(_attr_lt)})"
|
|
570
|
+
return _str
|
|
571
|
+
|
|
572
|
+
def __repr__(self) -> str:
|
|
573
|
+
"""*__repr__()* get the string representation of the *ArrayList*. This method returns a string representation.
|
|
574
|
+
|
|
575
|
+
Returns:
|
|
576
|
+
str: the string representation of the *ArrayList*.
|
|
577
|
+
"""
|
|
578
|
+
return self.__str__()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Module dllt.py
|
|
4
|
+
===========================================
|
|
5
|
+
|
|
6
|
+
Module to represent the **Doubly Linked List** data structure in *PyDASA*.
|
|
7
|
+
|
|
8
|
+
This module provides the **DoubleLinkedList** class which implements a doubly linked list with methods for insertion, deletion, and traversal.
|
|
9
|
+
|
|
10
|
+
classes:
|
|
11
|
+
**DoubleLinkedList**: Implements a doubly linked list with methods for insertion, deletion, and traversal.
|
|
12
|
+
|
|
13
|
+
*IMPORTANT:* based on the implementations proposed by the following authors/books:
|
|
14
|
+
|
|
15
|
+
#. Algorithms, 4th Edition, Robert Sedgewick and Kevin Wayne.
|
|
16
|
+
#. Data Structure and Algorithms in Python, M.T. Goodrich, R. Tamassia, M.H. Goldwasser.
|
|
17
|
+
"""
|
|
18
|
+
# TODO complete the DoubleLinkedList implementation
|