dtools.datastructures 0.25.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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