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.
- pythonic_fp/containers/__init__.py +17 -1
- pythonic_fp/containers/functional_tuple.py +51 -56
- pythonic_fp/containers/immutable_list.py +50 -54
- pythonic_fp/containers/queues/__init__.py +31 -0
- pythonic_fp/containers/queues/de.py +180 -0
- pythonic_fp/containers/queues/fifo.py +145 -0
- pythonic_fp/containers/queues/lifo.py +136 -0
- pythonic_fp/containers/queues/py.typed +0 -0
- pythonic_fp_containers-3.0.0.dist-info/METADATA +63 -0
- pythonic_fp_containers-3.0.0.dist-info/RECORD +13 -0
- pythonic_fp/containers/box.py +0 -159
- pythonic_fp/containers/maybe.py +0 -226
- pythonic_fp/containers/xor.py +0 -314
- pythonic_fp_containers-2.0.0.dist-info/METADATA +0 -50
- pythonic_fp_containers-2.0.0.dist-info/RECORD +0 -11
- {pythonic_fp_containers-2.0.0.dist-info → pythonic_fp_containers-3.0.0.dist-info}/WHEEL +0 -0
- {pythonic_fp_containers-2.0.0.dist-info → pythonic_fp_containers-3.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -12,7 +12,23 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""
|
|
15
|
+
"""Package of container like data structures.
|
|
16
|
+
|
|
17
|
+
+------------------+--------+----------------------------------------------------+
|
|
18
|
+
| Description | Class | Package/Module |
|
|
19
|
+
+==================+========+====================================================+
|
|
20
|
+
| Functional tuple | FTuple | module ``pythonic_fp.containers.functional_tuple`` |
|
|
21
|
+
+------------------+--------+----------------------------------------------------+
|
|
22
|
+
| Immutable list | IList | module ``pythonic_fp.containers.immutable_list`` |
|
|
23
|
+
+------------------+--------+----------------------------------------------------+
|
|
24
|
+
| Queues | | package ``pythonic_fp.containers.queues`` |
|
|
25
|
+
+------------------+--------+----------------------------------------------------+
|
|
26
|
+
|
|
27
|
+
.. note::
|
|
28
|
+
|
|
29
|
+
Module ``box`` has been moved to ``pythonic_fp.gadgets.box``.
|
|
30
|
+
|
|
31
|
+
"""
|
|
16
32
|
|
|
17
33
|
__author__ = 'Geoffrey R. Scheller'
|
|
18
34
|
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
@@ -17,24 +17,29 @@
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
from collections.abc import Callable, Iterator
|
|
20
|
-
from typing import cast, Never, overload
|
|
21
|
-
from pythonic_fp.iterables import
|
|
20
|
+
from typing import cast, Never, overload, TypeVar
|
|
21
|
+
from pythonic_fp.iterables.folding import accumulate
|
|
22
|
+
from pythonic_fp.iterables.merging import blend, concat, MergeEnum
|
|
22
23
|
|
|
23
|
-
__all__ = ['
|
|
24
|
+
__all__ = ['FTuple']
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
D = TypeVar('D', covariant=True)
|
|
27
|
+
|
|
28
|
+
class FTuple[D](tuple[D, ...]):
|
|
26
29
|
"""Functional Tuple suitable for inheritance
|
|
27
30
|
|
|
28
31
|
- Supports both indexing and slicing
|
|
29
|
-
-
|
|
32
|
+
- FTuple addition and int multiplication supported
|
|
30
33
|
|
|
31
34
|
- addition concatenates results, resulting in a Union type
|
|
32
35
|
- both left and right int multiplication supported
|
|
33
36
|
- homogeneous in its data type
|
|
34
37
|
- supports being further inherited from
|
|
35
38
|
- unslotted
|
|
36
|
-
"""
|
|
37
39
|
|
|
40
|
+
- Since these tuples are homogeneous, their covariance may be quirky
|
|
41
|
+
|
|
42
|
+
"""
|
|
38
43
|
def __reversed__(self) -> Iterator[D]:
|
|
39
44
|
for ii in range(len(self) - 1, -1, -1):
|
|
40
45
|
yield (self[ii])
|
|
@@ -80,19 +85,20 @@ class FunctionalTuple[D](tuple[D, ...]):
|
|
|
80
85
|
- fold left with an optional starting value
|
|
81
86
|
- first argument of function f is for the accumulated value
|
|
82
87
|
|
|
83
|
-
:raises ValueError: when
|
|
88
|
+
:raises ValueError: when FTuple empty and a start value not given
|
|
89
|
+
|
|
84
90
|
"""
|
|
85
|
-
|
|
91
|
+
it_self = iter(self)
|
|
86
92
|
if start is not None:
|
|
87
93
|
acc = start
|
|
88
94
|
elif self:
|
|
89
|
-
acc = cast(L, next(
|
|
95
|
+
acc = cast(L, next(it_self))
|
|
90
96
|
else:
|
|
91
97
|
if default is None:
|
|
92
|
-
msg = 'Both start and default cannot be None for an empty
|
|
93
|
-
raise ValueError('
|
|
98
|
+
msg = 'Both start and default cannot be None for an empty FTuple'
|
|
99
|
+
raise ValueError('FTuple.foldl - ' + msg)
|
|
94
100
|
acc = default
|
|
95
|
-
for v in
|
|
101
|
+
for v in it_self:
|
|
96
102
|
acc = f(acc, v)
|
|
97
103
|
return acc
|
|
98
104
|
|
|
@@ -108,29 +114,30 @@ class FunctionalTuple[D](tuple[D, ...]):
|
|
|
108
114
|
- fold right with an optional starting value
|
|
109
115
|
- second argument of function f is for the accumulated value
|
|
110
116
|
|
|
111
|
-
:raises ValueError: when
|
|
117
|
+
:raises ValueError: when FTuple empty and a start value not given
|
|
118
|
+
|
|
112
119
|
"""
|
|
113
|
-
|
|
120
|
+
it_self = reversed(self)
|
|
114
121
|
if start is not None:
|
|
115
122
|
acc = start
|
|
116
123
|
elif self:
|
|
117
|
-
acc = cast(R, next(
|
|
124
|
+
acc = cast(R, next(it_self))
|
|
118
125
|
else:
|
|
119
126
|
if default is None:
|
|
120
|
-
msg = 'Both start and default cannot be None for an empty
|
|
121
|
-
raise ValueError('
|
|
127
|
+
msg = 'Both start and default cannot be None for an empty FTuple'
|
|
128
|
+
raise ValueError('FTuple.foldR - ' + msg)
|
|
122
129
|
acc = default
|
|
123
|
-
for v in
|
|
130
|
+
for v in it_self:
|
|
124
131
|
acc = f(v, acc)
|
|
125
132
|
return acc
|
|
126
133
|
|
|
127
|
-
def copy(self) ->
|
|
128
|
-
"""Return a shallow copy of ``
|
|
134
|
+
def copy(self) -> FTuple[D]:
|
|
135
|
+
"""Return a shallow copy of ``FTuple`` in O(1) time & space complexity."""
|
|
129
136
|
return self.__class__(self)
|
|
130
137
|
|
|
131
138
|
def __add__(self, other: object, /) -> tuple[D, ...]:
|
|
132
|
-
if not isinstance(other,
|
|
133
|
-
msg = '
|
|
139
|
+
if not isinstance(other, FTuple):
|
|
140
|
+
msg = 'FTuple being added to something not an FTuple'
|
|
134
141
|
raise ValueError(msg)
|
|
135
142
|
return self.__class__(concat(self, other))
|
|
136
143
|
|
|
@@ -142,48 +149,36 @@ class FunctionalTuple[D](tuple[D, ...]):
|
|
|
142
149
|
|
|
143
150
|
def accummulate[L](
|
|
144
151
|
self, f: Callable[[L, D], L], s: L | None = None, /
|
|
145
|
-
) ->
|
|
152
|
+
) -> FTuple[L]:
|
|
146
153
|
"""Accumulate partial folds
|
|
147
154
|
|
|
148
|
-
Accumulate partial fold results in an ``
|
|
155
|
+
Accumulate partial fold results in an ``FTuple`` with an optional
|
|
149
156
|
starting value.
|
|
157
|
+
|
|
150
158
|
"""
|
|
151
159
|
if s is None:
|
|
152
|
-
return
|
|
153
|
-
return
|
|
160
|
+
return FTuple(accumulate(self, f))
|
|
161
|
+
return FTuple(accumulate(self, f, s))
|
|
154
162
|
|
|
155
|
-
def map[U](self, f: Callable[[D], U], /) ->
|
|
156
|
-
return
|
|
163
|
+
def map[U](self, f: Callable[[D], U], /) -> FTuple[U]:
|
|
164
|
+
return FTuple(map(f, self))
|
|
157
165
|
|
|
158
166
|
def bind[U](
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
self, f: Callable[[D], FTuple[U]],
|
|
168
|
+
merge_enum: MergeEnum = MergeEnum.Concat,
|
|
169
|
+
yield_partials: bool = False,
|
|
170
|
+
) -> FTuple[U] | Never:
|
|
171
|
+
"""Bind function ``f`` to the ``FTuple``.
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
:param ds: values to instantiate FTuple
|
|
174
|
+
:return: resulting FTuple
|
|
175
|
+
:raises ValueError: if given unknown merge_type
|
|
164
176
|
|
|
165
|
-
- CONCAT: sequentially concatenate iterables one after the other
|
|
166
|
-
- MERGE: round-robin merge iterables until one is exhausted
|
|
167
|
-
- EXHAUST: round-robin merge iterables until all are exhausted
|
|
168
|
-
|
|
169
|
-
:param ds: values to instantiate FunctionalTuple
|
|
170
|
-
:return: resulting FunctionalTuple
|
|
171
177
|
"""
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
raise ValueError('Unknown FM type')
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def functional_tuple[D](*ds: D):
|
|
184
|
-
"""Construct a ``FunctionalTuple`` from arguments.
|
|
185
|
-
|
|
186
|
-
:param ds: values to instantiate FunctionalTuple
|
|
187
|
-
:return: resulting FunctionalTuple
|
|
188
|
-
"""
|
|
189
|
-
return FunctionalTuple(ds)
|
|
178
|
+
return FTuple(
|
|
179
|
+
blend(
|
|
180
|
+
*map(f, self),
|
|
181
|
+
merge_enum=merge_enum,
|
|
182
|
+
yield_partials=yield_partials
|
|
183
|
+
)
|
|
184
|
+
)
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
- declared covariant in its generic datatype
|
|
19
19
|
- hashability should be enforced by LSP tooling
|
|
20
20
|
- hashability will be enforced at runtime
|
|
21
|
-
-
|
|
22
|
-
-
|
|
21
|
+
- IList addition supported via concatenation
|
|
22
|
+
- IList integer multiplication supported
|
|
23
23
|
|
|
24
24
|
"""
|
|
25
25
|
|
|
@@ -27,14 +27,15 @@ from __future__ import annotations
|
|
|
27
27
|
|
|
28
28
|
from collections.abc import Callable, Iterable, Iterator, Hashable
|
|
29
29
|
from typing import cast, Never, overload, TypeVar
|
|
30
|
-
from pythonic_fp.iterables import
|
|
30
|
+
from pythonic_fp.iterables.folding import accumulate
|
|
31
|
+
from pythonic_fp.iterables.merging import blend, concat, MergeEnum
|
|
31
32
|
|
|
32
|
-
__all__ = ['
|
|
33
|
+
__all__ = ['IList']
|
|
33
34
|
|
|
34
35
|
D = TypeVar('D', covariant=True)
|
|
35
36
|
T = TypeVar('T')
|
|
36
37
|
|
|
37
|
-
class
|
|
38
|
+
class IList[D](Hashable):
|
|
38
39
|
"""Immutable List like data structure.
|
|
39
40
|
|
|
40
41
|
- its method type parameters are also covariant
|
|
@@ -42,8 +43,8 @@ class ImmutableList[D](Hashable):
|
|
|
42
43
|
- supports both indexing and slicing
|
|
43
44
|
- addition concatenates results, resulting type a Union type
|
|
44
45
|
- both left and right int multiplication supported
|
|
45
|
-
"""
|
|
46
46
|
|
|
47
|
+
"""
|
|
47
48
|
__slots__ = ('_ds', '_len', '_hash')
|
|
48
49
|
__match_args__ = ('_ds', '_len')
|
|
49
50
|
|
|
@@ -53,7 +54,7 @@ class ImmutableList[D](Hashable):
|
|
|
53
54
|
|
|
54
55
|
def __init__(self, *dss: Iterable[D]) -> None:
|
|
55
56
|
if (size := len(dss)) > 1:
|
|
56
|
-
msg = f'
|
|
57
|
+
msg = f'IList expects at most 1 iterable argument, got {size}'
|
|
57
58
|
raise ValueError(msg)
|
|
58
59
|
else:
|
|
59
60
|
self._ds: tuple[D, ...] = tuple(dss[0]) if size == 1 else tuple()
|
|
@@ -61,7 +62,7 @@ class ImmutableList[D](Hashable):
|
|
|
61
62
|
try:
|
|
62
63
|
self._hash = hash((self._len, 42) + self._ds)
|
|
63
64
|
except TypeError as exc:
|
|
64
|
-
msg = f'
|
|
65
|
+
msg = f'IList: {exc}'
|
|
65
66
|
raise TypeError(msg)
|
|
66
67
|
|
|
67
68
|
def __hash__(self) -> int:
|
|
@@ -86,7 +87,7 @@ class ImmutableList[D](Hashable):
|
|
|
86
87
|
return '((' + ', '.join(map(repr, self)) + '))'
|
|
87
88
|
|
|
88
89
|
def __eq__(self, other: object, /) -> bool:
|
|
89
|
-
if not isinstance(other,
|
|
90
|
+
if not isinstance(other, IList):
|
|
90
91
|
return NotImplemented # magic object
|
|
91
92
|
if self._len != other._len:
|
|
92
93
|
return False
|
|
@@ -97,11 +98,11 @@ class ImmutableList[D](Hashable):
|
|
|
97
98
|
@overload
|
|
98
99
|
def __getitem__(self, idx: int, /) -> D: ...
|
|
99
100
|
@overload
|
|
100
|
-
def __getitem__(self, idx: slice, /) ->
|
|
101
|
+
def __getitem__(self, idx: slice, /) -> IList[D]: ...
|
|
101
102
|
|
|
102
|
-
def __getitem__(self, idx: slice | int, /) ->
|
|
103
|
+
def __getitem__(self, idx: slice | int, /) -> IList[D] | D:
|
|
103
104
|
if isinstance(idx, slice):
|
|
104
|
-
return
|
|
105
|
+
return IList(self._ds[idx])
|
|
105
106
|
return self._ds[idx]
|
|
106
107
|
|
|
107
108
|
def foldl[L](
|
|
@@ -117,6 +118,7 @@ class ImmutableList[D](Hashable):
|
|
|
117
118
|
- first argument of function ``f`` is for the accumulated value
|
|
118
119
|
|
|
119
120
|
"raises ValueError: when empty and a start value not given
|
|
121
|
+
|
|
120
122
|
"""
|
|
121
123
|
it = iter(self._ds)
|
|
122
124
|
if start is not None:
|
|
@@ -125,9 +127,9 @@ class ImmutableList[D](Hashable):
|
|
|
125
127
|
acc = cast(L, next(it)) # L_co = D_co in this case
|
|
126
128
|
else:
|
|
127
129
|
if default is None:
|
|
128
|
-
msg0 = '
|
|
130
|
+
msg0 = 'IList: foldl method requires '
|
|
129
131
|
msg1 = 'either start or default to be defined for '
|
|
130
|
-
msg2 = 'an empty
|
|
132
|
+
msg2 = 'an empty IList'
|
|
131
133
|
raise ValueError(msg0 + msg1 + msg2)
|
|
132
134
|
acc = default
|
|
133
135
|
for v in it:
|
|
@@ -147,6 +149,7 @@ class ImmutableList[D](Hashable):
|
|
|
147
149
|
- second argument of function ``f`` is for the accumulated value
|
|
148
150
|
|
|
149
151
|
"raises ValueError: when empty and a start value not given
|
|
152
|
+
|
|
150
153
|
"""
|
|
151
154
|
it = reversed(self._ds)
|
|
152
155
|
if start is not None:
|
|
@@ -155,68 +158,61 @@ class ImmutableList[D](Hashable):
|
|
|
155
158
|
acc = cast(R, next(it))
|
|
156
159
|
else:
|
|
157
160
|
if default is None:
|
|
158
|
-
msg0 = '
|
|
161
|
+
msg0 = 'IList: foldr method requires '
|
|
159
162
|
msg1 = 'either start or default to be defined for '
|
|
160
|
-
msg2 = 'an empty
|
|
163
|
+
msg2 = 'an empty IList'
|
|
161
164
|
raise ValueError(msg0 + msg1 + msg2)
|
|
162
165
|
acc = default
|
|
163
166
|
for v in it:
|
|
164
167
|
acc = f(v, acc)
|
|
165
168
|
return acc
|
|
166
169
|
|
|
167
|
-
def __add__(self, other:
|
|
168
|
-
if not isinstance(other,
|
|
169
|
-
msg = '
|
|
170
|
+
def __add__(self, other: IList[D], /) -> IList[D]:
|
|
171
|
+
if not isinstance(other, IList):
|
|
172
|
+
msg = 'IList being added to something not a IList'
|
|
170
173
|
raise ValueError(msg)
|
|
171
174
|
|
|
172
|
-
return
|
|
175
|
+
return IList(concat(self, other))
|
|
173
176
|
|
|
174
|
-
def __mul__(self, num: int, /) ->
|
|
175
|
-
return
|
|
177
|
+
def __mul__(self, num: int, /) -> IList[D]:
|
|
178
|
+
return IList(self._ds.__mul__(num if num > 0 else 0))
|
|
176
179
|
|
|
177
|
-
def __rmul__(self, num: int, /) ->
|
|
178
|
-
return
|
|
180
|
+
def __rmul__(self, num: int, /) -> IList[D]:
|
|
181
|
+
return IList(self._ds.__mul__(num if num > 0 else 0))
|
|
179
182
|
|
|
180
183
|
def accummulate[L](
|
|
181
184
|
self, f: Callable[[L, D], L], s: L | None = None, /
|
|
182
|
-
) ->
|
|
185
|
+
) -> IList[L]:
|
|
183
186
|
"""Accumulate partial folds
|
|
184
187
|
|
|
185
|
-
Accumulate partial fold results in an
|
|
188
|
+
Accumulate partial fold results in an IList with
|
|
186
189
|
an optional starting value.
|
|
190
|
+
|
|
187
191
|
"""
|
|
188
192
|
if s is None:
|
|
189
|
-
return
|
|
190
|
-
return
|
|
193
|
+
return IList(accumulate(self, f))
|
|
194
|
+
return IList(accumulate(self, f, s))
|
|
191
195
|
|
|
192
|
-
def map[U](self, f: Callable[[D], U], /) ->
|
|
193
|
-
return
|
|
196
|
+
def map[U](self, f: Callable[[D], U], /) -> IList[U]:
|
|
197
|
+
return IList(map(f, self))
|
|
194
198
|
|
|
195
199
|
def bind[U](
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
self,
|
|
201
|
+
f: Callable[[D], IList[U]],
|
|
202
|
+
merge_enum: MergeEnum = MergeEnum.Concat,
|
|
203
|
+
yield_partials: bool = False,
|
|
204
|
+
) -> IList[U] | Never:
|
|
205
|
+
"""Bind function `f` to the `IList`.
|
|
199
206
|
|
|
200
|
-
|
|
207
|
+
:param ds: values to instantiate IList
|
|
208
|
+
:return: resulting IList
|
|
209
|
+
:raises ValueError: if given unknown merge_type
|
|
201
210
|
|
|
202
|
-
- CONCAT: sequentially concatenate iterables one after the other
|
|
203
|
-
- MERGE: round-robin merge iterables until one is exhausted
|
|
204
|
-
- EXHAUST: round-robin merge iterables until all are exhausted
|
|
205
211
|
"""
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
raise ValueError(f'ImmutableList: Unknown FM type: {type}')
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def immutable_list[T](*ts: T) -> ImmutableList[TabError]:
|
|
218
|
-
"""Function to produce an ``ImmutableList`` from a variable number of arguments.
|
|
219
|
-
|
|
220
|
-
:param ds: initial values to push onto a new ImmutableList from right to left
|
|
221
|
-
"""
|
|
222
|
-
return ImmutableList(ts)
|
|
212
|
+
return IList(
|
|
213
|
+
blend(
|
|
214
|
+
*map(f, self),
|
|
215
|
+
merge_enum=merge_enum,
|
|
216
|
+
yield_partials=yield_partials
|
|
217
|
+
)
|
|
218
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
"""Three types of queues.
|
|
16
|
+
|
|
17
|
+
+------------------------------------+-----------+--------------------------+
|
|
18
|
+
| module | Class | description |
|
|
19
|
+
+====================================+===========+==========================+
|
|
20
|
+
| pythonic_fp.containers.queues.fifo | FIFOQueue | First-In-First-Out queue |
|
|
21
|
+
+------------------------------------+-----------+--------------------------+
|
|
22
|
+
| pythonic_fp.containers.queues.lifo | LIFOQueue | Last-In-First-Out queue |
|
|
23
|
+
+------------------------------------+-----------+--------------------------+
|
|
24
|
+
| pythonic_fp.containers.queues.de | DEQueue | Double-Ended queue |
|
|
25
|
+
+------------------------------------+-----------+--------------------------+
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__author__ = 'Geoffrey R. Scheller'
|
|
30
|
+
__copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
|
|
31
|
+
__license__ = 'Apache License 2.0'
|
|
@@ -0,0 +1,180 @@
|
|
|
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 Double-Ended (DE) Queue data structure.
|
|
16
|
+
|
|
17
|
+
- O(1) pops each end
|
|
18
|
+
- O(1) amortized pushes each end
|
|
19
|
+
- O(1) length determination
|
|
20
|
+
- will automatically increase storage capacity when needed
|
|
21
|
+
- in a Boolean context, true if not empty, false if empty
|
|
22
|
+
- neither indexable nor sliceable by design
|
|
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__ = ['DEQueue', 'de_queue']
|
|
33
|
+
|
|
34
|
+
D = TypeVar('D')
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class DEQueue[D]:
|
|
38
|
+
|
|
39
|
+
__slots__ = ('_ca',)
|
|
40
|
+
|
|
41
|
+
def __init__(self, *dss: Iterable[D]) -> None:
|
|
42
|
+
"""Initial data in FIFO order, newest to oldest, as if
|
|
43
|
+
pushed on from the right side.
|
|
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'DEQueue expects at most 1 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, DEQueue):
|
|
62
|
+
return False
|
|
63
|
+
return self._ca == other._ca
|
|
64
|
+
|
|
65
|
+
def __iter__(self) -> Iterator[D]:
|
|
66
|
+
return iter(list(self._ca))
|
|
67
|
+
|
|
68
|
+
def __reversed__(self) -> Iterator[D]:
|
|
69
|
+
return reversed(list(self._ca))
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
if len(self) == 0:
|
|
73
|
+
return 'DEQueue()'
|
|
74
|
+
return 'DEQueue(' + ', '.join(map(repr, self._ca)) + ')'
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
return '>< ' + ' | '.join(map(str, self)) + ' ><'
|
|
78
|
+
|
|
79
|
+
def copy(self) -> DEQueue[D]:
|
|
80
|
+
"""Shallow copy.
|
|
81
|
+
|
|
82
|
+
:return: shallow copy of the DEQueue
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
return DEQueue(self._ca)
|
|
86
|
+
|
|
87
|
+
def pushl(self, *ds: D) -> None:
|
|
88
|
+
"""Push data onto left side of DEQueue.
|
|
89
|
+
|
|
90
|
+
:param ds: items to be pushed onto DEQueue from the left
|
|
91
|
+
|
|
92
|
+
"""
|
|
93
|
+
self._ca.pushl(*ds)
|
|
94
|
+
|
|
95
|
+
def pushr(self, *ds: D) -> None:
|
|
96
|
+
"""Push data onto right side of DEQueue.
|
|
97
|
+
|
|
98
|
+
:param ds: items to be pushed onto DEQueue from the right
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
self._ca.pushr(*ds)
|
|
102
|
+
|
|
103
|
+
def popl(self) -> MayBe[D]:
|
|
104
|
+
"""Pop next data item from left side DEQueue, if it exists.
|
|
105
|
+
|
|
106
|
+
:return: MayBe of popped item if queue was not empty, empty MayBe otherwise
|
|
107
|
+
|
|
108
|
+
"""
|
|
109
|
+
if self._ca:
|
|
110
|
+
return MayBe(self._ca.popl())
|
|
111
|
+
return MayBe()
|
|
112
|
+
|
|
113
|
+
def popr(self) -> MayBe[D]:
|
|
114
|
+
"""Pop next item off right side DEQueue.
|
|
115
|
+
|
|
116
|
+
:return: MayBe of popped item if queue was not empty, empty MayBe otherwise
|
|
117
|
+
|
|
118
|
+
"""
|
|
119
|
+
if self._ca:
|
|
120
|
+
return MayBe(self._ca.popr())
|
|
121
|
+
return MayBe()
|
|
122
|
+
|
|
123
|
+
def peakl(self) -> MayBe[D]:
|
|
124
|
+
"""Peak at data on left side of DEQueue.
|
|
125
|
+
|
|
126
|
+
:return: MayBe of leftmost data if queue not empty, empty MayBe otherwise
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
if self._ca:
|
|
130
|
+
return MayBe(self._ca[0])
|
|
131
|
+
return MayBe()
|
|
132
|
+
|
|
133
|
+
def peakr(self) -> MayBe[D]:
|
|
134
|
+
"""Peak at right side of DEQueue. Does not consume item.
|
|
135
|
+
|
|
136
|
+
:return: MayBe of rightmost data if queue not empty, empty MayBe otherwise
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
if self._ca:
|
|
140
|
+
return MayBe(self._ca[-1])
|
|
141
|
+
return MayBe()
|
|
142
|
+
|
|
143
|
+
def foldl[L](self, f: Callable[[L, D], L], start: L | None = None) -> MayBe[L]:
|
|
144
|
+
"""Reduces DEQueue left to right.
|
|
145
|
+
|
|
146
|
+
:param f: reducing function, first argument is for accumulator
|
|
147
|
+
:param start: optional starting value
|
|
148
|
+
:return: MayBe of reduced value with f, empty MayBe if queue empty and no starting value given
|
|
149
|
+
|
|
150
|
+
"""
|
|
151
|
+
if start is None:
|
|
152
|
+
if not self._ca:
|
|
153
|
+
return MayBe()
|
|
154
|
+
return MayBe(self._ca.foldl(f, start))
|
|
155
|
+
|
|
156
|
+
def foldr[R](self, f: Callable[[D, R], R], start: R | None = None) -> MayBe[R]:
|
|
157
|
+
"""Reduces DEQueue right to left.
|
|
158
|
+
|
|
159
|
+
:param f: reducing function, second argument is for accumulator
|
|
160
|
+
:param start: optional starting value
|
|
161
|
+
:return: MayBe of reduced value with f, empty MayBe if queue empty and no starting value given
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
if start is None:
|
|
165
|
+
if not self._ca:
|
|
166
|
+
return MayBe()
|
|
167
|
+
return MayBe(self._ca.foldr(f, start))
|
|
168
|
+
|
|
169
|
+
def map[U](self, f: Callable[[D], U]) -> DEQueue[U]:
|
|
170
|
+
"""Map f over the DEQueue left to right, retain original order.
|
|
171
|
+
|
|
172
|
+
:return: new DEQueue instance
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
return DEQueue(map(f, self._ca))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def de_queue[D](*ds: D) -> DEQueue[D]:
|
|
179
|
+
"""DEQueue factory function."""
|
|
180
|
+
return DEQueue(ds)
|