pythonic-fp.splitends 0.30.0__py3-none-any.whl → 1.0.0__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.
@@ -12,10 +12,21 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """Pythonic FP - Splitends
15
+ """Mutable stack objects that can safely share data.
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.
20
+
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"
16
29
 
17
- Package implementing a singularly linked LIFO queue called a SplitEnd. These
18
- data structures can safely share data nodes between themselves.
19
30
  """
20
31
 
21
32
  __author__ = 'Geoffrey R. Scheller'
@@ -12,57 +12,52 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
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
- """
15
+ """LIFO stacks safely sharing immutable data between themselves."""
21
16
 
22
17
  from __future__ import annotations
23
18
 
24
19
  from collections.abc import Callable, Hashable, Iterator
25
- from typing import Never, TypeVar
26
- from pythonic_fp.containers.maybe import MayBe as MB
27
- from .splitend_node import SENode as Node
20
+ from typing import TypeVar
21
+ from pythonic_fp.iterables.folding import maybe_fold_left
22
+ from .splitend_node import SENode
28
23
 
29
24
  __all__ = ['SplitEnd']
30
25
 
31
26
  D = TypeVar('D', bound=Hashable)
32
- T = TypeVar('T')
33
27
 
34
28
 
35
29
  class SplitEnd[D]:
36
- """LIFO stacks which can safely share immutable data between themselves.
37
-
38
- - each SplitEnd is a very simple stateful (mutable) LIFO stack
39
- - data can be pushed and popped to the stack
40
- - the first value pushed onto the SplitEnd becomes it "root"
41
- - different mutable split ends can safely share the same "tail"
42
- - each SplitEnd sees itself as a singularly linked list
43
- - bush-like datastructures can be formed using multiple SplitEnds
44
- - len() returns the number of elements on the SplitEnd stack
45
- - in boolean context, return true if split end is not the "root"
30
+ """Like one of many "split ends" from a shaft of hair,
31
+ a ``splitend`` can be "snipped" shorter or "extended"
32
+ further from its "tip". Its root is irremovable and
33
+ cannot be "snipped" off. While mutable, different
34
+ splitends can safely share data with each other.
35
+
46
36
  """
47
- __slots__ = '_count', '_top', '_root'
37
+ __slots__ = '_count', '_tip', '_root'
48
38
 
49
- def __init__(self, root_data: D, *ds: D) -> None:
50
- node = Node(root_data, MB[Node[D]]())
51
- self._root = MB(node)
52
- self._top, self._count = self._root, 1
53
- for d in ds:
54
- node = Node(d, self._top)
55
- self._top, self._count = MB(node), self._count + 1
39
+ def __init__(self, root_data: D, *data: D) -> None:
40
+ """
41
+ :param root_data: irremovable initial data at bottom of stack
42
+ :param data: removable data to be pushed onto splitend stack
43
+
44
+ """
45
+ node: SENode[D] = SENode(root_data)
46
+ self._root = node
47
+ self._tip, self._count = node, 1
48
+ for d in data:
49
+ node = SENode(d, self._tip)
50
+ self._tip, self._count = node, self._count + 1
56
51
 
57
52
  def __iter__(self) -> Iterator[D]:
58
- return iter(self._top.get())
53
+ return iter(self._tip)
59
54
 
60
55
  def __reversed__(self) -> Iterator[D]:
61
56
  return reversed(list(self))
62
57
 
63
58
  def __bool__(self) -> bool:
64
59
  # Returns true until all data is exhausted
65
- return bool(self._top.get())
60
+ return bool(self._tip)
66
61
 
67
62
  def __len__(self) -> int:
68
63
  return self._count
@@ -82,8 +77,8 @@ class SplitEnd[D]:
82
77
  if self._root != other._root:
83
78
  return False
84
79
 
85
- left = self._top.get()
86
- right = other._top.get()
80
+ left = self._tip
81
+ right = other._tip
87
82
  for _ in range(self._count):
88
83
  if left is right:
89
84
  return True
@@ -94,40 +89,75 @@ class SplitEnd[D]:
94
89
  right = right._prev.get()
95
90
  return True
96
91
 
97
- def push(self, *ds: D) -> None:
98
- """Push data onto the top of the SplitEnd."""
92
+ def extend(self, *ds: D) -> None:
93
+ """Add data onto the tip of the SplitEnd. Like adding a hair
94
+ extension.
95
+
96
+ :param ds: data to extend the splitend
97
+
98
+ """
99
99
  for d in ds:
100
- node = Node(d, self._top)
101
- self._top, self._count = MB(node), self._count + 1
100
+ node = SENode(d, self._tip)
101
+ self._tip, self._count = node, self._count + 1
102
+
103
+ def snip(self) -> D:
104
+ """Snip data off tip of SplitEnd. Just return data if tip is root.
105
+
106
+ :return: data snipped off tip, otherwise root data if tip is root
102
107
 
103
- def pop(self) -> D | Never:
104
- """Pop data off of the top of the SplitEnd.
105
- Re-root SplitEnd if root is popped off.
106
108
  """
107
- data, self._top, self._count = self._top.get().pop2() + (self._count - 1,)
108
- if self._count == 0:
109
- self._count, self._top = 1, self._root
109
+ if self._count > 1:
110
+ data, self._tip, self._count = self._tip.pop2() + (self._count - 1,)
111
+ else:
112
+ data = self._tip.peak()
113
+
110
114
  return data
111
115
 
112
116
  def peak(self) -> D:
113
- """Return the data at the top of the SplitEnd, doesn't consume it."""
114
- return self._top.get().peak()
117
+ """Return data from tip of SplitEnd, do not consume it.
118
+
119
+ :return: data at the end of the SplitEnd
120
+
121
+ """
122
+ return self._tip.peak()
115
123
 
116
124
  def copy(self) -> SplitEnd[D]:
117
- """Return a copy of the SplitEnd.
125
+ """Return a copy of the SplitEnd. O(1) space & time complexity.
126
+
127
+ :return: a new SplitEnd instance with same data and root
118
128
 
119
- - O(1) space & time complexity.
120
- - returns a new instance with same data, including the root
121
129
  """
122
- se: SplitEnd[D] = SplitEnd(self._root.get().peak())
123
- se._count, se._top, se._root = self._count, self._top, self._root
130
+ se: SplitEnd[D] = SplitEnd(self._root.peak())
131
+ se._count, se._tip, se._root = self._count, self._tip, self._root
124
132
  return se
125
133
 
126
- def fold[T](self, f: Callable[[T, D], T], init: T | None = None, /) -> T | Never:
127
- """Reduce with a function, fold in natural LIFO Order."""
128
- if self._top:
129
- return self._top.get().fold(f, init)
130
- if init is not None:
131
- return init
132
- msg = 'SE: Folding empty SplitEnd but no initial value supplied'
133
- raise ValueError(msg)
134
+ def fold[T](
135
+ self,
136
+ f: Callable[[T, D], T],
137
+ init: T | None = None
138
+ ) -> T:
139
+ """Reduce with a function, fold in natural LIFO Order.
140
+
141
+ :param f: folding function, for argument is for the accumulator
142
+ :param init: optional initial starting value for the fold
143
+ :return: reduced value folding from tip to root in natural LIFO order
144
+
145
+ """
146
+ return self._tip.fold(f, init)
147
+
148
+ def rev_fold[T](
149
+ self,
150
+ f: Callable[[T, D], T],
151
+ init: T | None = None
152
+ ) -> T:
153
+ """Reduce with a function, fold from root to tip.
154
+
155
+ :param f: folding function, for argument is for the accumulator
156
+ :param init: optional initial starting value for the fold
157
+ :return: reduced value folding from tip to root in natural LIFO order
158
+
159
+ """
160
+ # The get() is safe because SplitEnds are never "empty" due to the root.
161
+ if init is None:
162
+ return maybe_fold_left(reversed(self), f).get()
163
+ return maybe_fold_left(reversed(self), f, init).get()
@@ -14,19 +14,19 @@
14
14
 
15
15
  """Data node class used privately by class SplitEnd.
16
16
 
17
- Node classes used with graph-like data structures. API designed to be used by
18
- other data structures which contain these data structures.
19
- """
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
20
 
21
+ """
21
22
  from __future__ import annotations
22
23
  from collections.abc import Callable, Hashable, Iterator
23
24
  from typing import cast, TypeVar
24
- from pythonic_fp.containers.maybe import MayBe as MB
25
+ from pythonic_fp.fptools.maybe import MayBe
25
26
 
26
27
  __all__ = ['SENode']
27
28
 
28
29
  D = TypeVar('D', bound=Hashable)
29
- T = TypeVar('T')
30
30
 
31
31
 
32
32
  class SENode[D]:
@@ -34,31 +34,28 @@ class SENode[D]:
34
34
 
35
35
  - hashable data node for a end-to-root singularly linked list.
36
36
  - designed so multiple splitends can safely share the same data
37
- - this type of node always
38
-
39
- - contain data
40
- - potential link to previous node
41
-
42
- - nodes point towards a unique "bottom node" with no predecessor
43
-
44
- - in a Boolean context returns true if not at the bottom
45
- - multiple bottom nodes can exist
46
37
 
47
38
  - two nodes compare as equal if
48
39
 
49
40
  - both their previous Nodes are the same
50
- - their data compares as equal
41
+ - their data compare as equal
51
42
 
52
43
  - more than one node can point to the same proceeding node
53
44
 
54
45
  - forming bush like graphs
46
+
55
47
  """
56
48
 
57
49
  __slots__ = '_data', '_prev'
58
50
 
59
- def __init__(self, data: D, prev: MB[SENode[D]] = MB()) -> None:
51
+ def __init__(self, data: D, prev: SENode[D] | None = None) -> None:
52
+ """
53
+ :param data: nodes always contain data of type D
54
+ :param prev: potential link to a previous node
55
+
56
+ """
60
57
  self._data: D = data
61
- self._prev: MB[SENode[D]] = prev
58
+ self._prev: MayBe[SENode[D]] = MayBe(prev) if prev is not None else MayBe()
62
59
 
63
60
  def __iter__(self) -> Iterator[D]:
64
61
  node = self
@@ -68,7 +65,7 @@ class SENode[D]:
68
65
  yield node._data
69
66
 
70
67
  def __bool__(self) -> bool:
71
- return self._prev != MB()
68
+ return self._prev != MayBe()
72
69
 
73
70
  def __eq__(self, other: object) -> bool:
74
71
  if not isinstance(other, type(self)):
@@ -84,19 +81,35 @@ class SENode[D]:
84
81
  """Return contained data"""
85
82
  return self._data
86
83
 
87
- def pop2(self) -> tuple[D, MB[SENode[D]]]:
88
- """Return the data at the *end* and potential *tail*."""
89
- return self._data, self._prev
84
+ def pop2(self) -> tuple[D, SENode[D]]:
85
+ """Return the data at the tip and the tail of the senode."""
86
+ if self._prev:
87
+ return self._data, self._prev.get()
88
+ return self._data, self
90
89
 
91
90
  def push(self, data: D) -> SENode[D]:
92
91
  """Push data onto the queue and return a new node containing the data."""
93
- return SENode(data, MB(self))
92
+ return SENode(data, self)
93
+
94
+ def fold[T](
95
+ self,
96
+ f: Callable[[T, D], T],
97
+ init: T | None = None
98
+ ) -> T:
99
+ """Fold data across linked nodes with a function..
100
+
101
+ .. code:: python
102
+
103
+ def fold[T](
104
+ self,
105
+ f: Callable([T, D], T],
106
+ init: T | None = None
107
+ ) -> T
94
108
 
95
- def fold[T](self, f: Callable[[T, D], T], init: T | None = None) -> T:
96
- """Reduce data across linked nodes.
109
+ :param f: folding function, first argument is for accumulated value
110
+ :param init: optional initial starting value for the fold
111
+ :return: reduced value folding from end to root in natural LIFO order
97
112
 
98
- - with a function and an optional starting value
99
- - reduces in natural LIFO order, from self to the root
100
113
  """
101
114
  if init is None:
102
115
  acc: T = cast(T, self._data)
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: pythonic-fp-splitends
3
+ Version: 1.0.0
4
+ Summary: Mutable stack objects that can safely share data.
5
+ Keywords: SplitEnd,lifo,stack
6
+ Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/x-rst
9
+ Classifier: Development Status :: 3 - Alpha
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
+ License-File: LICENSE
17
+ Requires-Dist: pythonic-fp-iterables>=5.0.0
18
+ Requires-Dist: pythonic-fp-fptools>=5.0.0
19
+ Requires-Dist: pytest>=8.4.1 ; extra == "test"
20
+ Requires-Dist: pythonic-fp>=1.1.0 ; extra == "test"
21
+ Requires-Dist: pythonic-fp-singletons>=1.0.0 ; extra == "test"
22
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-splitends/blob/main/CHANGELOG.rst
23
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/splitends/development/build/html/releases.html
24
+ Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
25
+ Project-URL: Source, https://github.com/grscheller/pythonic-fp-splitends
26
+ Provides-Extra: test
27
+
28
+ Pythonic FP - SplitEnds
29
+ =======================
30
+
31
+ PyPI project
32
+ `pythonic-fp.splitends <https://pypi.org/project/pythonic-fp.splitends/>`_.
33
+
34
+ Python package Implementing a singularly linked LIFO queue called
35
+ a ``SplitEnd``. These data structures can safely share data nodes
36
+ between themselves.
37
+
38
+ - each ``SplitEnd`` is a very simple stateful (mutable) LIFO stack
39
+ - data can be "extended" to or "snipped" off of the end (tip)
40
+ - the "root" value of a ``SplitEnd`` is fixed and cannot be "snipped"
41
+ - different mutable split ends can safely share the same "tail"
42
+ - each ``SplitEnd`` sees itself as a singularly linked list
43
+ - bush-like datastructures can be formed using multiple ``SplitEnds``
44
+ - the ``SplitEnd`` copy method and ``len`` are O(1)
45
+ - in boolean context returns true if the ``SplitEnd`` is not just a "root"
46
+
47
+ This PyPI project is part of of the grscheller
48
+ `pythonic-fp namespace projects <https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
49
+
50
+ **Warning:** This PyPI project is Alpha level software and subject to
51
+ change.
52
+
53
+ Documentation
54
+ -------------
55
+
56
+ Documentation for this project is hosted on
57
+ `GitHub Pages
58
+ <https://grscheller.github.io/pythonic-fp/splitends/development/build/html>`_.
59
+
60
+ Copyright and License
61
+ ---------------------
62
+
63
+ Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
64
+ License, Version 2.0. See the LICENSE file for details.
65
+
@@ -0,0 +1,8 @@
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=Th4aLzzS_fvIzt_zNn9Y3Dtb1gSJ9ZFI9QV-zQ-FEDg,5139
4
+ pythonic_fp/splitends/splitend_node.py,sha256=YnXrbju5MW6wEjjDfGEmwZ_hjbaYWDi1p7zfM8cC_ug,3682
5
+ pythonic_fp_splitends-1.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
6
+ pythonic_fp_splitends-1.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
7
+ pythonic_fp_splitends-1.0.0.dist-info/METADATA,sha256=TivzYvGfOjBAn-vNUmXsRLxowyRrgE_8g_RU3QhEIr8,2630
8
+ pythonic_fp_splitends-1.0.0.dist-info/RECORD,,
@@ -1,55 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: pythonic-fp.splitends
3
- Version: 0.30.0
4
- Summary: Pythonic FP - Splitends
5
- Keywords: SplitEnd,lifo,stack
6
- Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
- Requires-Python: >=3.12
8
- Description-Content-Type: text/markdown
9
- Classifier: Development Status :: 3 - Alpha
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
- License-File: LICENSE
17
- Requires-Dist: pythonic-fp.containers >=2.0.0
18
- Project-URL: Changelog, https://grscheller.github.io/pythonic-fp-splitends/html/changelog.html
19
- Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-splitends/html/
20
- Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
21
- Project-URL: Source, https://github.com/grscheller/pythonic-fp-splitends
22
-
23
- # Pythonic FP - SplitEnds
24
-
25
- PyPI project
26
- [pythonic-fp.splitends](https://pypi.org/project/pythonic-fp.splitends/)
27
- implementing a singularly linked data structures allowing data to be
28
- safely shared between multiple instances.
29
-
30
- - Singularly link stack with shareable data nodes
31
- - *module* `pythonic_fp.splitends.splitend`
32
- - *class* `SplitEnd`
33
-
34
- - Shareable nodes used by SplitEnd instances
35
- - *module* `pythonic_fp.splitends.splitend_node`
36
- - *class* `SENode`
37
- - shareable nodes used by SplitEnd instances
38
- - this class is designed to be inaccessible to client code
39
-
40
- This PyPI project is part of of the grscheller
41
- [pythonic-fp namespace projects](https://github.com/grscheller/pythonic-fp/blob/main/README.md).
42
-
43
- **Warning:** This PyPI project is Alpha level software and subject to
44
- change. It will probably remain Alpha for quite a while.
45
-
46
- ## Documentation
47
-
48
- Documentation hosted on
49
- [GitHub Pages](https://grscheller.github.io/pythonic-fp-splitends/html).
50
-
51
- ## Copyright and License
52
-
53
- Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
54
- License, Version 2.0. See the LICENSE file for details.
55
-
@@ -1,8 +0,0 @@
1
- pythonic_fp/splitends/__init__.py,sha256=hAswthv0JMpJlJqid_hwgIO1OEYRqGSRKcACKBdvmUw,898
2
- pythonic_fp/splitends/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- pythonic_fp/splitends/splitend.py,sha256=C4Zw_s02oAo9ivItinbeavJWFSuyWRx8QdLxa9MQAe0,4608
4
- pythonic_fp/splitends/splitend_node.py,sha256=T0bAGMY4dL8Vv5w5z6BYX6pUrNQhXHaLVZo2XTtmvFI,3362
5
- pythonic_fp_splitends-0.30.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
6
- pythonic_fp_splitends-0.30.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
7
- pythonic_fp_splitends-0.30.0.dist-info/METADATA,sha256=nYdAplEfe5m2LH5X2Yc1fOIZ3lPijnVa-jbfzjJ4vUs,2091
8
- pythonic_fp_splitends-0.30.0.dist-info/RECORD,,