pythonic-fp.splitends 1.0.2__py3-none-any.whl → 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonic_fp/splitends/__init__.py +17 -13
- pythonic_fp/splitends/__init__.pyi +0 -0
- pythonic_fp/splitends/splitend.py +171 -61
- pythonic_fp/splitends/splitend.pyi +21 -28
- pythonic_fp/splitends/splitend_node.py +77 -63
- pythonic_fp/splitends/splitend_node.pyi +14 -30
- {pythonic_fp_splitends-1.0.2.dist-info → pythonic_fp_splitends-2.0.2.dist-info}/METADATA +11 -12
- pythonic_fp_splitends-2.0.2.dist-info/RECORD +11 -0
- pythonic_fp_splitends-1.0.2.dist-info/RECORD +0 -10
- {pythonic_fp_splitends-1.0.2.dist-info → pythonic_fp_splitends-2.0.2.dist-info}/WHEEL +0 -0
- {pythonic_fp_splitends-1.0.2.dist-info → pythonic_fp_splitends-2.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2024-
|
|
1
|
+
# Copyright 2024-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -14,21 +14,25 @@
|
|
|
14
14
|
|
|
15
15
|
"""Mutable stack objects that can safely share data.
|
|
16
16
|
|
|
17
|
-
Python package
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
Python package implementing a singularly linked stack of nodes called a ``SplitEnd``.
|
|
18
|
+
While mutable, these data structures can safely share immutable data nodes between
|
|
19
|
+
themselves. These nodes, of type ``SENode``, always contain data. A root node is
|
|
20
|
+
a node whose previous node is itself. Root nodes mark the bottom of the stack.
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
Like one of many "split ends" from shafts of hair, a ``SplitEnd`` can be "snipped"
|
|
23
|
+
shorter or "extended" further from its "end" node at the top of the stack. Its "root"
|
|
24
|
+
node is at the bottom of the stack and can not be "snipped" or "cut" off.
|
|
25
|
+
|
|
26
|
+
A ``SplitEnd`` can be duplicated onto a compatible root node. By compatible, the
|
|
27
|
+
new root must contain data that compares as equal to the old root node.
|
|
28
|
+
|
|
29
|
+
**TODO:** A ``scalp`` is a container of root nodes all of whose contained data must
|
|
30
|
+
be hashable. The ``scalp.add(d)`` method will return the unique root node with data
|
|
31
|
+
that compares as equal to ``d``, or create a new root node if such a contained node
|
|
32
|
+
does not exist.
|
|
29
33
|
|
|
30
34
|
"""
|
|
31
35
|
|
|
32
36
|
__author__ = 'Geoffrey R. Scheller'
|
|
33
|
-
__copyright__ = 'Copyright (c) 2023-
|
|
37
|
+
__copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
|
|
34
38
|
__license__ = 'Apache License 2.0'
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,51 +12,83 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""
|
|
15
|
+
"""
|
|
16
|
+
Class SplitEnds
|
|
17
|
+
---------------
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
.. admonition:: LIFO stacks safely sharing immutable data.
|
|
20
|
+
|
|
21
|
+
- each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
|
|
22
|
+
- data can be either "extended" to or "snipped" off the "end"
|
|
23
|
+
- the "root" of a ``SplitEnd``
|
|
24
|
+
|
|
25
|
+
- it is fixed and cannot be removed from the ``SplitEnd``
|
|
26
|
+
|
|
27
|
+
- different mutable split ends can safely share the same "tail"
|
|
28
|
+
- each ``SplitEnd`` sees itself as a singularly linked list
|
|
29
|
+
- bush-like datastructures can be formed using multiple ``SplitEnds``
|
|
30
|
+
- the ``SplitEnd.split`` and ``len`` methods are O(1)
|
|
31
|
+
- in boolean context returns true if the ``SplitEnd`` is not just its "root"
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from collections.abc import Callable, Iterator
|
|
36
|
+
from typing import cast, Final, overload
|
|
37
|
+
from pythonic_fp.gadgets.sentinels.flavored import Sentinel
|
|
38
|
+
from pythonic_fp.iterables.folding import reduce_left, fold_left
|
|
39
|
+
from pythonic_fp.queues.lifo import LIFOQueue
|
|
20
40
|
from .splitend_node import SENode
|
|
21
41
|
|
|
22
42
|
__all__ = ['SplitEnd']
|
|
23
43
|
|
|
24
|
-
|
|
44
|
+
type _SecretType = tuple[str, str, str]
|
|
45
|
+
_secret_value: Final[_SecretType] = 'split', 'end', '_private'
|
|
46
|
+
type _Sentinel = Sentinel[_SecretType]
|
|
47
|
+
_sentinel: Final[_Sentinel] = Sentinel(_secret_value)
|
|
25
48
|
|
|
26
49
|
|
|
27
50
|
class SplitEnd[D]:
|
|
28
|
-
|
|
29
|
-
a ``splitend`` can be "snipped" shorter or "extended"
|
|
30
|
-
further from its "tip". Its root is irremovable and
|
|
31
|
-
cannot be "snipped" off. While mutable, different
|
|
32
|
-
splitends can safely share data with each other.
|
|
33
|
-
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
__slots__ = '_count', '_tip', '_root'
|
|
51
|
+
__slots__ = '_end', '_root', '_count'
|
|
37
52
|
|
|
38
|
-
def __init__(self,
|
|
53
|
+
def __init__(self, *ds: D, root: SENode[D] | _Sentinel = _sentinel) -> None:
|
|
39
54
|
"""
|
|
40
|
-
:param root_data:
|
|
41
|
-
:param data:
|
|
55
|
+
:param root_data: Irremovable initial data at bottom of stack.
|
|
56
|
+
:param data: Removable data to be pushed onto splitend stack.
|
|
42
57
|
|
|
43
58
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
if root is _sentinel:
|
|
60
|
+
if ds:
|
|
61
|
+
root_node: SENode[D] = SENode(ds[0])
|
|
62
|
+
ds = ds[1:]
|
|
63
|
+
else:
|
|
64
|
+
msg = 'SplitEnd: No data provided for root node'
|
|
65
|
+
raise ValueError(msg)
|
|
66
|
+
else:
|
|
67
|
+
if root:
|
|
68
|
+
root_node = cast(SENode[D], root)
|
|
69
|
+
else:
|
|
70
|
+
msg = 'SplitEnd: Provided node is not a root node'
|
|
71
|
+
raise ValueError(msg)
|
|
72
|
+
|
|
73
|
+
end, count = root_node, 1
|
|
74
|
+
for d in ds:
|
|
75
|
+
node = SENode(d, end)
|
|
76
|
+
end, count = node, count + 1
|
|
77
|
+
|
|
78
|
+
self._end, self._root, self._count = end, root_node, count
|
|
50
79
|
|
|
51
80
|
def __iter__(self) -> Iterator[D]:
|
|
52
|
-
return iter(self.
|
|
81
|
+
return iter(self._end)
|
|
53
82
|
|
|
54
83
|
def __reversed__(self) -> Iterator[D]:
|
|
55
84
|
return reversed(list(self))
|
|
56
85
|
|
|
57
86
|
def __bool__(self) -> bool:
|
|
58
|
-
|
|
59
|
-
|
|
87
|
+
"""
|
|
88
|
+
:returns: ``True`` is ``SplitEnd`` is not just its root node.
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
return bool(self._end)
|
|
60
92
|
|
|
61
93
|
def __len__(self) -> int:
|
|
62
94
|
return self._count
|
|
@@ -76,18 +108,44 @@ class SplitEnd[D]:
|
|
|
76
108
|
if self._root != other._root:
|
|
77
109
|
return False
|
|
78
110
|
|
|
79
|
-
left = self.
|
|
80
|
-
right = other.
|
|
111
|
+
left = self._end
|
|
112
|
+
right = other._end
|
|
81
113
|
for _ in range(self._count):
|
|
82
114
|
if left is right:
|
|
83
115
|
return True
|
|
84
|
-
if left.
|
|
116
|
+
if left.data() != right.data():
|
|
85
117
|
return False
|
|
86
118
|
if left:
|
|
87
|
-
left = left.
|
|
88
|
-
right = right.
|
|
119
|
+
left = left.prev()
|
|
120
|
+
right = right.prev()
|
|
89
121
|
return True
|
|
90
122
|
|
|
123
|
+
def cut(self, num: int | None = None) -> tuple[D, ...]:
|
|
124
|
+
"""Cut data off end of ``SplitEnd``.
|
|
125
|
+
|
|
126
|
+
:param num: Optional number of nodes to cut, default is entire stack.
|
|
127
|
+
:returns: Tuple of data cut off from end.
|
|
128
|
+
|
|
129
|
+
"""
|
|
130
|
+
if num is None or num > self._count:
|
|
131
|
+
num = self._count
|
|
132
|
+
|
|
133
|
+
data: tuple[D, ...] = ()
|
|
134
|
+
node = self._end
|
|
135
|
+
count = self._count
|
|
136
|
+
n = num
|
|
137
|
+
while n > 0:
|
|
138
|
+
d, node = node.both()
|
|
139
|
+
data = data + (d,)
|
|
140
|
+
n -= 1
|
|
141
|
+
|
|
142
|
+
if self._count - num > 1:
|
|
143
|
+
self._end, self._count = node, count - num
|
|
144
|
+
else:
|
|
145
|
+
self._end, self._count = node, 1
|
|
146
|
+
|
|
147
|
+
return data
|
|
148
|
+
|
|
91
149
|
def extend(self, *ds: D) -> None:
|
|
92
150
|
"""Add data onto the tip of the SplitEnd. Like adding a hair
|
|
93
151
|
extension.
|
|
@@ -96,59 +154,111 @@ class SplitEnd[D]:
|
|
|
96
154
|
|
|
97
155
|
"""
|
|
98
156
|
for d in ds:
|
|
99
|
-
node = SENode(d, self.
|
|
100
|
-
self.
|
|
157
|
+
node = SENode(d, self._end)
|
|
158
|
+
self._end, self._count = node, self._count + 1
|
|
159
|
+
|
|
160
|
+
def peak(self) -> D:
|
|
161
|
+
"""Return the data at end (top) of SplitEnd without consuming it.
|
|
162
|
+
|
|
163
|
+
:returns: The data at the end of the SplitEnd.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
return self._end.data()
|
|
167
|
+
|
|
168
|
+
def root(self) -> SENode[D]:
|
|
169
|
+
"""
|
|
170
|
+
:returns: The root SENode node of the SplitEnd.
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
return self._root
|
|
174
|
+
|
|
175
|
+
def reroot(self, root: SENode[D]) -> 'SplitEnd[D]':
|
|
176
|
+
"""Create a brand new SplitEnd with the same data but different root.
|
|
177
|
+
|
|
178
|
+
.. note::
|
|
179
|
+
|
|
180
|
+
Two nodes are compatible root nodes if and only if
|
|
181
|
+
|
|
182
|
+
- they are both actually root nodes
|
|
183
|
+
|
|
184
|
+
- which implies that their previous nodes are themselves
|
|
185
|
+
|
|
186
|
+
- their data compare as equal
|
|
187
|
+
|
|
188
|
+
- comparing by identity is too strong for some use cases
|
|
189
|
+
|
|
190
|
+
:returns: New SplitEnd with the same data and the new ``root``.
|
|
191
|
+
:raises ValueError: If new and original root nodes are not compatible.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
if not root:
|
|
195
|
+
msg = 'New root node is not a root node.'
|
|
196
|
+
raise ValueError(msg)
|
|
197
|
+
if root.data() != self._root.data():
|
|
198
|
+
msg = 'New root node not compatible with current root node.'
|
|
199
|
+
raise ValueError(msg)
|
|
200
|
+
|
|
201
|
+
lifo = LIFOQueue[D]()
|
|
202
|
+
for data in self:
|
|
203
|
+
lifo.push(data)
|
|
204
|
+
lifo.pop()
|
|
205
|
+
|
|
206
|
+
return SplitEnd(*lifo, root = root)
|
|
101
207
|
|
|
102
208
|
def snip(self) -> D:
|
|
103
209
|
"""Snip data off tip of SplitEnd. Just return data if tip is root.
|
|
104
210
|
|
|
105
|
-
:returns:
|
|
211
|
+
:returns: Data snipped off tip, just return root data if at root.
|
|
106
212
|
|
|
107
213
|
"""
|
|
108
214
|
if self._count > 1:
|
|
109
|
-
data, self.
|
|
215
|
+
data, self._end, self._count = self._end.both() + (self._count - 1,)
|
|
110
216
|
else:
|
|
111
|
-
data = self.
|
|
217
|
+
data = self._end.data()
|
|
112
218
|
|
|
113
219
|
return data
|
|
114
220
|
|
|
115
|
-
def
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
:returns: data at the end of the SplitEnd
|
|
221
|
+
def split(self, *ds: D) -> 'SplitEnd[D]':
|
|
222
|
+
"""Split the end and add more data.
|
|
119
223
|
|
|
120
|
-
|
|
121
|
-
return self._tip.peak()
|
|
122
|
-
|
|
123
|
-
def copy(self) -> 'SplitEnd[D]':
|
|
124
|
-
"""Return a copy of the SplitEnd. O(1) space & time complexity.
|
|
125
|
-
|
|
126
|
-
:returns: a new SplitEnd instance with same data and root
|
|
224
|
+
:returns: New instance, same data nodes plus additional ones on end.
|
|
127
225
|
|
|
128
226
|
"""
|
|
129
|
-
se: SplitEnd[D] = SplitEnd(self._root.
|
|
130
|
-
se._count, se.
|
|
227
|
+
se: SplitEnd[D] = SplitEnd(self._root.data())
|
|
228
|
+
se._count, se._end, se._root = self._count, self._end, self._root
|
|
229
|
+
se.extend(*ds)
|
|
131
230
|
return se
|
|
132
231
|
|
|
232
|
+
@overload
|
|
233
|
+
def fold[T](self, f: Callable[[D, D], D]) -> D: ...
|
|
234
|
+
@overload
|
|
235
|
+
def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
236
|
+
|
|
133
237
|
def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
|
|
134
|
-
"""Reduce with a function,
|
|
238
|
+
"""Reduce with a function, folding from tip to root.
|
|
135
239
|
|
|
136
|
-
:param f:
|
|
137
|
-
:param init:
|
|
138
|
-
:returns:
|
|
240
|
+
:param f: Folding function, first argument is for the accumulator.
|
|
241
|
+
:param init: Optional initial starting value for the fold.
|
|
242
|
+
:returns: Reduced value folding from tip to root in natural LIFO order.
|
|
139
243
|
|
|
140
244
|
"""
|
|
141
|
-
|
|
245
|
+
if init is None:
|
|
246
|
+
return self._end.fold(f) # type: ignore
|
|
247
|
+
return self._end.fold(f, init)
|
|
248
|
+
|
|
249
|
+
@overload
|
|
250
|
+
def rev_fold[T](self, f: Callable[[D, D], D]) -> D: ...
|
|
251
|
+
@overload
|
|
252
|
+
def rev_fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
142
253
|
|
|
143
254
|
def rev_fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
|
|
144
255
|
"""Reduce with a function, fold from root to tip.
|
|
145
256
|
|
|
146
|
-
:param f:
|
|
147
|
-
:param init:
|
|
148
|
-
:returns:
|
|
257
|
+
:param f: Folding function, first argument is for the accumulator.
|
|
258
|
+
:param init: Optional initial starting value for the fold.
|
|
259
|
+
:returns: Reduced value folding from root to tip.
|
|
149
260
|
|
|
150
261
|
"""
|
|
151
|
-
# The get() is safe because SplitEnds are never "empty" due to the root.
|
|
152
262
|
if init is None:
|
|
153
|
-
return
|
|
154
|
-
return
|
|
263
|
+
return cast(T, reduce_left(reversed(self), cast(Callable[[D, D], D], f)))
|
|
264
|
+
return fold_left(reversed(self), f, init)
|
|
@@ -1,38 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
from collections.abc import Callable, Hashable, Iterator
|
|
16
|
-
from typing import TypeVar
|
|
1
|
+
from .splitend_node import SENode
|
|
2
|
+
from collections.abc import Callable, Iterator
|
|
3
|
+
from pythonic_fp.gadgets.sentinels.flavored import Sentinel
|
|
4
|
+
from typing import overload
|
|
17
5
|
|
|
18
6
|
__all__ = ['SplitEnd']
|
|
19
7
|
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
type _SecretType = tuple[str, str, str]
|
|
9
|
+
type _Sentinel = Sentinel[_SecretType]
|
|
22
10
|
class SplitEnd[D]:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def __init__(self, root_data: D, *data: D) -> None: ...
|
|
11
|
+
def __init__(self, *ds: D, root: SENode[D] | _Sentinel = ...) -> None: ...
|
|
26
12
|
def __iter__(self) -> Iterator[D]: ...
|
|
27
13
|
def __reversed__(self) -> Iterator[D]: ...
|
|
28
14
|
def __bool__(self) -> bool: ...
|
|
29
15
|
def __len__(self) -> int: ...
|
|
30
|
-
def
|
|
31
|
-
def
|
|
32
|
-
def __eq__(self, other: object, /) -> bool: ...
|
|
16
|
+
def __eq__(self, other: object) -> bool: ...
|
|
17
|
+
def cut(self, num: int | None = None) -> tuple[D, ...]: ...
|
|
33
18
|
def extend(self, *ds: D) -> None: ...
|
|
34
|
-
def snip(self) -> D: ...
|
|
35
19
|
def peak(self) -> D: ...
|
|
36
|
-
def
|
|
37
|
-
def
|
|
38
|
-
def
|
|
20
|
+
def root(self) -> SENode[D]: ...
|
|
21
|
+
def reroot(self, root: SENode[D]) -> SplitEnd[D]: ...
|
|
22
|
+
def snip(self) -> D: ...
|
|
23
|
+
def split(self, *ds: D) -> SplitEnd[D]: ...
|
|
24
|
+
@overload
|
|
25
|
+
def fold[T](self, f: Callable[[D, D], D]) -> D: ...
|
|
26
|
+
@overload
|
|
27
|
+
def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
28
|
+
@overload
|
|
29
|
+
def rev_fold[T](self, f: Callable[[D, D], D]) -> D: ...
|
|
30
|
+
@overload
|
|
31
|
+
def rev_fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright 2023-
|
|
1
|
+
# Copyright 2023-2026 Geoffrey R. Scheller
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,123 +12,137 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""Data node class used privately by class SplitEnd.
|
|
16
|
-
|
|
17
|
-
Node classes used to make bush-like graphs. API made public since it
|
|
18
|
-
might prove useful to someone designing other data structures similar
|
|
19
|
-
to a ``splitend``.
|
|
20
|
-
|
|
21
15
|
"""
|
|
16
|
+
Class SENode
|
|
17
|
+
------------
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
from typing import cast, TypeVar
|
|
25
|
-
from pythonic_fp.fptools.maybe import MayBe
|
|
26
|
-
|
|
27
|
-
__all__ = ['SENode']
|
|
28
|
-
|
|
29
|
-
D = TypeVar('D', bound=Hashable)
|
|
19
|
+
.. admonition:: Used to make inwardly directed bush-like graphs
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
class SENode[D]:
|
|
33
|
-
"""Data node for class SplitEnd
|
|
34
|
-
|
|
35
|
-
- hashable data node for a end-to-root singularly linked list.
|
|
36
21
|
- designed so multiple splitends can safely share the same data
|
|
37
|
-
|
|
22
|
+
- nodes always contain data
|
|
23
|
+
- data node ``SENode[D]`` make up end-to-root singularly linked lists
|
|
38
24
|
- two nodes compare as equal if
|
|
39
25
|
|
|
40
26
|
- both their previous Nodes are the same
|
|
41
27
|
- their data compare as equal
|
|
42
28
|
|
|
29
|
+
- a root node is a node whose previous node is itself
|
|
30
|
+
|
|
31
|
+
- root nodes mark the bottom of splitend stacks
|
|
32
|
+
|
|
43
33
|
- more than one node can point to the same proceeding node
|
|
44
34
|
|
|
45
35
|
- forming bush like graphs
|
|
46
36
|
|
|
47
|
-
|
|
37
|
+
"""
|
|
48
38
|
|
|
39
|
+
from collections.abc import Callable, Iterator
|
|
40
|
+
from typing import cast, Final, overload, Self
|
|
41
|
+
from pythonic_fp.gadgets.sentinels.flavored import Sentinel
|
|
42
|
+
|
|
43
|
+
__all__ = ['SENode']
|
|
44
|
+
|
|
45
|
+
type _Sentinel = Sentinel[str]
|
|
46
|
+
_sentinel: Final[_Sentinel] = Sentinel('_split_end_node_private_str')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SENode[D]:
|
|
49
50
|
__slots__ = '_data', '_prev'
|
|
50
51
|
|
|
51
|
-
def __init__(self, data: D, prev:
|
|
52
|
+
def __init__(self, data: D, prev: Self | _Sentinel = _sentinel) -> None:
|
|
52
53
|
"""
|
|
53
|
-
:param data:
|
|
54
|
-
:param prev:
|
|
54
|
+
:param data: Nodes always contain data of type ``D``.
|
|
55
|
+
:param prev: Link to previous node. Points to ``self`` if a root node.
|
|
56
|
+
"""
|
|
57
|
+
self._data = data
|
|
58
|
+
if prev is not _sentinel:
|
|
59
|
+
self._prev = cast(Self, prev)
|
|
60
|
+
else:
|
|
61
|
+
self._prev = self
|
|
55
62
|
|
|
63
|
+
def __bool__(self) -> bool:
|
|
56
64
|
"""
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
:returns: ``True`` if ``SENode`` is not a root node.
|
|
66
|
+
"""
|
|
67
|
+
return self._prev is not self
|
|
59
68
|
|
|
60
69
|
def __iter__(self) -> Iterator[D]:
|
|
61
70
|
node = self
|
|
62
71
|
while node:
|
|
63
72
|
yield node._data
|
|
64
|
-
node = node._prev
|
|
73
|
+
node = node._prev
|
|
65
74
|
yield node._data
|
|
66
75
|
|
|
67
|
-
def __bool__(self) -> bool:
|
|
68
|
-
return self._prev != MayBe()
|
|
69
|
-
|
|
70
76
|
def __eq__(self, other: object) -> bool:
|
|
77
|
+
"""
|
|
78
|
+
Two ``SENodes`` nodes are equal if their previous nodes are the
|
|
79
|
+
same object and their data compare as equal.
|
|
80
|
+
|
|
81
|
+
"""
|
|
71
82
|
if not isinstance(other, type(self)):
|
|
72
83
|
return False
|
|
73
84
|
|
|
74
|
-
if self._prev
|
|
85
|
+
if self._prev is not other._prev:
|
|
75
86
|
return False
|
|
76
87
|
if self._data == other._data:
|
|
77
88
|
return True
|
|
78
89
|
return False
|
|
79
90
|
|
|
80
|
-
def
|
|
81
|
-
"""
|
|
91
|
+
def both(self) -> tuple[D, Self]:
|
|
92
|
+
"""Peak at data and previous node, if a root then data and self.
|
|
82
93
|
|
|
83
|
-
:returns:
|
|
94
|
+
:returns: tuple of type tuple[D, SENode[D]]
|
|
84
95
|
|
|
85
96
|
"""
|
|
86
|
-
return self._data
|
|
97
|
+
return self._data, self._prev
|
|
87
98
|
|
|
88
|
-
def
|
|
89
|
-
"""
|
|
99
|
+
def data(self) -> D:
|
|
100
|
+
"""Peak at data.
|
|
90
101
|
|
|
91
|
-
:returns:
|
|
102
|
+
:returns: The data stored in the ``SENode``.
|
|
92
103
|
|
|
93
104
|
"""
|
|
94
|
-
|
|
95
|
-
return self._data, self._prev.get()
|
|
96
|
-
return self._data, self
|
|
105
|
+
return self._data
|
|
97
106
|
|
|
98
|
-
def
|
|
99
|
-
"""
|
|
107
|
+
def prev(self) -> Self:
|
|
108
|
+
"""Peak at previous node.
|
|
100
109
|
|
|
101
|
-
:
|
|
102
|
-
:returns: the resulting SENode[D] representing the top of the stack
|
|
110
|
+
:returns: The previous node stored in the ``SENode``.
|
|
103
111
|
|
|
104
112
|
"""
|
|
105
|
-
return
|
|
113
|
+
return self._prev
|
|
106
114
|
|
|
107
|
-
def
|
|
108
|
-
"""
|
|
115
|
+
def push(self, data: D) -> Self:
|
|
116
|
+
"""Create a new ``SENode[D]``.
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
:param data: Data for new node to contain.
|
|
119
|
+
:returns: New ``SENode`` whose previous node is the current node.
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
f: Callable([T, D], T],
|
|
115
|
-
init: T | None = None
|
|
116
|
-
) -> T
|
|
121
|
+
"""
|
|
122
|
+
return cast(Self, SENode(data, self))
|
|
117
123
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
124
|
+
@overload
|
|
125
|
+
def fold(self, f: Callable[[D, D], D]) -> D: ...
|
|
126
|
+
@overload
|
|
127
|
+
def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
128
|
+
|
|
129
|
+
def fold[T](self, f: Callable[[T, D], T], init: T | _Sentinel = _sentinel) -> T:
|
|
130
|
+
"""Fold data across linked nodes with a function..
|
|
131
|
+
|
|
132
|
+
:param f: Folding function, first argument is for accumulated value.`
|
|
133
|
+
:param init: Optional initial starting value for the fold.
|
|
134
|
+
:returns: Reduced value folded from end to root in natural LIFO order.
|
|
121
135
|
|
|
122
136
|
"""
|
|
123
|
-
if init is
|
|
124
|
-
acc
|
|
125
|
-
node = self._prev.get()
|
|
126
|
-
else:
|
|
127
|
-
acc = init
|
|
137
|
+
if init is not _sentinel:
|
|
138
|
+
acc = cast(T, init)
|
|
128
139
|
node = self
|
|
140
|
+
else:
|
|
141
|
+
acc = cast(T, self._data) # in this case T = D
|
|
142
|
+
node = self._prev
|
|
129
143
|
|
|
130
144
|
while node:
|
|
131
145
|
acc = f(acc, node._data)
|
|
132
|
-
node = node._prev
|
|
146
|
+
node = node._prev
|
|
133
147
|
acc = f(acc, node._data)
|
|
134
148
|
return acc
|
|
@@ -1,36 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
from collections.abc import Callable, Hashable, Iterator
|
|
16
|
-
from typing import TypeVar
|
|
17
|
-
from pythonic_fp.fptools.maybe import MayBe
|
|
1
|
+
from collections.abc import Callable, Iterator
|
|
2
|
+
from pythonic_fp.gadgets.sentinels.flavored import Sentinel
|
|
3
|
+
from typing import Self, overload
|
|
18
4
|
|
|
19
5
|
__all__ = ['SENode']
|
|
20
6
|
|
|
21
|
-
|
|
22
|
-
|
|
7
|
+
type _Sentinel = Sentinel[str]
|
|
23
8
|
class SENode[D]:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_data: D
|
|
27
|
-
_prev: MayBe[SENode[D]]
|
|
28
|
-
|
|
29
|
-
def __init__(self, data: D, prev: SENode[D] | None = None) -> None: ...
|
|
30
|
-
def __iter__(self) -> Iterator[D]: ...
|
|
9
|
+
def __init__(self, data: D, prev: Self | _Sentinel = ...) -> None: ...
|
|
31
10
|
def __bool__(self) -> bool: ...
|
|
11
|
+
def __iter__(self) -> Iterator[D]: ...
|
|
32
12
|
def __eq__(self, other: object) -> bool: ...
|
|
33
|
-
def
|
|
34
|
-
def
|
|
35
|
-
def
|
|
36
|
-
def
|
|
13
|
+
def both(self) -> tuple[D, Self]: ...
|
|
14
|
+
def data(self) -> D: ...
|
|
15
|
+
def prev(self) -> Self: ...
|
|
16
|
+
def push(self, data: D) -> Self: ...
|
|
17
|
+
@overload
|
|
18
|
+
def fold(self, f: Callable[[D, D], D]) -> D: ...
|
|
19
|
+
@overload
|
|
20
|
+
def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonic-fp-splitends
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Mutable stack objects that can safely share data.
|
|
5
5
|
Keywords: SplitEnd,lifo,stack
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
@@ -14,13 +14,13 @@ Classifier: Operating System :: OS Independent
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: pythonic-fp-fptools>=5.
|
|
18
|
-
Requires-Dist: pythonic-fp-iterables>=5.1.
|
|
17
|
+
Requires-Dist: pythonic-fp-fptools>=5.2.0
|
|
18
|
+
Requires-Dist: pythonic-fp-iterables>=5.1.3
|
|
19
|
+
Requires-Dist: pythonic-fp-queues>=5.1.1
|
|
19
20
|
Requires-Dist: pytest>=8.4.1 ; extra == "test"
|
|
20
|
-
Requires-Dist: pythonic-fp-gadgets>=3.0.0 ; extra == "test"
|
|
21
21
|
Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.rst
|
|
22
|
-
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/splitends/development/build/html/
|
|
23
|
-
Project-URL: Homepage, https://github.
|
|
22
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/splitends/development/build/html/
|
|
23
|
+
Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/homepage/build/html/
|
|
24
24
|
Project-URL: Source, https://github.com/grscheller/pythonic-fp-splitends
|
|
25
25
|
Provides-Extra: test
|
|
26
26
|
|
|
@@ -43,18 +43,17 @@ between themselves.
|
|
|
43
43
|
- the ``SplitEnd`` copy method and ``len`` are O(1)
|
|
44
44
|
- in boolean context returns true if the ``SplitEnd`` is not just a "root"
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
`pythonic-fp
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
change.
|
|
46
|
+
Part of the
|
|
47
|
+
`pythonic-fp
|
|
48
|
+
<https://grscheller.github.io/pythonic-fp>`_
|
|
49
|
+
PyPI projects.
|
|
51
50
|
|
|
52
51
|
Documentation
|
|
53
52
|
-------------
|
|
54
53
|
|
|
55
54
|
Documentation for this project is hosted on
|
|
56
55
|
`GitHub Pages
|
|
57
|
-
<https://grscheller.github.io/pythonic-fp/splitends
|
|
56
|
+
<https://grscheller.github.io/pythonic-fp/splitends>`_.
|
|
58
57
|
|
|
59
58
|
Copyright and License
|
|
60
59
|
---------------------
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pythonic_fp/splitends/__init__.py,sha256=T3FgFehJkrlKx2OKHJJETDcEJ_2nKfyl96wTb4Nw6Fw,1780
|
|
2
|
+
pythonic_fp/splitends/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pythonic_fp/splitends/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pythonic_fp/splitends/splitend.py,sha256=TkLQ5o4-SCnnC_HTpda6cSotaxyQ6aNbc8DB81qNqSI,8317
|
|
5
|
+
pythonic_fp/splitends/splitend.pyi,sha256=OdTRATFXX5smbpwc8wm3gt4yyeTd_kN8VbMeNYl2Cho,1193
|
|
6
|
+
pythonic_fp/splitends/splitend_node.py,sha256=0Qz_oIvwbvtlYZOD-tZ0lD4PxMRysFPkBNPTBkGdkQk,4306
|
|
7
|
+
pythonic_fp/splitends/splitend_node.pyi,sha256=TozeGg4lmjMf0UPfZRTxRJR2kRpq0053VjbjIBUeosk,705
|
|
8
|
+
pythonic_fp_splitends-2.0.2.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
|
|
9
|
+
pythonic_fp_splitends-2.0.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
10
|
+
pythonic_fp_splitends-2.0.2.dist-info/METADATA,sha256=l9gYIuYaAd9pIQwLAe_6AHnSjowRzx6JKsV4uxtI6dQ,2381
|
|
11
|
+
pythonic_fp_splitends-2.0.2.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
pythonic_fp/splitends/__init__.py,sha256=gZzfqx_ODj0p32Zije6mFB-VUovxn2e8MGXjtpqcP2g,1457
|
|
2
|
-
pythonic_fp/splitends/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
pythonic_fp/splitends/splitend.py,sha256=k5oBCoTDL3OGKAgJ9bYeHZ5JL-FnRKwdQ7mEqS1e3x0,5018
|
|
4
|
-
pythonic_fp/splitends/splitend.pyi,sha256=xBJHTBdMO0VzeyhIUTiflm3BA3Pfq1GOjy9kvJOju1Q,1437
|
|
5
|
-
pythonic_fp/splitends/splitend_node.py,sha256=Fr74SpejGuLPK4giv1jKslX7NXxYe6Zh31Q3HDyAvEE,3894
|
|
6
|
-
pythonic_fp/splitends/splitend_node.pyi,sha256=3vmKwiQBsJV0v043fkA6CVPaNSFeJ02chUuwIs3G-YY,1273
|
|
7
|
-
pythonic_fp_splitends-1.0.2.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
|
|
8
|
-
pythonic_fp_splitends-1.0.2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
9
|
-
pythonic_fp_splitends-1.0.2.dist-info/METADATA,sha256=jTPGsuy0qVW39R7EgbG60p2Gvwt3yYZY6v-cV8uICkQ,2575
|
|
10
|
-
pythonic_fp_splitends-1.0.2.dist-info/RECORD,,
|
|
File without changes
|
{pythonic_fp_splitends-1.0.2.dist-info → pythonic_fp_splitends-2.0.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|