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.
@@ -1,4 +1,4 @@
1
- # Copyright 2024-2025 Geoffrey R. Scheller
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 Implementing a singularly linked LIFO queue
18
- called a ``SplitEnd``. These data structures can safely share
19
- data nodes between themselves.
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
- - each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
22
- - data can be "extended" to or "snipped" off of the end (tip)
23
- - the "root" value of a ``SplitEnd`` is fixed and cannot be "snipped"
24
- - different mutable split ends can safely share the same "tail"
25
- - each ``SplitEnd`` sees itself as a singularly linked list
26
- - bush-like datastructures can be formed using multiple ``SplitEnds``
27
- - the ``SplitEnd`` copy method and ``len`` are O(1)
28
- - in boolean context returns true if the ``SplitEnd`` is not just a "root"
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-2025 Geoffrey R. Scheller'
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-2025 Geoffrey R. Scheller
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
- """LIFO stacks safely sharing immutable data between themselves."""
15
+ """
16
+ Class SplitEnds
17
+ ---------------
16
18
 
17
- from collections.abc import Callable, Hashable, Iterator
18
- from typing import TypeVar
19
- from pythonic_fp.iterables.folding import maybe_fold_left
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
- D = TypeVar('D', bound=Hashable)
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
- """Like one of many "split ends" from a shaft of hair,
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, root_data: D, *data: D) -> None:
53
+ def __init__(self, *ds: D, root: SENode[D] | _Sentinel = _sentinel) -> None:
39
54
  """
40
- :param root_data: irremovable initial data at bottom of stack
41
- :param data: removable data to be pushed onto splitend stack
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
- node: SENode[D] = SENode(root_data)
45
- self._root = node
46
- self._tip, self._count = node, 1
47
- for d in data:
48
- node = SENode(d, self._tip)
49
- self._tip, self._count = node, self._count + 1
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._tip)
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
- # Returns true until all data is exhausted
59
- return bool(self._tip)
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._tip
80
- right = other._tip
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.peak() != right.peak():
116
+ if left.data() != right.data():
85
117
  return False
86
118
  if left:
87
- left = left._prev.get()
88
- right = right._prev.get()
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._tip)
100
- self._tip, self._count = node, self._count + 1
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: data snipped off tip, otherwise root data if tip is root
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._tip, self._count = self._tip.pop2() + (self._count - 1,)
215
+ data, self._end, self._count = self._end.both() + (self._count - 1,)
110
216
  else:
111
- data = self._tip.peak()
217
+ data = self._end.data()
112
218
 
113
219
  return data
114
220
 
115
- def peak(self) -> D:
116
- """Return data from tip of SplitEnd, do not consume it.
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.peak())
130
- se._count, se._tip, se._root = self._count, self._tip, self._root
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, fold in natural LIFO Order.
238
+ """Reduce with a function, folding from tip to root.
135
239
 
136
- :param f: folding function, for argument is for the accumulator
137
- :param init: optional initial starting value for the fold
138
- :returns: reduced value folding from tip to root in natural LIFO order
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
- return self._tip.fold(f, init)
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: folding function, for argument is for the accumulator
147
- :param init: optional initial starting value for the fold
148
- :returns: reduced value folding from tip to root in natural LIFO order
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 maybe_fold_left(reversed(self), f).get()
154
- return maybe_fold_left(reversed(self), f, init).get()
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
- # 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
- 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
- D = TypeVar('D', bound=Hashable)
21
-
8
+ type _SecretType = tuple[str, str, str]
9
+ type _Sentinel = Sentinel[_SecretType]
22
10
  class SplitEnd[D]:
23
- __slots__ = '_count', '_tip', '_root'
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 __repr__(self) -> str: ...
31
- def __str__(self) -> str: ...
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 copy(self) -> SplitEnd[D]: ...
37
- def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T: ...
38
- def rev_fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T: ...
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-2025 Geoffrey R. Scheller
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
- from collections.abc import Callable, Hashable, Iterator
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: 'SENode[D] | None' = None) -> None:
52
+ def __init__(self, data: D, prev: Self | _Sentinel = _sentinel) -> None:
52
53
  """
53
- :param data: nodes always contain data of type D
54
- :param prev: potential link to a previous node
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
- self._data: D = data
58
- self._prev: MayBe[SENode[D]] = MayBe(prev) if prev is not None else MayBe()
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.get()
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 != other._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 peak(self) -> D:
81
- """Returns contained data.
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: data stored in SENode
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 pop2(self) -> 'tuple[D, SENode[D]]':
89
- """Return the data at the tip and the tail of the SENode.
99
+ def data(self) -> D:
100
+ """Peak at data.
90
101
 
91
- :returns: a tuple of the data at the tip and the previous SENode
102
+ :returns: The data stored in the ``SENode``.
92
103
 
93
104
  """
94
- if self._prev:
95
- return self._data, self._prev.get()
96
- return self._data, self
105
+ return self._data
97
106
 
98
- def push(self, data: D) -> 'SENode[D]':
99
- """Push data onto the queue and return a new node containing the data.
107
+ def prev(self) -> Self:
108
+ """Peak at previous node.
100
109
 
101
- :param data: data to be pushed onto the SENode stack
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 SENode(data, self)
113
+ return self._prev
106
114
 
107
- def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
108
- """Fold data across linked nodes with a function..
115
+ def push(self, data: D) -> Self:
116
+ """Create a new ``SENode[D]``.
109
117
 
110
- .. code:: python
118
+ :param data: Data for new node to contain.
119
+ :returns: New ``SENode`` whose previous node is the current node.
111
120
 
112
- def fold[T](
113
- self,
114
- f: Callable([T, D], T],
115
- init: T | None = None
116
- ) -> T
121
+ """
122
+ return cast(Self, SENode(data, self))
117
123
 
118
- :param f: folding function, first argument is for accumulated value
119
- :param init: optional initial starting value for the fold
120
- :returns: reduced value folding from end to root in natural LIFO order
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 None:
124
- acc: T = cast(T, self._data)
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.get()
146
+ node = node._prev
133
147
  acc = f(acc, node._data)
134
148
  return acc
@@ -1,36 +1,20 @@
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
- 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
- D = TypeVar('D', bound=Hashable)
22
-
7
+ type _Sentinel = Sentinel[str]
23
8
  class SENode[D]:
24
- __slots__ = '_data', '_prev'
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 peak(self) -> D: ...
34
- def pop2(self) -> tuple[D, SENode[D]]: ...
35
- def push(self, data: D) -> SENode[D]: ...
36
- def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T: ...
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: 1.0.2
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.1.1
18
- Requires-Dist: pythonic-fp-iterables>=5.1.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/releases.html
23
- Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
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
- This PyPI project is part of of the grscheller
47
- `pythonic-fp namespace projects <https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
48
-
49
- **Warning:** This PyPI project is Alpha level software and subject to
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/development/build/html>`_.
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,,