pythonic-fp.containers 2.0.0__py3-none-any.whl → 3.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.
@@ -0,0 +1,145 @@
1
+ # Copyright 2023-2024 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Stateful First-In-First-Out (FIFO) Queue data structure.
16
+
17
+ - O(1) pops
18
+ - O(1) amortized pushes
19
+ - in a Boolean context, true if not empty, false if empty
20
+ - will automatically increase storage capacity when needed
21
+ - neither indexable nor sliceable by design
22
+ - O(1) length determination
23
+
24
+ """
25
+ from __future__ import annotations
26
+
27
+ from collections.abc import Callable, Iterable, Iterator
28
+ from typing import TypeVar
29
+ from pythonic_fp.circulararray.auto import CA
30
+ from pythonic_fp.fptools.maybe import MayBe
31
+
32
+ __all__ = ['FIFOQueue', 'fifo_queue']
33
+
34
+ D = TypeVar('D')
35
+
36
+
37
+ class FIFOQueue[D]:
38
+
39
+ __slots__ = ('_ca',)
40
+
41
+ def __init__(self, *dss: Iterable[D]) -> None:
42
+ """Initial data in natural FIFO order, newest to oldest.
43
+
44
+ :param dss: takes up to one iterable
45
+ :raises ValueError: if more than 1 iterable is given
46
+
47
+ """
48
+ if (size := len(dss)) > 1:
49
+ msg = f'FIFOQueue expects at most 1 iterable argument, got {size}'
50
+ raise ValueError(msg)
51
+ self._ca = CA(dss[0]) if size == 1 else CA()
52
+
53
+ def __bool__(self) -> bool:
54
+ return len(self._ca) > 0
55
+
56
+ def __len__(self) -> int:
57
+ return len(self._ca)
58
+
59
+ def __eq__(self, other: object) -> bool:
60
+ if not isinstance(other, FIFOQueue):
61
+ return False
62
+ return self._ca == other._ca
63
+
64
+ def __iter__(self) -> Iterator[D]:
65
+ return iter(list(self._ca))
66
+
67
+ def __repr__(self) -> str:
68
+ if len(self) == 0:
69
+ return 'FIFOQueue()'
70
+ return 'FIFOQueue(' + ', '.join(map(repr, self._ca)) + ')'
71
+
72
+ def __str__(self) -> str:
73
+ return '<< ' + ' < '.join(map(str, self)) + ' <<'
74
+
75
+ def copy(self) -> FIFOQueue[D]:
76
+ """Shallow copy.
77
+
78
+ :return: shallow copy of the FIFOQueue
79
+
80
+ """
81
+ return FIFOQueue(self._ca)
82
+
83
+ def push(self, *ds: D) -> None:
84
+ """Push data onto FIFOQueue.
85
+
86
+ :param ds: data items to be pushed onto FIFOQueue
87
+
88
+ """
89
+ self._ca.pushr(*ds)
90
+
91
+ def pop(self) -> MayBe[D]:
92
+ """Pop oldest data item off of FIFOQueue.
93
+
94
+ :return: MayBe of popped data item if queue was not empty, empty MayBe otherwise
95
+
96
+ """
97
+ if self._ca:
98
+ return MayBe(self._ca.popl())
99
+ return MayBe()
100
+
101
+ def peak_last_in(self) -> MayBe[D]:
102
+ """Peak at newest data item on queue.
103
+
104
+ :return: MayBe of newest data item on queue, empty MayBe if queue empty
105
+
106
+ """
107
+ if self._ca:
108
+ return MayBe(self._ca[-1])
109
+ return MayBe()
110
+
111
+ def peak_next_out(self) -> MayBe[D]:
112
+ """Peak at oldest data item on queue.
113
+
114
+ :return: MayBe of oldest data item on queue, empty MayBe if queue empty
115
+
116
+ """
117
+ if self._ca:
118
+ return MayBe(self._ca[0])
119
+ return MayBe()
120
+
121
+ def fold[T](self, f: Callable[[T, D], T], start: T | None = None) -> MayBe[T]:
122
+ """Reduces FIFOQueue in natural FIFO Order, oldest to newest.
123
+
124
+ :param f: reducing function, first argument is for accumulator
125
+ :param start: optional starting value
126
+ :return: MayBe of reduced value with f, empty MayBe if queue empty and no starting value given
127
+
128
+ """
129
+ if start is None:
130
+ if not self._ca:
131
+ return MayBe()
132
+ return MayBe(self._ca.foldl(f, start))
133
+
134
+ def map[U](self, f: Callable[[D], U]) -> FIFOQueue[U]:
135
+ """Map f over the FIFOQueue, retain original order.
136
+
137
+ :return: new FIFOQueue instance
138
+
139
+ """
140
+ return FIFOQueue(map(f, self._ca))
141
+
142
+
143
+ def fifo_queue[D](*ds: D) -> FIFOQueue[D]:
144
+ """FIFOQueue factory function."""
145
+ return FIFOQueue(ds)
@@ -0,0 +1,136 @@
1
+ # Copyright 2023-2024 Geoffrey R. Scheller
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Stateful Last-In-First-Out (LIFO) Queue data structure.
16
+
17
+ - O(1) pops
18
+ - O(1) amortized pushes
19
+ - in a Boolean context, true if not empty, false if empty
20
+ - will automatically increase storage capacity when needed
21
+ - neither indexable nor sliceable by design
22
+ - O(1) length determination
23
+
24
+ """
25
+ from __future__ import annotations
26
+
27
+ from collections.abc import Callable, Iterable, Iterator
28
+ from typing import TypeVar
29
+ from pythonic_fp.circulararray.auto import CA
30
+ from pythonic_fp.fptools.function import swap
31
+ from pythonic_fp.fptools.maybe import MayBe
32
+
33
+ __all__ = ['LIFOQueue', 'lifo_queue']
34
+
35
+ D = TypeVar('D')
36
+
37
+
38
+ class LIFOQueue[D]:
39
+
40
+ __slots__ = ('_ca',)
41
+
42
+ def __init__(self, *dss: Iterable[D]) -> None:
43
+ """Initial data in natural LIFO order, oldest to newest.
44
+
45
+ :param dss: takes up to one iterable
46
+ :raises ValueError: if more than 1 iterable is given
47
+
48
+ """
49
+ if (size := len(dss)) > 1:
50
+ msg = f'LIFOQueue expects at most 1 iterable argument, got {size}'
51
+ raise ValueError(msg)
52
+ self._ca = CA(dss[0]) if size == 1 else CA()
53
+
54
+ def __bool__(self) -> bool:
55
+ return len(self._ca) > 0
56
+
57
+ def __len__(self) -> int:
58
+ return len(self._ca)
59
+
60
+ def __eq__(self, other: object) -> bool:
61
+ if not isinstance(other, LIFOQueue):
62
+ return False
63
+ return self._ca == other._ca
64
+
65
+ def __iter__(self) -> Iterator[D]:
66
+ return reversed(list(self._ca))
67
+
68
+ def __repr__(self) -> str:
69
+ if len(self) == 0:
70
+ return 'LIFOQueue()'
71
+ return 'LIFOQueue(' + ', '.join(map(repr, self._ca)) + ')'
72
+
73
+ def __str__(self) -> str:
74
+ return '|| ' + ' > '.join(map(str, self)) + ' ><'
75
+
76
+ def copy(self) -> LIFOQueue[D]:
77
+ """Shallow copy.
78
+
79
+ :return: shallow copy of the LIFOQueue
80
+
81
+ """
82
+ return LIFOQueue(reversed(self._ca))
83
+
84
+ def push(self, *ds: D) -> None:
85
+ """Push data onto LIFOQueue.
86
+
87
+ :param ds: data items to be pushed onto LIFOQueue
88
+
89
+ """
90
+ self._ca.pushr(*ds)
91
+
92
+ def pop(self) -> MayBe[D]:
93
+ """Pop newest data item off of LIFOQueue.
94
+
95
+ :return: MayBe of popped data item if queue was not empty, empty MayBe otherwise
96
+
97
+ """
98
+ if self._ca:
99
+ return MayBe(self._ca.popr())
100
+ return MayBe()
101
+
102
+ def peak(self) -> MayBe[D]:
103
+ """Peak at newest data item on queue.
104
+
105
+ :return: MayBe of newest data item on queue, empty MayBe if queue empty
106
+
107
+ """
108
+ if self._ca:
109
+ return MayBe(self._ca[-1])
110
+ return MayBe()
111
+
112
+ def fold[T](self, f: Callable[[T, D], T], start: T | None = None) -> MayBe[T]:
113
+ """Reduces LIFOQUEUE in natural LIFO Order, newest to oldest.
114
+
115
+ :param f: reducing function, first argument is for accumulator
116
+ :param start: optional starting value
117
+ :return: MayBe of reduced value with f, empty MayBe if queue empty and no starting value given
118
+
119
+ """
120
+ if start is None:
121
+ if not self._ca:
122
+ return MayBe()
123
+ return MayBe(self._ca.foldr(swap(f), start))
124
+
125
+ def map[U](self, f: Callable[[D], U]) -> LIFOQueue[U]:
126
+ """Map f over the LIFOQueue, retain original order.
127
+
128
+ :return: new LIFOQueue instance
129
+
130
+ """
131
+ return LIFOQueue(reversed(CA(map(f, reversed(self._ca)))))
132
+
133
+
134
+ def lifo_queue[D](*ds: D) -> LIFOQueue[D]:
135
+ """LIFOQueue factory function."""
136
+ return LIFOQueue(ds)
File without changes
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: pythonic-fp-containers
3
+ Version: 3.0.0
4
+ Summary: Package of container like data structures.
5
+ Keywords: boxes,functional_tuple,immutable_list,queue,fifo,lifo,dqueue,dequeue
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 :: 5 - Production/Stable
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-circulararray>= 5.3.0
18
+ Requires-Dist: pythonic-fp-fptools>=5.0.0
19
+ Requires-Dist: pythonic-fp-iterables>=5.0.0
20
+ Requires-Dist: pythonic-fp-singletons>=1.0.0
21
+ Requires-Dist: pytest>=8.4.1 ; extra == "test"
22
+ Requires-Dist: pythonic-fp-splitends>=1.0.0 ; extra == "test"
23
+ Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-containers/blob/main/CHANGELOG.rst
24
+ Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/containers/development/build/html/releases.html
25
+ Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
26
+ Project-URL: Source, https://github.com/grscheller/pythonic-fp-containers
27
+ Provides-Extra: test
28
+
29
+ Pythonic FP - Containers
30
+ ========================
31
+
32
+ PyPI project
33
+ `pythonic-fp.containers <https://pypi.org/project/pythonic-fp.containers>`_.
34
+
35
+ Python package of container like data structures.
36
+
37
+ +------------------+------------------------------------------------------+
38
+ | Description | Module |
39
+ +==================+======================================================+
40
+ | Single item box | *module* ``pythonic_fp.containers.box`` |
41
+ | Functional tuple | *module* ``pythonic_fp.containers.functional_tuple`` |
42
+ | Immutable list | *module* ``pythonic_fp.containers.immutable_list`` |
43
+ +------------------+------------------------------------------------------+
44
+
45
+ **DEPRECATION WARNING:** ``pythonic_fp.fptools.box`` as been deprecated.
46
+ Use ``pythonic_fp.box`` instead.
47
+
48
+ This PyPI project is part of of the grscheller
49
+ `pythonic-fp namespace projects <https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_.
50
+
51
+ Documentation
52
+ -------------
53
+
54
+ Documentation for this project is hosted on
55
+ `GitHub Pages
56
+ <https://grscheller.github.io/pythonic-fp/containers/development/build/html>`_.
57
+
58
+ Copyright and License
59
+ ---------------------
60
+
61
+ Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
62
+ License, Version 2.0. See the LICENSE file for details.
63
+
@@ -0,0 +1,13 @@
1
+ pythonic_fp/containers/__init__.py,sha256=N5-djJadhpE6h8bg31AtUrqami8RPjsiUYwlwrPpYnI,1603
2
+ pythonic_fp/containers/functional_tuple.py,sha256=AffGbovvg0WpnImZwzhD47djLps27Ns29eKzFosXOC4,5799
3
+ pythonic_fp/containers/immutable_list.py,sha256=SGfGKRyDyBylrNn9qvpoKog531RKAusRsxhtnVa5Rps,6677
4
+ pythonic_fp/containers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ pythonic_fp/containers/queues/__init__.py,sha256=GJdzodYt32pADih2UHOiR9U4jfZ6SQSw6PsCYZ2r1wg,1459
6
+ pythonic_fp/containers/queues/de.py,sha256=lBoZE8SRe6__NEFpNiqKhumDtdIVvm7S7HMojEUAL1s,5288
7
+ pythonic_fp/containers/queues/fifo.py,sha256=Q9luMfZzSJzGU3aJwnJ5OIeT6hs-6ieh70b5wYGodI4,4221
8
+ pythonic_fp/containers/queues/lifo.py,sha256=xudvLpEvqv9bl3bCqTiwlC2IDZ9A5kHVl-aIjNAKyeA,4031
9
+ pythonic_fp/containers/queues/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ pythonic_fp_containers-3.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
11
+ pythonic_fp_containers-3.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
12
+ pythonic_fp_containers-3.0.0.dist-info/METADATA,sha256=UeoEZi26JqOUc0AYxcwpr8FrxsNEA7XOu5IMySlm_jk,2672
13
+ pythonic_fp_containers-3.0.0.dist-info/RECORD,,
@@ -1,159 +0,0 @@
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
- """Stateful container holds at most one item of a given type."""
16
-
17
- from __future__ import annotations
18
-
19
- __all__ = ['Box']
20
-
21
- from collections.abc import Callable, Iterator
22
- from typing import cast, Final, Never, overload, TypeVar
23
- from pythonic_fp.fptools.singletons import Sentinel
24
-
25
- D = TypeVar('D')
26
-
27
- _sentinel: Final[Sentinel] = Sentinel('Box')
28
-
29
- class Box[D]:
30
- """Container holding at most one item of a given type
31
-
32
- - where ``Box(item)`` contains at most one item of type ``~D``
33
-
34
- - ``Box()`` creates an empty container
35
- - can store any item of any type, including ``None``
36
- """
37
-
38
- __slots__ = ('_item',)
39
- __match_args__ = ('_item',)
40
-
41
- T = TypeVar('T')
42
-
43
- @overload
44
- def __init__(self) -> None: ...
45
- @overload
46
- def __init__(self, value: D) -> None: ...
47
-
48
- def __init__(self, value: D | Sentinel = Sentinel('Box')) -> None:
49
- """Initialize Box with an "optional" initial value.
50
-
51
- :param value: "optional" initial value
52
- :type value: ~D
53
- """
54
- self._item: D | Sentinel = value
55
-
56
- def __bool__(self) -> bool:
57
- return self._item is not Sentinel('Box')
58
-
59
- def __iter__(self) -> Iterator[D]:
60
- if self:
61
- yield cast(D, self._item)
62
-
63
- def __repr__(self) -> str:
64
- if self:
65
- return 'Box(' + repr(self._item) + ')'
66
- return 'Box()'
67
-
68
- def __len__(self) -> int:
69
- return 1 if self else 0
70
-
71
- def __eq__(self, other: object) -> bool:
72
- if not isinstance(other, type(self)):
73
- return False
74
-
75
- if self._item is other._item:
76
- return True
77
- if self._item == other._item:
78
- return True
79
- return False
80
-
81
- @overload
82
- def get(self) -> D | Never: ...
83
- @overload
84
- def get(self, alt: D) -> D: ...
85
- @overload
86
- def get(self, alt: Sentinel) -> D | Never: ...
87
-
88
- def get(self, alt: D | Sentinel = Sentinel('Box')) -> D | Never:
89
- """Return the contained value if it exists, otherwise an alternate value.
90
-
91
- :rtype: ~D
92
- :raises ValueError: when an alternate value is not provided but needed
93
- """
94
- if self._item is not _sentinel:
95
- return cast(D, self._item)
96
- if alt is _sentinel:
97
- msg = 'Box: get from empty Box with no alt return value provided'
98
- raise ValueError(msg)
99
- return cast(D, alt)
100
-
101
- def pop(self) -> D | Never:
102
- """Pop the value if Box is not empty.
103
-
104
- :rtype: ~D
105
- :raises ValueError: if box is empty
106
- """
107
- if self._item is _sentinel:
108
- msg = 'Box: Trying to pop an item from an empty Box'
109
- raise ValueError(msg)
110
- popped = cast(D, self._item)
111
- self._item = _sentinel
112
- return popped
113
-
114
- def push(self, item: D) -> None | Never:
115
- """Push an item in an empty Box.
116
-
117
- :raises ValueError: if box is not empty
118
- """
119
- if self._item is Sentinel('Box'):
120
- self._item = item
121
- else:
122
- msg = 'Box: Trying to push an item in an empty Box'
123
- raise ValueError(msg)
124
- return None
125
-
126
- def put(self, item: D) -> None:
127
- """Put an item in the Box. Discard any previous contents."""
128
- self._item = item
129
-
130
- def exchange(self, new_item: D) -> D | Never:
131
- """Exchange an item with what is in the Box.
132
-
133
- :rtype: ~D
134
- :raises ValueError: if box is empty
135
- """
136
- if self._item is _sentinel:
137
- msg = 'Box: Trying to exchange items using an empty Box'
138
- raise ValueError(msg)
139
- popped = cast(D, self._item)
140
- self._item = new_item
141
- return popped
142
-
143
- def map[T](self, f: Callable[[D], T]) -> Box[T]:
144
- """Map function ``f`` over contents.
145
-
146
- :return: a new instance
147
- """
148
- if self._item is Sentinel('Box'):
149
- return Box()
150
- return Box(f(cast(D, self._item)))
151
-
152
- def bind[T](self, f: Callable[[D], Box[T]]) -> Box[T]:
153
- """Flatmap ``Box`` with function ``f``.
154
-
155
- :return: a new instance
156
- """
157
- if self._item is _sentinel:
158
- return Box()
159
- return f(cast(D, self._item))