dtools.datastructures 0.25.0__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.
@@ -0,0 +1,31 @@
1
+ # Copyright 2023-2024 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ ### package datastructures
17
+
18
+ Designed to be helpful when using and implementation algorithms.
19
+
20
+ #### Modules and sub-packages
21
+
22
+ * module dtools.datastructures.nodes: nodes for graph-like data structures
23
+ * package dtools.datastructures.splitends: data sharing between mutable objects
24
+ * module dtools.datastructures.tuples: tuple-like data structures
25
+ * module dtools.datastructures.queues: queue data structures
26
+
27
+ """
28
+ __version__ = "0.25.0"
29
+ __author__ = "Geoffrey R. Scheller"
30
+ __copyright__ = "Copyright (c) 2023-2024 Geoffrey R. Scheller"
31
+ __license__ = "Apache License 2.0"
@@ -0,0 +1,189 @@
1
+ # Copyright 2023-2024 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ ### Nodes for Graphs
17
+
18
+ Node classes used with graph-like data structures. API designed to be used by
19
+ other data structures which contain these data structures.
20
+
21
+ #### Node types
22
+
23
+ * class **SL_Node:**
24
+ * class **DL_Node:**
25
+ * class **Tree_Node:**
26
+
27
+ """
28
+ from __future__ import annotations
29
+ from collections.abc import Callable, Iterator
30
+ from typing import Callable, cast, Iterator
31
+ from dtools.fp.err_handling import MB
32
+
33
+ __all__ = ['SL_Node', 'DL_Node', 'Tree_Node']
34
+
35
+ class SL_Node[D]():
36
+ """Data node for rearward Pointing (tip-to-root) singularly linked graphs.
37
+
38
+ * for mutable and immutable linear data structures
39
+ * designed so multiple instances can safely share the same data
40
+ * this type of node always contain data and optionally a previous Node
41
+ * nodes point towards a unique "root node" with no predecessor
42
+ * in a Boolean context return false only if only at a root
43
+ * multiple root nodes can exist
44
+ * empty data structures can be "re-rooted"
45
+ * two nodes compare as equal if
46
+ * both their previous Nodes are the same
47
+ * their data compares as equal
48
+ * more than one node can point to the same node forming bush like graphs
49
+
50
+ """
51
+ __slots__ = '_data', '_prev'
52
+
53
+ def __init__(self, data: D, prev: MB[SL_Node[D]]) -> None:
54
+ self._data = data
55
+ self._prev = prev
56
+
57
+ def __iter__(self) -> Iterator[D]:
58
+ node = self
59
+ while node:
60
+ yield node._data
61
+ node = node._prev.get()
62
+ yield node._data
63
+
64
+ def __bool__(self) -> bool:
65
+ return self._prev != MB()
66
+
67
+ def data_eq(self, other: SL_Node[D]) -> bool:
68
+ if self._data is other._data:
69
+ return True
70
+ elif self._data == other._data:
71
+ return True
72
+ else:
73
+ return False
74
+
75
+ def __eq__(self, other: object) -> bool:
76
+ if not isinstance(other, type(self)):
77
+ return False
78
+
79
+ if self._prev is not other._prev:
80
+ return False
81
+ else:
82
+ return self.data_eq(other)
83
+
84
+ def get_data(self) -> D:
85
+ return self._data
86
+
87
+ def fold[T](self, f: Callable[[T, D], T], init: T|None=None) -> T:
88
+ """Reduce data across linked nodes.
89
+
90
+ * with a function and an optional starting value
91
+ * reduces in natural LIFO order
92
+ * from self to the root
93
+
94
+ """
95
+ if init is None:
96
+ acc: T = cast(T, self._data)
97
+ node = self._prev.get()
98
+ else:
99
+ acc = init
100
+ node = self
101
+
102
+ while node:
103
+ acc = f(acc, node._data)
104
+ node = node._prev.get()
105
+ acc = f(acc, node._data)
106
+ return acc
107
+
108
+ def pop2(self) -> tuple[D, MB[SL_Node[D]]]:
109
+ """Return the *head* and, if it exists, the top node of the *tail*."""
110
+ return self._data, self._prev
111
+
112
+ def push_data(self, data: D) -> SL_Node[D]:
113
+ """Push data onto the stack and return a new node containing the data."""
114
+ return SL_Node(data, MB(self))
115
+
116
+ class DL_Node[D]():
117
+ """Doubly Linked Node.
118
+
119
+ Doubly linked nodes for graph-like data structures.
120
+
121
+ * this type of node always contain data, even if that data is None
122
+ * in a Boolean context return true if both left and right nodes exist
123
+ * doubly link lists possible
124
+ * circular graphs are possible
125
+ * simple recursive binary trees possible
126
+
127
+ """
128
+ __slots__ = '_left', '_data', '_right'
129
+
130
+ def __init__(self, left: MB[DL_Node[D]], data: D, right: MB[DL_Node[D]]):
131
+ self._left = left
132
+ self._data = data
133
+ self._right = right
134
+
135
+ def __bool__(self) -> bool:
136
+ if self._left == MB() or self._right == MB():
137
+ return False
138
+ return True
139
+
140
+ def __eq__(self, other: object) -> bool:
141
+ if not isinstance(other, type(self)):
142
+ return False
143
+
144
+ if self._left is not other._left:
145
+ return False
146
+ if self._right is not other._right:
147
+ return False
148
+ if self._data is other._data:
149
+ return True
150
+ elif self._data == other._data:
151
+ return True
152
+
153
+ return False
154
+
155
+ def has_left(self) -> bool:
156
+ return self._left != MB()
157
+
158
+ def has_right(self) -> bool:
159
+ return self._right != MB()
160
+
161
+ class Tree_Node[D, M]():
162
+ """Binary Tree Node with metadata.
163
+
164
+ Nodes useful for binary trees.
165
+
166
+ * this type of node always contain data, even if that data is None
167
+ * in a Boolean context return true if not at the top of the tree
168
+ * potential uses of metadata can be for re-balancing or repeat counts
169
+ """
170
+ __slots__ = '_data', '_left', '_right', '_up'
171
+
172
+ def __init__(self, data: D,
173
+ up: MB[Tree_Node[D,M]],
174
+ left: MB[Tree_Node[D,M]],
175
+ right: MB[Tree_Node[D,M]],
176
+ meta: tuple[M, ...] = ()):
177
+ self._data = data
178
+ self._up = up
179
+ self._left = left
180
+ self._right = right
181
+
182
+ def __bool__(self) -> bool:
183
+ if self._up == MB():
184
+ return False
185
+ else:
186
+ return True
187
+
188
+ def is_top(self) -> bool:
189
+ return self._up == MB()
File without changes
@@ -0,0 +1,420 @@
1
+ # Copyright 2023-2024 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """### Queue based data structures
16
+
17
+ * stateful queue data structures with amortized O(1) pushes and pops each end
18
+ * obtaining length (number of elements) of a queue is an O(1) operation
19
+ * implemented in a "has-a" relationship with a Python list based circular array
20
+ * these data structures will resize themselves larger as needed
21
+
22
+ #### FIFOQueue
23
+
24
+ * class **FIFOQueue:** First-In-First-Out Queue
25
+ * function **FQ:** Constructs a FIFOQueue from a variable number of arguments
26
+
27
+ ---
28
+
29
+ #### LIFOQueue
30
+
31
+ * class **LIFOQueue:** Last-In-First-Out Queue
32
+ * function **LQ:** Constructs a LIFOQueue from a variable number of arguments
33
+
34
+ ---
35
+
36
+ #### DoubleQueue
37
+
38
+ * class **DoubleQueue:** Double-Ended Queue
39
+ * function **DQ:** Constructs a DoubleQueue from a variable number of arguments
40
+
41
+ """
42
+ from __future__ import annotations
43
+
44
+ from collections.abc import Callable, Iterable, Iterator, Sequence
45
+ from typing import Never, overload
46
+ from dtools.circular_array.ca import ca, CA
47
+ from dtools.fp.err_handling import MB
48
+
49
+ __all__ = [ 'DoubleQueue', 'FIFOQueue', 'LIFOQueue', 'QueueBase',
50
+ 'DQ', 'FQ', 'LQ' ]
51
+
52
+ class QueueBase[D](Sequence[D]):
53
+ """Base class for circular area based queues.
54
+
55
+ * implemented with a dtools.circular-array in a "has-a" relationship
56
+ * order of initial data retained
57
+ * slicing not yet implemented
58
+
59
+ """
60
+ __slots__ = '_ca'
61
+
62
+ def __init__(self, *dss: Iterable[D]) -> None:
63
+ if len(dss) < 2:
64
+ self._ca = ca(*dss)
65
+ else:
66
+ msg1 = f'{type(self).__name__}: expected at most 1 '
67
+ msg2 = f'iterable argument, got {len(dss)}.'
68
+ raise TypeError(msg1+msg2)
69
+
70
+ def __bool__(self) -> bool:
71
+ return len(self._ca) > 0
72
+
73
+ def __len__(self) -> int:
74
+ return len(self._ca)
75
+
76
+ def __eq__(self, other: object, /) -> bool:
77
+ if not isinstance(other, type(self)):
78
+ return False
79
+ return self._ca == other._ca
80
+
81
+ @overload
82
+ def __getitem__(self, idx: int, /) -> D: ...
83
+ @overload
84
+ def __getitem__(self, idx: slice, /) -> Sequence[D]: ...
85
+
86
+ def __getitem__(self, idx: int|slice, /) -> D|Sequence[D]|Never:
87
+ if isinstance(idx, slice):
88
+ raise NotImplementedError
89
+ return self._ca[idx]
90
+
91
+ class FIFOQueue[D](QueueBase[D]):
92
+ """FIFO Queue
93
+
94
+ * stateful First-In-First-Out (FIFO) data structure
95
+ * initial data pushed on in natural FIFO order
96
+
97
+ """
98
+ __slots__ = ()
99
+
100
+ def __iter__(self) -> Iterator[D]:
101
+ return iter(list(self._ca))
102
+
103
+ def __repr__(self) -> str:
104
+ if len(self) == 0:
105
+ return 'FQ()'
106
+ else:
107
+ return 'FQ(' + ', '.join(map(repr, self._ca)) + ')'
108
+
109
+ def __str__(self) -> str:
110
+ return "<< " + " < ".join(map(str, self)) + " <<"
111
+
112
+ def copy(self) -> FIFOQueue[D]:
113
+ """Return a shallow copy of the `FIFOQueue`."""
114
+ return FIFOQueue(self._ca)
115
+
116
+ def push(self, *ds: D) -> None:
117
+ """Push data onto `FIFOQueue`.
118
+
119
+ * like a Python List, does not return a value
120
+
121
+ """
122
+ self._ca.pushR(*ds)
123
+
124
+ def pop(self) -> MB[D]:
125
+ """Pop data from `FIFOQueue`.
126
+
127
+ * pop item off queue, return item in a maybe monad
128
+ * returns an empty `MB()` if queue is empty
129
+
130
+ """
131
+ if self._ca:
132
+ return MB(self._ca.popL())
133
+ else:
134
+ return MB()
135
+
136
+ def peak_last_in(self) -> MB[D]:
137
+ """Peak last data into `FIFOQueue`.
138
+
139
+ * return a maybe monad of the last item pushed to queue
140
+ * does not consume the data
141
+ * if item already popped, return `MB()`
142
+
143
+ """
144
+ if self._ca:
145
+ return MB(self._ca[-1])
146
+ else:
147
+ return MB()
148
+
149
+ def peak_next_out(self) -> MB[D]:
150
+ """Peak next data out of `FIFOQueue`.
151
+
152
+ * returns a maybe monad of the next item to be popped from the queue.
153
+ * does not consume it the item
154
+ * returns `MB()` if queue is empty
155
+
156
+ """
157
+ if self._ca:
158
+ return MB(self._ca[0])
159
+ else:
160
+ return MB()
161
+
162
+ def fold[L](self, f: Callable[[L, D], L], initial: L|None=None, /) -> MB[L]:
163
+ """Fold `FIFOQueue` in natural order.
164
+
165
+ Reduce with `f` using an optional initial value.
166
+
167
+ * folds in natural FIFO Order (oldest to newest)
168
+ * note that when an initial value is not given then `~L = ~D`
169
+ * if iterable empty & no initial value given, return `MB()`
170
+ * traditional FP type order given for function `f`
171
+
172
+ """
173
+ if initial is None:
174
+ if not self._ca:
175
+ return MB()
176
+ return MB(self._ca.foldL(f, initial=initial))
177
+
178
+ def map[U](self, f: Callable[[D], U], /) -> FIFOQueue[U]:
179
+ """Map over the `FIFOQueue`.
180
+
181
+ * map function `f` over the queue
182
+ * oldest to newest
183
+ * retain original order
184
+ * returns a new instance
185
+
186
+ """
187
+ return FIFOQueue(map(f, self._ca))
188
+
189
+ class LIFOQueue[D](QueueBase[D]):
190
+ """LIFO Queue.
191
+
192
+ * stateful Last-In-First-Out (LIFO) data structure
193
+ * initial data pushed on in natural LIFO order
194
+
195
+ """
196
+ __slots__ = ()
197
+
198
+ def __iter__(self) -> Iterator[D]:
199
+ return reversed(list(self._ca))
200
+
201
+ def __repr__(self) -> str:
202
+ if len(self) == 0:
203
+ return 'LQ()'
204
+ else:
205
+ return 'LQ(' + ', '.join(map(repr, self._ca)) + ')'
206
+
207
+ def __str__(self) -> str:
208
+ return "|| " + " > ".join(map(str, self)) + " ><"
209
+
210
+ def copy(self) -> LIFOQueue[D]:
211
+ """Return a shallow copy of the `LIFOQueue`."""
212
+ return LIFOQueue(reversed(self._ca))
213
+
214
+ def push(self, *ds: D) -> None:
215
+ """Push data onto `LIFOQueue`.
216
+
217
+ * like a Python List, does not return a value
218
+
219
+ """
220
+ self._ca.pushR(*ds)
221
+
222
+ def pop(self) -> MB[D]:
223
+ """Pop data from `LIFOQueue`.
224
+
225
+ * pop item off of queue, return item in a maybe monad
226
+ * returns an empty `MB()` if queue is empty
227
+
228
+ """
229
+ if self._ca:
230
+ return MB(self._ca.popR())
231
+ else:
232
+ return MB()
233
+
234
+ def peak(self) -> MB[D]:
235
+ """Peak next data out of `LIFOQueue`.
236
+
237
+ * return a maybe monad of the next item to be popped from the queue
238
+ * does not consume the item
239
+ * returns `MB()` if queue is empty
240
+
241
+ """
242
+ if self._ca:
243
+ return MB(self._ca[-1])
244
+ else:
245
+ return MB()
246
+
247
+ def fold[R](self, f: Callable[[D, R], R], initial: R|None=None, /) -> MB[R]:
248
+ """Fold `LIFOQueue` in natural order.
249
+
250
+ Reduce with `f` using an optional initial value.
251
+
252
+ * folds in natural LIFO Order (newest to oldest)
253
+ * note that when an initial value is not given then `~R = ~D`
254
+ * if iterable empty & no initial value given, return `MB()`
255
+ * traditional FP type order given for function `f`
256
+
257
+ """
258
+ if initial is None:
259
+ if not self._ca:
260
+ return MB()
261
+ return MB(self._ca.foldR(f, initial=initial))
262
+
263
+ def map[U](self, f: Callable[[D], U], /) -> LIFOQueue[U]:
264
+ """Map Over the `LIFOQueue`.
265
+
266
+ * map the function `f` over the queue
267
+ * newest to oldest
268
+ * retain original order
269
+ * returns a new instance
270
+
271
+ """
272
+ return LIFOQueue(reversed(CA(*map(f, reversed(self._ca)))))
273
+
274
+ class DoubleQueue[D](QueueBase[D]):
275
+ """Double Ended Queue
276
+
277
+ * stateful Double-Ended (DEQueue) data structure
278
+ * order of initial data retained
279
+
280
+ """
281
+ __slots__ = ()
282
+
283
+ def __iter__(self) -> Iterator[D]:
284
+ return iter(list(self._ca))
285
+
286
+ def __reversed__(self) -> Iterator[D]:
287
+ return reversed(list(self._ca))
288
+
289
+ def __repr__(self) -> str:
290
+ if len(self) == 0:
291
+ return 'DQ()'
292
+ else:
293
+ return 'DQ(' + ', '.join(map(repr, self._ca)) + ')'
294
+
295
+ def __str__(self) -> str:
296
+ return ">< " + " | ".join(map(str, self)) + " ><"
297
+
298
+ def copy(self) -> DoubleQueue[D]:
299
+ """Return a shallow copy of the `DoubleQueue`."""
300
+ return DoubleQueue(self._ca)
301
+
302
+ def pushL(self, *ds: D) -> None:
303
+ """Push data onto left side (front) of `DoubleQueue`.
304
+
305
+ * like a Python List, does not return a value
306
+
307
+ """
308
+ self._ca.pushL(*ds)
309
+
310
+ def pushR(self, *ds: D) -> None:
311
+ """Push data onto right side (rear) of `DoubleQueue`.
312
+
313
+ * like a Python List, does not return a value
314
+
315
+ """
316
+ self._ca.pushR(*ds)
317
+
318
+ def popL(self) -> MB[D]:
319
+ """Pop Data from left side (front) of `DoubleQueue`.
320
+
321
+ * pop left most item off of queue, return item in a maybe monad
322
+ * returns an empty `MB()` if queue is empty
323
+
324
+ """
325
+ if self._ca:
326
+ return MB(self._ca.popL())
327
+ else:
328
+ return MB()
329
+
330
+ def popR(self) -> MB[D]:
331
+ """Pop Data from right side (rear) of `DoubleQueue`.
332
+
333
+ * pop right most item off of queue, return item in a maybe monad
334
+ * returns an empty `MB()` if queue is empty
335
+
336
+ """
337
+ if self._ca:
338
+ return MB(self._ca.popR())
339
+ else:
340
+ return MB()
341
+
342
+ def peakL(self) -> MB[D]:
343
+ """Peak left side of `DoubleQueue`.
344
+
345
+ * return left most value in a maybe monad
346
+ * does not consume the item
347
+ * returns an empty `MB()` if queue is empty
348
+
349
+ """
350
+ if self._ca:
351
+ return MB(self._ca[0])
352
+ else:
353
+ return MB()
354
+
355
+ def peakR(self) -> MB[D]:
356
+ """Peak right side of `DoubleQueue`.
357
+
358
+ * return right most value in a maybe monad
359
+ * does not consume the item
360
+ * returns an empty `MB()` if queue is empty
361
+
362
+ """
363
+ if self._ca:
364
+ return MB(self._ca[-1])
365
+ else:
366
+ return MB()
367
+
368
+ def foldL[L](self, f: Callable[[L, D], L], initial: L|None=None, /) -> MB[L]:
369
+ """Fold `DoubleQueue` left to right.
370
+
371
+ Reduce left with `f` using an optional initial value.
372
+
373
+ * note that when an initial value is not given then `~L = ~D`
374
+ * if iterable empty & no initial value given, return `MB()`
375
+ * traditional FP type order given for function `f`
376
+
377
+ """
378
+ if initial is None:
379
+ if not self._ca:
380
+ return MB()
381
+ return MB(self._ca.foldL(f, initial=initial))
382
+
383
+ def foldR[R](self, f: Callable[[D, R], R], initial: R|None=None, /) -> MB[R]:
384
+ """Fold `DoubleQueue` right to left.
385
+
386
+ Reduce right with `f` using an optional initial value.
387
+
388
+ * note that when an initial value is not given then `~R = ~D`
389
+ * if iterable empty & no initial value given, return `MB()`
390
+ * traditional FP type order given for function `f`
391
+
392
+ """
393
+ if initial is None:
394
+ if not self._ca:
395
+ return MB()
396
+ return MB(self._ca.foldR(f, initial=initial))
397
+
398
+ def map[U](self, f: Callable[[D], U], /) -> DoubleQueue[U]:
399
+ """`Map a function over `DoubleQueue`.
400
+
401
+ * map the function `f` over the `DoubleQueue`
402
+ * left to right
403
+ * retain original order
404
+ * returns a new instance
405
+
406
+ """
407
+ return DoubleQueue(map(f, self._ca))
408
+
409
+ def FQ[D](*ds: D) -> FIFOQueue[D]:
410
+ """Return a FIFOQueue where data is pushed on in natural FIFO order."""
411
+ return FIFOQueue(ds)
412
+
413
+ def LQ[D](*ds: D) -> LIFOQueue[D]:
414
+ """Return a LIFOQueue where data is pushed on in natural LIFO order."""
415
+ return LIFOQueue(ds)
416
+
417
+ def DQ[D](*ds: D) -> DoubleQueue[D]:
418
+ """Return a DoubleQueue whose data is pushed on from the right."""
419
+ return DoubleQueue(ds)
420
+
@@ -0,0 +1,27 @@
1
+ # Copyright 2023-2025 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ ### package splitends
17
+
18
+ Singularly linked datastructures that can safely share data between themselves.
19
+
20
+ #### Package splitend modules
21
+
22
+ * module **dtools.datastructures.splitends.se: Basic SplitEnd data structure
23
+
24
+ """
25
+ __author__ = "Geoffrey R. Scheller"
26
+ __copyright__ = "Copyright (c) 2024 Geoffrey R. Scheller"
27
+ __license__ = "Apache License 2.0"
File without changes
@@ -0,0 +1,179 @@
1
+ # Copyright 2023-2025 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """### SplitEnd stack related data structures
16
+
17
+ With use I am finding this data structure needs some sort of supporting
18
+ infrastructure. Hence I split the original splitend module out to be its own
19
+ subpackage.
20
+
21
+ #### SplitEnd Stack type and SE factory function
22
+
23
+ * class SplitEnd: Singularly linked stack with shareable data nodes
24
+ * function SE: create SplitEnd from a variable number of arguments
25
+
26
+ """
27
+ from __future__ import annotations
28
+
29
+ from collections.abc import Callable, Iterable, Iterator
30
+ from typing import cast, Never
31
+ from ..nodes import SL_Node
32
+ from dtools.fp.err_handling import MB
33
+
34
+ __all__ = [ 'SplitEnd', 'SE' ]
35
+
36
+ class SplitEnd[D]():
37
+ """Class SplitEnd
38
+
39
+ LIFO stacks which can safely share immutable data between themselves.
40
+
41
+ * each SplitEnd is a very simple stateful (mutable) LIFO stack
42
+ * top of the stack is the "top"
43
+ * data can be pushed and popped to the stack
44
+ * different mutable split ends can safely share the same "tail"
45
+ * each SplitEnd sees itself as a singularly linked list
46
+ * bush-like datastructures can be formed using multiple SplitEnds
47
+ * len() returns the number of elements on the SplitEnd stack
48
+ * in boolean context, return true if split end is not empty
49
+
50
+ """
51
+ __slots__ = '_count', '_tip'
52
+
53
+ def __init__(self, *dss: Iterable[D]) -> None:
54
+ if length:=len(dss) < 2:
55
+ self._tip: MB[SL_Node[D]] = MB()
56
+ self._count: int = 0
57
+ if length == 1:
58
+ self.pushI(*dss)
59
+ else:
60
+ msg1 = 'SplitEnd: expected at most 1 '
61
+ msg2 = f'iterable argument, got {length}.'
62
+ raise TypeError(msg1+msg2)
63
+
64
+ def __iter__(self) -> Iterator[D]:
65
+ if self._tip == MB():
66
+ empty: tuple[D, ...] = ()
67
+ return iter(empty)
68
+ return iter(self._tip.get())
69
+
70
+ def __reversed__(self) -> Iterator[D]:
71
+ return reversed(list(self))
72
+
73
+ def __bool__(self) -> bool:
74
+ # Returns true if not a root node
75
+ return bool(self._tip)
76
+
77
+ def __len__(self) -> int:
78
+ return self._count
79
+
80
+ def __repr__(self) -> str:
81
+ return 'SE(' + ', '.join(map(repr, reversed(self))) + ')'
82
+
83
+ def __str__(self) -> str:
84
+ return ('>< ' + ' -> '.join(map(str, self)) + ' ||')
85
+
86
+ def __eq__(self, other: object, /) -> bool:
87
+ if not isinstance(other, type(self)):
88
+ return False
89
+
90
+ if self._count != other._count:
91
+ return False
92
+ if self._count == 0:
93
+ return True
94
+
95
+ left = self._tip.get()
96
+ right = other._tip.get()
97
+ for _ in range(self._count):
98
+ if left is right:
99
+ return True
100
+ if not left.data_eq(right):
101
+ return False
102
+ if left:
103
+ left = left._prev.get()
104
+ right = right._prev.get()
105
+
106
+ return True
107
+
108
+ def pushI(self, ds: Iterable[D], /) -> None:
109
+ """Push data onto the top of the SplitEnd."""
110
+ for d in ds:
111
+ node = SL_Node(d, self._tip)
112
+ self._tip, self._count = MB(node), self._count+1
113
+
114
+ def push(self, *ds: D) -> None:
115
+ """Push data onto the top of the SplitEnd."""
116
+ for d in ds:
117
+ node = SL_Node(d, self._tip)
118
+ self._tip, self._count = MB(node), self._count+1
119
+
120
+ def pop(self, default: D|None = None, /) -> D|Never:
121
+ """Pop data off of the top of the SplitEnd.
122
+
123
+ * raises ValueError if
124
+ * popping from an empty SplitEnd
125
+ * and no default value was given
126
+
127
+ """
128
+ if self._count == 0:
129
+ if default is None:
130
+ raise ValueError('SE: Popping from an empty SplitEnd')
131
+ else:
132
+ return default
133
+
134
+ data, self._tip, self._count = self._tip.get().pop2() + (self._count-1,)
135
+ return data
136
+
137
+ def peak(self, default: D|None = None, /) -> D:
138
+ """Return the data at the top of the SplitEnd.
139
+
140
+ * does not consume the data
141
+ * raises ValueError if peaking at an empty SplitEnd
142
+
143
+ """
144
+ if self._count == 0:
145
+ if default is None:
146
+ raise ValueError('SE: Popping from an empty SplitEnd')
147
+ else:
148
+ return default
149
+
150
+ return self._tip.get().get_data()
151
+
152
+ def copy(self) -> SplitEnd[D]:
153
+ """Return a copy of the SplitEnd.
154
+
155
+ * O(1) space & time complexity.
156
+ * returns a new instance
157
+
158
+ """
159
+ se: SplitEnd[D] = SE()
160
+ se._tip, se._count = self._tip, self._count
161
+ return se
162
+
163
+ def fold[T](self, f:Callable[[T, D], T], init: T|None = None, /) -> T|Never:
164
+ """Reduce with a function.
165
+
166
+ * folds in natural LIFO Order
167
+
168
+ """
169
+ if self._tip != MB():
170
+ return self._tip.get().fold(f, init)
171
+ elif init is not None:
172
+ return init
173
+ else:
174
+ msg = 'SE: Folding empty SplitEnd but no initial value supplied'
175
+ raise ValueError(msg)
176
+
177
+ def SE[D](*ds: D) -> SplitEnd[D]:
178
+ return SplitEnd(ds)
179
+
@@ -0,0 +1,203 @@
1
+ # Copyright 2023-2025 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """### Tuple based data structures
16
+
17
+ Only example here is the ftuple, basically an FP interface wrapping a tuple.
18
+ Originally it inherited from tuple, but I found containing the tuple in a
19
+ "has-a" relationship makes for a faster implementation. Buried in the git
20
+ history is another example called a "process array" (parray) which I might
21
+ return to someday. The idea of the parray is a fixed length sequence with
22
+ sentinel values.
23
+
24
+ #### FTuple and FT factory function.
25
+
26
+ * class FTuple: Wrapped tuple with a Functional Programming API
27
+ * function FE:
28
+
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from collections.abc import Callable, Iterable, Iterator, Sequence
34
+ from typing import cast, overload
35
+ from dtools.fp.iterables import FM, accumulate, concat, exhaust, merge
36
+
37
+ __all__ = ['FTuple', 'FT']
38
+
39
+ class FTuple[D](Sequence[D]):
40
+ """
41
+ #### Functional Tuple
42
+
43
+ * immutable tuple-like data structure with a functional interface
44
+ * supports both indexing and slicing
45
+ * `FTuple` addition & `int` multiplication supported
46
+ * addition concatenates results, resulting type a Union type
47
+ * both left and right int multiplication supported
48
+
49
+ """
50
+ __slots__ = '_ds'
51
+
52
+ def __init__(self, *dss: Iterable[D]) -> None:
53
+ if len(dss) < 2:
54
+ self._ds: tuple[D, ...] = tuple(*dss)
55
+ else:
56
+ msg = f'FTuple expected at most 1 iterable argument, got {len(dss)}'
57
+ raise TypeError(msg)
58
+
59
+ def __iter__(self) -> Iterator[D]:
60
+ return iter(self._ds)
61
+
62
+ def __reversed__(self) -> Iterator[D]:
63
+ return reversed(self._ds)
64
+
65
+ def __bool__(self) -> bool:
66
+ return bool(len(self._ds))
67
+
68
+ def __len__(self) -> int:
69
+ return len(self._ds)
70
+
71
+ def __repr__(self) -> str:
72
+ return 'FT(' + ', '.join(map(repr, self)) + ')'
73
+
74
+ def __str__(self) -> str:
75
+ return "((" + ", ".join(map(repr, self)) + "))"
76
+
77
+ def __eq__(self, other: object, /) -> bool:
78
+ if self is other:
79
+ return True
80
+ if not isinstance(other, type(self)):
81
+ return False
82
+ return self._ds == other._ds
83
+
84
+ @overload
85
+ def __getitem__(self, idx: int, /) -> D: ...
86
+ @overload
87
+ def __getitem__(self, idx: slice, /) -> FTuple[D]: ...
88
+
89
+ def __getitem__(self, idx: slice|int, /) -> FTuple[D]|D:
90
+ if isinstance(idx, slice):
91
+ return FTuple(self._ds[idx])
92
+ else:
93
+ return self._ds[idx]
94
+
95
+ def foldL[L](self,
96
+ f: Callable[[L, D], L], /,
97
+ start: L|None=None,
98
+ default: L|None=None) -> L|None:
99
+ """
100
+ **Fold Left**
101
+
102
+ * fold left with an optional starting value
103
+ * first argument of function `f` is for the accumulated value
104
+ * throws `ValueError` when `FTuple` empty and a start value not given
105
+
106
+ """
107
+ it = iter(self._ds)
108
+ if start is not None:
109
+ acc = start
110
+ elif self:
111
+ acc = cast(L, next(it)) # L = D in this case
112
+ else:
113
+ if default is None:
114
+ msg = 'Both start and default cannot be None for an empty FTuple'
115
+ raise ValueError('FTuple.foldL - ' + msg)
116
+ acc = default
117
+ for v in it:
118
+ acc = f(acc, v)
119
+ return acc
120
+
121
+ def foldR[R](self,
122
+ f: Callable[[D, R], R], /,
123
+ start: R|None=None,
124
+ default: R|None=None) -> R|None:
125
+ """
126
+ **Fold Right**
127
+
128
+ * fold right with an optional starting value
129
+ * second argument of function `f` is for the accumulated value
130
+ * throws `ValueError` when `FTuple` empty and a start value not given
131
+
132
+ """
133
+ it = reversed(self._ds)
134
+ if start is not None:
135
+ acc = start
136
+ elif self:
137
+ acc = cast(R, next(it)) # R = D in this case
138
+ else:
139
+ if default is None:
140
+ msg = 'Both start and default cannot be None for an empty FTuple'
141
+ raise ValueError('FTuple.foldR - ' + msg)
142
+ acc = default
143
+ for v in it:
144
+ acc = f(v, acc)
145
+ return acc
146
+
147
+ def copy(self) -> FTuple[D]:
148
+ """
149
+ **Copy**
150
+
151
+ Return a shallow copy of the FTuple in O(1) time & space complexity.
152
+
153
+ """
154
+ return FTuple(self)
155
+
156
+ def __add__[E](self, other: FTuple[E], /) -> FTuple[D|E]:
157
+ return FTuple(concat(self, other))
158
+
159
+ def __mul__(self, num: int, /) -> FTuple[D]:
160
+ return FTuple(self._ds.__mul__(num if num > 0 else 0))
161
+
162
+ def __rmul__(self, num: int, /) -> FTuple[D]:
163
+ return FTuple(self._ds.__mul__(num if num > 0 else 0))
164
+
165
+ def accummulate[L](self, f: Callable[[L, D], L], s: L|None=None, /) -> FTuple[L]:
166
+ """
167
+ **Accumulate partial folds**
168
+
169
+ Accumulate partial fold results in an FTuple with an optional starting
170
+ value.
171
+
172
+ """
173
+ if s is None:
174
+ return FTuple(accumulate(self, f))
175
+ else:
176
+ return FTuple(accumulate(self, f, s))
177
+
178
+ def map[U](self, f: Callable[[D], U], /) -> FTuple[U]:
179
+ return FTuple(map(f, self))
180
+
181
+ def bind[U](self, f: Callable[[D], FTuple[U]], type: FM=FM.CONCAT, /) -> FTuple[U]:
182
+ """
183
+ Bind function `f` to the `FTuple`.
184
+
185
+ * type = CONCAT: sequentially concatenate iterables one after the other
186
+ * type = MERGE: merge iterables together until one is exhausted
187
+ * type = Exhaust: merge iterables together until all are exhausted
188
+
189
+ """
190
+ match type:
191
+ case FM.CONCAT:
192
+ return FTuple(concat(*map(lambda x: iter(x), map(f, self))))
193
+ case FM.MERGE:
194
+ return FTuple(merge(*map(lambda x: iter(x), map(f, self))))
195
+ case FM.EXHAUST:
196
+ return FTuple(exhaust(*map(lambda x: iter(x), map(f, self))))
197
+ case '*':
198
+ raise ValueError('Unknown FM type')
199
+
200
+ def FT[D](*ds: D) -> FTuple[D]:
201
+ """Return an FTuple whose values are the function arguments."""
202
+ return FTuple(ds)
203
+
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright (c) 2023-2024 Geoffrey R. Scheller
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.3
2
+ Name: dtools.datastructures
3
+ Version: 0.25.0
4
+ Summary: ### package datastructures
5
+ Keywords: datastructures,data structures,fifo,lifo,stack,queue,SplitEnd
6
+ Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Framework :: Pytest
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Typing :: Typed
16
+ Requires-Dist: dtools.circular-array >= 3.9.0, < 3.10
17
+ Requires-Dist: dtools.fp >= 1.3.0, < 1.4
18
+ Requires-Dist: pytest >=8.3.2 ; extra == "test"
19
+ Project-URL: Changelog, https://github.com/grscheller/dtools-datastructures/blob/main/CHANGELOG.md
20
+ Project-URL: Documentation, https://grscheller.github.io/dtools-docs/datastructures
21
+ Project-URL: Source, https://github.com/grscheller/dtools-datastructures
22
+ Provides-Extra: test
23
+
24
+ # Python Datastructures Useful for Algorithms
25
+
26
+ Python package of data structures which support the use and
27
+ implementation of algorithms.
28
+
29
+ * **Repositories**
30
+ * [dtools.datastructures][1] project on *PyPI*
31
+ * [Source code][2] on *GitHub*
32
+ * **Detailed documentation**
33
+ * [Detailed API documentation][3] on *GH-Pages*
34
+
35
+
36
+ ### Overview
37
+
38
+ Data structures allowing developers to focus on the algorithms they are
39
+ using instead of all the "bit fiddling" required to implement behaviors,
40
+ perform memory management, and handle coding edge cases. These data
41
+ structures allow iterators to leisurely iterate over inaccessible copies
42
+ of internal state while the data structures themselves are free to
43
+ safely mutate. They are designed to be reasonably "atomic" without
44
+ introducing inordinate complexity. Some of these data structures allow
45
+ data to be safely shared between multiple data structure instances by
46
+ making shared data immutable and inaccessible to client code.
47
+
48
+ * functional & imperative programming styles supported
49
+ * functional programming encouraged
50
+ * project endeavors to remain Pythonic
51
+ * methods which mutate objects don't return anything
52
+ * like Python lists
53
+ * in caparisons identity is considered before equality
54
+ * like Python builtins
55
+
56
+ Sometimes the real power of a data structure comes not from what it
57
+ empowers you to do, but from what it prevents you from doing to
58
+ yourself.
59
+
60
+ ---
61
+
62
+ [1]: https://pypi.org/project/grscheller.dtools-datastructures/
63
+ [2]: https://github.com/grscheller/dtools-datastructures/
64
+ [3]: https://grscheller.github.io/dtools-docs/datastructures/
65
+
@@ -0,0 +1,12 @@
1
+ dtools/datastructures/__init__.py,sha256=6GNS6Y94Af0IeRhKdQlUoPsKK1BLfpeSa_wjxXjz4uw,1163
2
+ dtools/datastructures/nodes.py,sha256=surkupETxZUAZeuhf8wM8BUWbP98trmqx5fczx32IU4,5695
3
+ dtools/datastructures/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dtools/datastructures/queues.py,sha256=30rBRKZRCwYLLog2SIFLxqb5RzMvTfxroZK_K2FOfoM,12119
5
+ dtools/datastructures/tuples.py,sha256=88-e9Dpoeku8sYa3cmDFX17D80LiIb7lFHC2CGa6OnU,6583
6
+ dtools/datastructures/splitends/__init__.py,sha256=y0BpPKBg6g9GtcHwhEhQTwnAIdpxvhVD09vE1AMPYPM,940
7
+ dtools/datastructures/splitends/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ dtools/datastructures/splitends/se.py,sha256=Anm7fWa9A0hB6v6I3SVg-ZMm47J5LblAyyOV5gMnli8,5600
9
+ dtools_datastructures-0.25.0.dist-info/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
10
+ dtools_datastructures-0.25.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
11
+ dtools_datastructures-0.25.0.dist-info/METADATA,sha256=reT_ouL-PHBhasetsZDmhTrjrwqp6aXAvhMe2PA18f4,2625
12
+ dtools_datastructures-0.25.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.10.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any