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.
- dtools/datastructures/__init__.py +31 -0
- dtools/datastructures/nodes.py +189 -0
- dtools/datastructures/py.typed +0 -0
- dtools/datastructures/queues.py +420 -0
- dtools/datastructures/splitends/__init__.py +27 -0
- dtools/datastructures/splitends/py.typed +0 -0
- dtools/datastructures/splitends/se.py +179 -0
- dtools/datastructures/tuples.py +203 -0
- dtools_datastructures-0.25.0.dist-info/LICENSE +190 -0
- dtools_datastructures-0.25.0.dist-info/METADATA +65 -0
- dtools_datastructures-0.25.0.dist-info/RECORD +12 -0
- dtools_datastructures-0.25.0.dist-info/WHEEL +4 -0
@@ -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,,
|