pythonic-fp.splitends 2.0.2__tar.gz → 3.1.0__tar.gz

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,38 +1,44 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pythonic-fp-splitends
3
- Version: 2.0.2
4
- Summary: Mutable stack objects that can safely share data.
3
+ Version: 3.1.0
4
+ Summary: Mutable stack datastructures that can safely share data.
5
5
  Keywords: SplitEnd,lifo,stack
6
6
  Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
- Requires-Python: >=3.13
8
- Description-Content-Type: text/x-rst
7
+ Requires-Python: >=3.14
8
+ Description-Content-Type: text/markdown
9
9
  Classifier: Development Status :: 3 - Alpha
10
10
  Classifier: Framework :: Pytest
11
11
  Classifier: Intended Audience :: Developers
12
12
  Classifier: License :: OSI Approved :: Apache Software License
13
13
  Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
15
16
  Classifier: Typing :: Typed
16
17
  License-File: LICENSE
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
20
- Requires-Dist: pytest>=8.4.1 ; extra == "test"
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://grscheller.github.io/pythonic-fp/homepage/build/html/
18
+ Requires-Dist: pythonic-fp-fptools>=6.1,<6.2
19
+ Requires-Dist: pythonic-fp-iterables>=5.1.4,<6
20
+ Requires-Dist: pythonic-fp-queues>=5.1.3,<6
21
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.md
22
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-splitends/release/html/
23
+ Project-URL: Homepage, https://grscheller.github.io/pythonic-fp/
24
24
  Project-URL: Source, https://github.com/grscheller/pythonic-fp-splitends
25
- Provides-Extra: test
26
25
 
27
- Pythonic FP - SplitEnds
28
- =======================
26
+ # Pythonic FP - SplitEnds
27
+
28
+ Package Implementing a singularly linked LIFO queue called
29
+ a `SplitEnd`. These data structures can safely share data nodes
30
+ between themselves.
29
31
 
30
32
  PyPI project
31
- `pythonic-fp.splitends <https://pypi.org/project/pythonic-fp.splitends/>`_.
33
+ [pythonic-fp.splitends](https://pypi.org/project/pythonic-fp-splitends/).
34
+ Part of the
35
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
36
+ PyPI projects.
32
37
 
33
- Python package Implementing a singularly linked LIFO queue called
34
- a ``SplitEnd``. These data structures can safely share data nodes
35
- between themselves.
38
+ ## Documentation
39
+
40
+ Documentation and other links for this project are hosted on
41
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/splitends.html).
36
42
 
37
43
  - each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
38
44
  - data can be "extended" to or "snipped" off of the end (tip)
@@ -43,21 +49,8 @@ between themselves.
43
49
  - the ``SplitEnd`` copy method and ``len`` are O(1)
44
50
  - in boolean context returns true if the ``SplitEnd`` is not just a "root"
45
51
 
46
- Part of the
47
- `pythonic-fp
48
- <https://grscheller.github.io/pythonic-fp>`_
49
- PyPI projects.
50
-
51
- Documentation
52
- -------------
53
-
54
- Documentation for this project is hosted on
55
- `GitHub Pages
56
- <https://grscheller.github.io/pythonic-fp/splitends>`_.
57
-
58
- Copyright and License
59
- ---------------------
52
+ ## Copyright and License
60
53
 
61
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
54
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
62
55
  License, Version 2.0. See the LICENSE file for details.
63
56
 
@@ -1,12 +1,19 @@
1
- Pythonic FP - SplitEnds
2
- =======================
1
+ # Pythonic FP - SplitEnds
2
+
3
+ Package Implementing a singularly linked LIFO queue called
4
+ a `SplitEnd`. These data structures can safely share data nodes
5
+ between themselves.
3
6
 
4
7
  PyPI project
5
- `pythonic-fp.splitends <https://pypi.org/project/pythonic-fp.splitends/>`_.
8
+ [pythonic-fp.splitends](https://pypi.org/project/pythonic-fp-splitends/).
9
+ Part of the
10
+ [pythonic-fp](https://grscheller.github.io/pythonic-fp/)
11
+ PyPI projects.
6
12
 
7
- Python package Implementing a singularly linked LIFO queue called
8
- a ``SplitEnd``. These data structures can safely share data nodes
9
- between themselves.
13
+ ## Documentation
14
+
15
+ Documentation and other links for this project are hosted on
16
+ [GitHub Pages](https://grscheller.github.io/pythonic-fp/projects/splitends.html).
10
17
 
11
18
  - each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
12
19
  - data can be "extended" to or "snipped" off of the end (tip)
@@ -17,20 +24,7 @@ between themselves.
17
24
  - the ``SplitEnd`` copy method and ``len`` are O(1)
18
25
  - in boolean context returns true if the ``SplitEnd`` is not just a "root"
19
26
 
20
- Part of the
21
- `pythonic-fp
22
- <https://grscheller.github.io/pythonic-fp>`_
23
- PyPI projects.
24
-
25
- Documentation
26
- -------------
27
-
28
- Documentation for this project is hosted on
29
- `GitHub Pages
30
- <https://grscheller.github.io/pythonic-fp/splitends>`_.
31
-
32
- Copyright and License
33
- ---------------------
27
+ ## Copyright and License
34
28
 
35
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
29
+ Copyright (c) 2023-2026 Geoffrey R. Scheller. Licensed under the Apache
36
30
  License, Version 2.0. See the LICENSE file for details.
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["flit_core>=3.12,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "pythonic-fp-splitends"
7
+ version = "3.1.0"
8
+ description = "Mutable stack datastructures that can safely share data."
9
+ readme = "README.md"
10
+ requires-python = ">=3.14" # floor: PEP 649/749 deferred annotations — class-level self-reference possible NameErrors on <3.14
11
+ authors = [
12
+ { name = "Geoffrey R. Scheller", email = "geoffrey@scheller.com" },
13
+ ]
14
+ keywords = [
15
+ "SplitEnd",
16
+ "lifo",
17
+ "stack",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 3 - Alpha",
21
+ "Framework :: Pytest",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: Apache Software License",
24
+ "Operating System :: OS Independent",
25
+ "Programming Language :: Python :: 3.14",
26
+ "Programming Language :: Python :: 3.15",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = [
30
+ "pythonic-fp-fptools>=6.1,<6.2",
31
+ "pythonic-fp-iterables>=5.1.4,<6",
32
+ "pythonic-fp-queues>=5.1.3,<6",
33
+ ]
34
+
35
+ [project.urls]
36
+ Changelog = "https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.md"
37
+ Documentation ="https://grscheller.github.io/pythonic-fp-splitends/release/html/"
38
+ Homepage = "https://grscheller.github.io/pythonic-fp/"
39
+ Source = "https://github.com/grscheller/pythonic-fp-splitends"
40
+
41
+ [dependency-groups]
42
+ docs = [
43
+ "furo>=2025.12.19",
44
+ "sphinx>=9.1",
45
+ ]
46
+ test = [
47
+ "pytest>=8.4.1",
48
+ ]
49
+
50
+ [tool.flit.module]
51
+ name = "pythonic_fp.splitends"
52
+
53
+ [tool.flit.sdist]
54
+ exclude = [
55
+ "docs/",
56
+ "tests/",
57
+ "**/.mypy_cache/",
58
+ "**/.ruff_cache/",
59
+ "**/.pytest_cache/",
60
+ "**/__pycache__/",
61
+ "**/.dmypy.json",
62
+ ]
63
+
64
+ [tool.pytest]
65
+ addopts = ["-ra"]
66
+ consider_namespace_packages = true
67
+ testpaths = ["tests/"]
68
+
69
+ [tool.mypy]
70
+ explicit_package_bases = true
71
+ local_partial_types = true
72
+ namespace_packages = true
73
+ warn_redundant_casts = true
74
+ warn_return_any = true
75
+ warn_unused_configs = true
76
+
77
+ [tool.ruff]
78
+ target-version = "py314"
79
+
80
+ [tool.ruff.lint]
81
+ ignore = ["RUF022", "E741"]
82
+
83
+ [tool.ruff.lint.flake8-quotes]
84
+ docstring-quotes = "double"
85
+
86
+ [tool.ruff.lint.per-file-ignores]
87
+ "tests/**/*.py" = [
88
+ "E731",
89
+ "C901",
90
+ ]
91
+
92
+ [tool.ruff.format]
93
+ quote-style = "single"
94
+ docstring-code-line-length = 72
@@ -0,0 +1,41 @@
1
+ # Copyright 2024-2026 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
+ .. admonition:: Mutable stack objects that can safely share data
17
+
18
+ Python package implementing a singularly linked stack of nodes called a ``SplitEnd``.
19
+ While mutable, these data structures can safely share immutable data nodes between
20
+ themselves. These nodes, of type ``SENode``, always contain data. A root node is
21
+ a node whose previous node is itself. Root nodes mark the bottom of the stack.
22
+
23
+ Like one of many "split ends" from shafts of hair, a ``SplitEnd`` can be "snipped"
24
+ shorter or "extended" further from its "end" node at the top of the stack. Its "root"
25
+ node is at the bottom of the stack and can not be "snipped" or "cut" off.
26
+
27
+ A ``SplitEnd`` can be duplicated onto a compatible root node. By compatible, the
28
+ new root must contain data that compares as equal to the old root node.
29
+
30
+ .. admonition:: Future directions
31
+
32
+ A ``scalp`` is a container of root nodes all of whose contained data must
33
+ be hashable. The ``scalp.add(d)`` method will return the unique root node with data
34
+ that compares as equal to ``d``, or create a new root node if such a contained node
35
+ does not exist.
36
+
37
+ """
38
+
39
+ __author__ = 'Geoffrey R. Scheller'
40
+ __copyright__ = 'Copyright (c) 2023-2026 Geoffrey R. Scheller'
41
+ __license__ = 'Apache License 2.0'
@@ -19,16 +19,20 @@ Class SplitEnds
19
19
  .. admonition:: LIFO stacks safely sharing immutable data.
20
20
 
21
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``
22
+ - data can be either "extended" to or "snipped" off the "end"
23
+ - "root" of a ``SplitEnd`` is embedded in a ``scalp``
24
24
 
25
- - it is fixed and cannot be removed from the ``SplitEnd``
25
+ - the root data is immutable
26
+ - it cannot be split off
26
27
 
27
28
  - different mutable split ends can safely share the same "tail"
28
29
  - each ``SplitEnd`` sees itself as a singularly linked list
29
30
  - bush-like datastructures can be formed using multiple ``SplitEnds``
30
31
  - the ``SplitEnd.split`` and ``len`` methods are O(1)
31
- - in boolean context returns true if the ``SplitEnd`` is not just its "root"
32
+ - in a boolean context
33
+
34
+ - falsy just a "root"
35
+ - truthy otherwise
32
36
 
33
37
  """
34
38
 
@@ -52,8 +56,10 @@ class SplitEnd[D]:
52
56
 
53
57
  def __init__(self, *ds: D, root: SENode[D] | _Sentinel = _sentinel) -> None:
54
58
  """
55
- :param root_data: Irremovable initial data at bottom of stack.
56
- :param data: Removable data to be pushed onto splitend stack.
59
+ .. admonition:: init
60
+
61
+ :param root_data: Irremovable initial data at bottom of stack.
62
+ :param ds: Removable data to be pushed onto splitend stack.
57
63
 
58
64
  """
59
65
  if root is _sentinel:
@@ -78,28 +84,57 @@ class SplitEnd[D]:
78
84
  self._end, self._root, self._count = end, root_node, count
79
85
 
80
86
  def __iter__(self) -> Iterator[D]:
87
+ """
88
+ .. admonition:: iter
89
+
90
+ Iterate from end to root.
91
+
92
+ :yields: data from end to root
93
+
94
+ """
81
95
  return iter(self._end)
82
96
 
83
97
  def __reversed__(self) -> Iterator[D]:
98
+ """
99
+ .. admonition:: reverse iter
100
+
101
+ Iterate from end to root.
102
+
103
+ :yields: data from end to root
104
+
105
+ """
84
106
  return reversed(list(self))
85
107
 
86
108
  def __bool__(self) -> bool:
87
109
  """
88
- :returns: ``True`` is ``SplitEnd`` is not just its root node.
110
+ .. admonition:: bool
111
+
112
+ :returns: ``True`` if ``SplitEnd`` is not just its root node.
89
113
 
90
114
  """
91
115
  return bool(self._end)
92
116
 
93
117
  def __len__(self) -> int:
94
- return self._count
118
+ """
119
+ .. admonition:: len
95
120
 
96
- def __repr__(self) -> str:
97
- return 'SplitEend(' + ', '.join(map(repr, reversed(self))) + ')'
121
+ :returns: The number of nodes on the ``Splitend``.
98
122
 
99
- def __str__(self) -> str:
100
- return '>< ' + ' -> '.join(map(str, self)) + ' ||'
123
+ """
124
+ return self._count
101
125
 
102
126
  def __eq__(self, other: object, /) -> bool:
127
+ """
128
+ .. admonition:: equality comparison
129
+
130
+ Efficiently compare two ``SplitEnds``.
131
+
132
+ :returns: ``True`` only if ``other`` is also a ``Splitend``
133
+ whose corresponding data to ``self`` compares as
134
+ equal and both share the same root node.
135
+ Otherwise ``False``.
136
+
137
+ """
103
138
  if not isinstance(other, type(self)):
104
139
  return False
105
140
 
@@ -120,11 +155,54 @@ class SplitEnd[D]:
120
155
  right = right.prev()
121
156
  return True
122
157
 
158
+ def __repr__(self) -> str:
159
+ """
160
+ .. admonition:: repr string
161
+
162
+ Construct string to reproduce the ``SplitEnd``.
163
+ TODO: Fix, does not include root node.
164
+
165
+ :returns: String to reproduce the ``SplitEnd``.
166
+
167
+ """
168
+ return 'SplitEend(' + ', '.join(map(repr, reversed(self))) + ')'
169
+
170
+ def __str__(self) -> str:
171
+ """
172
+ .. admonition:: user string
173
+
174
+ Construct string meaningful to an end user.
175
+ Does not give root node info.
176
+
177
+ :returns: String to reproduce the ``SplitEnd``.
178
+
179
+ """
180
+ return '>< ' + ' -> '.join(map(str, self)) + ' ||'
181
+
182
+ def snip(self) -> D:
183
+ """
184
+ .. admonition:: cut just tip
185
+
186
+ :returns: Data from snipped tip,
187
+ just return root data if at root.
188
+
189
+ """
190
+ if self._count > 1:
191
+ data, self._end, self._count = self._end.both() + (self._count - 1,)
192
+ else:
193
+ data = self._end.data()
194
+
195
+ return data
196
+
123
197
  def cut(self, num: int | None = None) -> tuple[D, ...]:
124
- """Cut data off end of ``SplitEnd``.
198
+ """
199
+ .. admonition:: Cut data off end of SplitEnd
200
+
201
+ Pop "cut" data off Split, just copy root data.
125
202
 
126
- :param num: Optional number of nodes to cut, default is entire stack.
127
- :returns: Tuple of data cut off from end.
203
+ :param num: Optional number of nodes to cut,
204
+ default is entire stack.
205
+ :returns: Tuple of data cut off from end towards root.
128
206
 
129
207
  """
130
208
  if num is None or num > self._count:
@@ -147,10 +225,13 @@ class SplitEnd[D]:
147
225
  return data
148
226
 
149
227
  def extend(self, *ds: D) -> None:
150
- """Add data onto the tip of the SplitEnd. Like adding a hair
151
- extension.
228
+ """
229
+ .. admonition:: extend SplitEnd with data
152
230
 
153
- :param ds: data to extend the splitend
231
+ Add data onto the tip of the SplitEnd. Like adding
232
+ a hair extension.
233
+
234
+ :param ds: data to extend the splitend
154
235
 
155
236
  """
156
237
  for d in ds:
@@ -158,37 +239,48 @@ class SplitEnd[D]:
158
239
  self._end, self._count = node, self._count + 1
159
240
 
160
241
  def peak(self) -> D:
161
- """Return the data at end (top) of SplitEnd without consuming it.
242
+ """
243
+ .. admonition:: peak at end
162
244
 
163
- :returns: The data at the end of the SplitEnd.
245
+ Return the data at end (top) of the SplitEnd
246
+ without consuming it.
247
+
248
+ :returns: The data at the end (tip) of the SplitEnd.
164
249
 
165
250
  """
166
251
  return self._end.data()
167
252
 
168
253
  def root(self) -> SENode[D]:
169
254
  """
170
- :returns: The root SENode node of the SplitEnd.
255
+ .. admonition:: get root
256
+
257
+ :returns: The root SENode node of the SplitEnd.
171
258
 
172
259
  """
173
260
  return self._root
174
261
 
175
262
  def reroot(self, root: SENode[D]) -> 'SplitEnd[D]':
176
- """Create a brand new SplitEnd with the same data but different root.
263
+ """
264
+ .. admonition:: re-root SplitEnd
177
265
 
178
- .. note::
266
+ Create a brand new SplitEnd with the same data but different root.
179
267
 
180
- Two nodes are compatible root nodes if and only if
268
+ :returns: New SplitEnd with the same data
269
+ and the new ``root``.
270
+ :raises ValueError: If new and original root nodes
271
+ are not compatible.
181
272
 
182
- - they are both actually root nodes
273
+ .. note::
183
274
 
184
- - which implies that their previous nodes are themselves
275
+ Two nodes are compatible root nodes if and only if
185
276
 
186
- - their data compare as equal
277
+ - they are both actually root nodes
187
278
 
188
- - comparing by identity is too strong for some use cases
279
+ - which implies that their previous nodes are themselves
189
280
 
190
- :returns: New SplitEnd with the same data and the new ``root``.
191
- :raises ValueError: If new and original root nodes are not compatible.
281
+ - their data compare as equal
282
+
283
+ - comparing by identity is too strong
192
284
 
193
285
  """
194
286
  if not root:
@@ -205,23 +297,14 @@ class SplitEnd[D]:
205
297
 
206
298
  return SplitEnd(*lifo, root = root)
207
299
 
208
- def snip(self) -> D:
209
- """Snip data off tip of SplitEnd. Just return data if tip is root.
210
-
211
- :returns: Data snipped off tip, just return root data if at root.
212
-
300
+ def split(self, *ds: D) -> 'SplitEnd[D]':
213
301
  """
214
- if self._count > 1:
215
- data, self._end, self._count = self._end.both() + (self._count - 1,)
216
- else:
217
- data = self._end.data()
302
+ .. admonition:: split
218
303
 
219
- return data
220
-
221
- def split(self, *ds: D) -> 'SplitEnd[D]':
222
- """Split the end and add more data.
304
+ Split the end and add more data.
223
305
 
224
- :returns: New instance, same data nodes plus additional ones on end.
306
+ :returns: New instance, same data nodes plus
307
+ additional ones on end. Same root node.
225
308
 
226
309
  """
227
310
  se: SplitEnd[D] = SplitEnd(self._root.data())
@@ -235,11 +318,16 @@ class SplitEnd[D]:
235
318
  def fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
236
319
 
237
320
  def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
238
- """Reduce with a function, folding from tip to root.
321
+ """
322
+ .. admonition:: fold
323
+
324
+ Reduce with a function, folding from tip to root.
239
325
 
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.
326
+ :param f: Folding function, first argument
327
+ is for the accumulator.
328
+ :param init: Optional initial starting value for the fold.
329
+ :returns: Reduced value folding from tip to root
330
+ in natural LIFO order.
243
331
 
244
332
  """
245
333
  if init is None:
@@ -252,11 +340,15 @@ class SplitEnd[D]:
252
340
  def rev_fold[T](self, f: Callable[[T, D], T], init: T) -> T: ...
253
341
 
254
342
  def rev_fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
255
- """Reduce with a function, fold from root to tip.
343
+ """
344
+ .. admonition:: reverse fold
345
+
346
+ Reduce with a function, fold from root to tip.
256
347
 
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.
348
+ :param f: Folding function, first argument
349
+ is for the accumulator.
350
+ :param init: Optional initial starting value for the fold.
351
+ :returns: Reduced value folding from root to tip.
260
352
 
261
353
  """
262
354
  if init is None: