dtools.circular-array 3.9.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ # Copyright 2016-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
+ """### Circular Array
16
+
17
+ Package for an indexable circular array data structure.
18
+
19
+ #### Modules
20
+
21
+ * module dtools.circular_array.ca: circular array Python data structure
22
+
23
+ """
24
+ __version__ = "3.9.0"
25
+ __author__ = "Geoffrey R. Scheller"
26
+ __copyright__ = "Copyright (c) 2023-2025 Geoffrey R. Scheller"
27
+ __license__ = "Apache License 2.0"
@@ -0,0 +1,429 @@
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
+ """### Indexable circular array data structure module."""
16
+ from __future__ import annotations
17
+ from collections.abc import Callable, Iterable, Iterator, Sequence
18
+ from typing import Any, cast, Never, overload
19
+
20
+ __all__ = [ 'ca', 'CA' ]
21
+
22
+ class ca[D](Sequence[D]):
23
+ """
24
+ #### Indexable circular array data structure
25
+
26
+ * generic, stateful data structure
27
+ * amortized O(1) pushing and popping from either end
28
+ * O(1) random access any element
29
+ * will resize itself as needed
30
+ * sliceable
31
+ * makes defensive copies of contents for the purposes of iteration
32
+ * in boolean context returns true if not empty, false if empty
33
+ * in comparisons compare identity before equality (like Python built-ins do)
34
+ * lowercase class name choosen to match built-ins like `list` and `tuple`
35
+ * raises `IndexError` for out-of-bounds indexing
36
+ * raises `ValueError` for popping from or folding an empty `ca`
37
+ * raises `TypeError` if more than 2 arguments are passed to constructor
38
+
39
+ """
40
+ __slots__ = '_data', '_cnt', '_cap', '_front', '_rear'
41
+
42
+ def __init__(self, *dss: Iterable[D]) -> None:
43
+ if len(dss) < 2:
44
+ self._data: list[D|None] = [None] + cast(list[D|None], list(*dss)) + [None]
45
+ else:
46
+ msg = f'ca expected at most 1 argument, got {len(dss)}'
47
+ raise TypeError(msg)
48
+ self._cap = cap = len(self._data)
49
+ self._cnt = cap - 2
50
+ if cap == 2:
51
+ self._front = 0
52
+ self._rear = 1
53
+ else:
54
+ self._front = 1
55
+ self._rear = cap - 2
56
+
57
+ def _double_storage_capacity(self) -> None:
58
+ if self._front <= self._rear:
59
+ self._data += [None]*self._cap
60
+ self._cap *= 2
61
+ else:
62
+ self._data = self._data[:self._front] + [None]*self._cap + self._data[self._front:]
63
+ self._front, self._cap = self._front + self._cap, 2*self._cap
64
+
65
+ def _compact_storage_capacity(self) -> None:
66
+ """Compact the ca."""
67
+ match self._cnt:
68
+ case 0:
69
+ self._cap, self._front, self._rear, self._data = \
70
+ 2, 0, 1, [None, None]
71
+ case 1:
72
+ self._cap, self._front, self._rear, self._data = \
73
+ 3, 1, 1, [None, self._data[self._front], None]
74
+ case _:
75
+ if self._front <= self._rear:
76
+ self._cap, self._front, self._rear, self._data = \
77
+ self._cnt+2, 1, self._cnt, [None] + self._data[self._front:self._rear+1] + [None]
78
+ else:
79
+ self._cap, self._front, self._rear, self._data = \
80
+ self._cnt+2, 1, self._cnt, [None] + self._data[self._front:] + self._data[:self._rear+1] + [None]
81
+
82
+ def __iter__(self) -> Iterator[D]:
83
+ if self._cnt > 0:
84
+ capacity, rear, position, current_state = \
85
+ self._cap, self._rear, self._front, self._data.copy()
86
+
87
+ while position != rear:
88
+ yield cast(D, current_state[position])
89
+ position = (position + 1) % capacity
90
+ yield cast(D, current_state[position])
91
+
92
+ def __reversed__(self) -> Iterator[D]:
93
+ if self._cnt > 0:
94
+ capacity, front, position, current_state = \
95
+ self._cap, self._front, self._rear, self._data.copy()
96
+
97
+ while position != front:
98
+ yield cast(D, current_state[position])
99
+ position = (position - 1) % capacity
100
+ yield cast(D, current_state[position])
101
+
102
+ def __repr__(self) -> str:
103
+ return 'CA(' + ', '.join(map(repr, self)) + ')'
104
+
105
+ def __str__(self) -> str:
106
+ return '(|' + ', '.join(map(str, self)) + '|)'
107
+
108
+ def __bool__(self) -> bool:
109
+ return self._cnt > 0
110
+
111
+ def __len__(self) -> int:
112
+ return self._cnt
113
+
114
+ @overload
115
+ def __getitem__(self, idx: int, /) -> D: ...
116
+ @overload
117
+ def __getitem__(self, idx: slice, /) -> ca[D]: ...
118
+
119
+ def __getitem__(self, idx: int|slice, /) -> D|ca[D]:
120
+ if isinstance(idx, slice):
121
+ return ca(list(self)[idx])
122
+
123
+ cnt = self._cnt
124
+ if 0 <= idx < cnt:
125
+ return cast(D, self._data[(self._front + idx) % self._cap])
126
+ elif -cnt <= idx < 0:
127
+ return cast(D, self._data[(self._front + cnt + idx) % self._cap])
128
+ else:
129
+ if cnt > 0:
130
+ foo = [1, 2, 3]
131
+ foo.__setitem__
132
+ msg1 = 'Out of bounds: '
133
+ msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
134
+ msg3 = 'while getting value from a ca.'
135
+ raise IndexError(msg1 + msg2 + msg3)
136
+ else:
137
+ msg0 = 'Trying to get a value from an empty ca.'
138
+ raise IndexError(msg0)
139
+
140
+ @overload
141
+ def __setitem__(self, idx: int, vals: D, /) -> None: ...
142
+ @overload
143
+ def __setitem__(self, idx: slice, vals: Iterable[D], /) -> None: ...
144
+
145
+ def __setitem__(self, idx: int|slice, vals: D|Iterable[D], /) -> None:
146
+ if isinstance(idx, slice):
147
+ if isinstance(vals, Iterable):
148
+ data = list(self)
149
+ data[idx] = vals
150
+ _ca = ca(data)
151
+ self._data, self._cnt, self._cap, self._front, self._rear = \
152
+ _ca._data, _ca._cnt, _ca._cap, _ca._front, _ca._rear
153
+ return
154
+ else:
155
+ msg = 'must assign iterable to extended slice'
156
+ foo = [1,2,3]
157
+ foo.__delitem__(2)
158
+ raise TypeError(msg)
159
+
160
+ cnt = self._cnt
161
+ if 0 <= idx < cnt:
162
+ self._data[(self._front + idx) % self._cap] = cast(D, vals)
163
+ elif -cnt <= idx < 0:
164
+ self._data[(self._front + cnt + idx) % self._cap] = cast(D, vals)
165
+ else:
166
+ if cnt > 0:
167
+ msg1 = 'Out of bounds: '
168
+ msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
169
+ msg3 = 'while setting value from a ca.'
170
+ raise IndexError(msg1 + msg2 + msg3)
171
+ else:
172
+ msg0 = 'Trying to set a value from an empty ca.'
173
+ raise IndexError(msg0)
174
+
175
+ @overload
176
+ def __delitem__(self, idx: int) -> None: ...
177
+ @overload
178
+ def __delitem__(self, idx: slice) -> None: ...
179
+
180
+ def __delitem__(self, idx: int|slice) -> None:
181
+ data = list(self)
182
+ del data[idx]
183
+ _ca = ca(data)
184
+ self._data, self._cnt, self._cap, self._front, self._rear = \
185
+ _ca._data, _ca._cnt, _ca._cap, _ca._front, _ca._rear
186
+ return
187
+
188
+ def __eq__(self, other: object, /) -> bool:
189
+ if self is other:
190
+ return True
191
+ if not isinstance(other, type(self)):
192
+ return False
193
+
194
+ frontL, frontR, \
195
+ countL, countR, \
196
+ capacityL, capacityR = \
197
+ self._front, other._front, \
198
+ self._cnt, other._cnt, \
199
+ self._cap, other._cap
200
+
201
+ if countL != countR:
202
+ return False
203
+
204
+ for nn in range(countL):
205
+ if self._data[(frontL+nn)%capacityL] is other._data[(frontR+nn)%capacityR]:
206
+ continue
207
+ if self._data[(frontL+nn)%capacityL] != other._data[(frontR+nn)%capacityR]:
208
+ return False
209
+ return True
210
+
211
+ def pushL(self, *ds: D) -> None:
212
+ """Push data from the left onto the ca."""
213
+ for d in ds:
214
+ if self._cnt == self._cap:
215
+ self._double_storage_capacity()
216
+ self._front = (self._front - 1) % self._cap
217
+ self._data[self._front], self._cnt = d, self._cnt + 1
218
+
219
+ def pushR(self, *ds: D) -> None:
220
+ """Push data from the right onto the ca."""
221
+ for d in ds:
222
+ if self._cnt == self._cap:
223
+ self._double_storage_capacity()
224
+ self._rear = (self._rear + 1) % self._cap
225
+ self._data[self._rear], self._cnt = d, self._cnt + 1
226
+
227
+ def popL(self) -> D|Never:
228
+ """Pop one value off the left side of the ca.
229
+
230
+ * raises `ValueError` when called on an empty ca
231
+ """
232
+ if self._cnt > 1:
233
+ d, self._data[self._front], self._front, self._cnt = \
234
+ self._data[self._front], None, (self._front+1) % self._cap, self._cnt - 1
235
+ elif self._cnt < 1:
236
+ msg = 'Method popL called on an empty ca'
237
+ raise ValueError(msg)
238
+ else:
239
+ d, self._data[self._front], self._cnt, self._front, self._rear = \
240
+ self._data[self._front], None, 0, 0, self._cap - 1
241
+ return cast(D, d)
242
+
243
+ def popR(self) -> D|Never:
244
+ """Pop one value off the right side of the ca.
245
+
246
+ * raises `ValueError` when called on an empty ca
247
+ """
248
+ if self._cnt > 0:
249
+ d, self._data[self._rear], self._rear, self._cnt = \
250
+ self._data[self._rear], None, (self._rear - 1) % self._cap, self._cnt - 1
251
+ elif self._cnt < 1:
252
+ msg = 'Method popR called on an empty ca'
253
+ raise ValueError(msg)
254
+ else:
255
+ d, self._data[self._front], self._cnt, self._front, self._rear = \
256
+ self._data[self._front], None, 0, 0, self._cap - 1
257
+ return cast(D, d)
258
+
259
+ def popLD(self, default: D, /) -> D:
260
+ """Pop one value from left, provide a mandatory default value.
261
+
262
+ * safe version of popL
263
+ * returns a default value in the event the `ca` is empty
264
+ """
265
+ try:
266
+ return self.popL()
267
+ except ValueError:
268
+ return default
269
+
270
+ def popRD(self, default: D, /) -> D:
271
+ """Pop one value from right, provide a mandatory default value.
272
+
273
+ * safe version of popR
274
+ * returns a default value in the event the `ca` is empty
275
+ """
276
+ try:
277
+ return self.popR()
278
+ except ValueError:
279
+ return default
280
+
281
+ def popLT(self, max: int) -> tuple[D, ...]:
282
+ """Pop multiple values from left side of ca.
283
+
284
+ * returns the results in a tuple of type `tuple[~D, ...]`
285
+ * returns an empty tuple if `ca` is empty
286
+ * pop no more that `max` values
287
+ * will pop less if `ca` becomes empty
288
+ """
289
+ ds: list[D] = []
290
+
291
+ while max > 0:
292
+ try:
293
+ ds.append(self.popL())
294
+ except ValueError:
295
+ break
296
+ else:
297
+ max -= 1
298
+
299
+ return tuple(ds)
300
+
301
+ def popRT(self, max: int) -> tuple[D, ...]:
302
+ """Pop multiple values from right side of `ca`.
303
+
304
+ * returns the results in a tuple of type `tuple[~D, ...]`
305
+ * returns an empty tuple if `ca` is empty
306
+ * pop no more that `max` values
307
+ * will pop less if `ca` becomes empty
308
+ """
309
+ ds: list[D] = []
310
+ while max > 0:
311
+ try:
312
+ ds.append(self.popR())
313
+ except ValueError:
314
+ break
315
+ else:
316
+ max -= 1
317
+
318
+ return tuple(ds)
319
+
320
+ def rotL(self, n: int=1) -> None:
321
+ """Rotate ca arguments left n times."""
322
+ if self._cnt < 2:
323
+ return
324
+ while n > 0:
325
+ self.pushR(self.popL())
326
+ n -= 1
327
+
328
+ def rotR(self, n: int=1) -> None:
329
+ """Rotate ca arguments right n times."""
330
+ if self._cnt < 2:
331
+ return
332
+ while n > 0:
333
+ self.pushL(self.popR())
334
+ n -= 1
335
+
336
+ def map[U](self, f: Callable[[D], U], /) -> ca[U]:
337
+ """Apply function f over contents, returns new `ca` instance.
338
+
339
+ * parameter `f` function of type `f[~D, ~U] -> ca[~U]`
340
+ * returns a new instance of type `ca[~U]`
341
+ """
342
+ return ca(map(f, self))
343
+
344
+ def foldL[L](self, f: Callable[[L, D], L], /, initial: L|None=None) -> L:
345
+ """Left fold ca via function and optional initial value.
346
+
347
+ * parameter `f` function of type `f[~L, ~D] -> ~L`
348
+ * the first argument to `f` is for the accumulated value.
349
+ * parameter `initial` is an optional initial value
350
+ * returns the reduced value of type `~L`
351
+ * note that `~L` and `~D` can be the same type
352
+ * if an initial value is not given then by necessity `~L = ~D`
353
+ * raises `ValueError` when called on an empty `ca` and `initial` not given
354
+ """
355
+ if self._cnt == 0:
356
+ if initial is None:
357
+ msg = 'Method foldL called on an empty ca without an initial value.'
358
+ raise ValueError(msg)
359
+ else:
360
+ return initial
361
+ else:
362
+ if initial is None:
363
+ acc = cast(L, self[0]) # in this case D = L
364
+ for idx in range(1, self._cnt):
365
+ acc = f(acc, self[idx])
366
+ return acc
367
+ else:
368
+ acc = initial
369
+ for d in self:
370
+ acc = f(acc, d)
371
+ return acc
372
+
373
+ def foldR[R](self, f: Callable[[D, R], R], /, initial: R|None=None) -> R:
374
+ """Right fold ca via function and optional initial value.
375
+
376
+ * parameter `f` function of type `f[~D, ~R] -> ~R`
377
+ * the second argument to f is for the accumulated value
378
+ * parameter `initial` is an optional initial value
379
+ * returns the reduced value of type `~R`
380
+ * note that `~R` and `~D` can be the same type
381
+ * if an initial value is not given then by necessity `~R = ~D`
382
+ * raises `ValueError` when called on an empty `ca` and `initial` not given
383
+ """
384
+ if self._cnt == 0:
385
+ if initial is None:
386
+ msg = 'Method foldR called on an empty ca without an initial value.'
387
+ raise ValueError(msg)
388
+ else:
389
+ return initial
390
+ else:
391
+ if initial is None:
392
+ acc = cast(R, self[-1]) # in this case D = R
393
+ for idx in range(self._cnt-2, -1, -1):
394
+ acc = f(self[idx], acc)
395
+ return acc
396
+ else:
397
+ acc = initial
398
+ for d in reversed(self):
399
+ acc = f(d, acc)
400
+ return acc
401
+
402
+ def capacity(self) -> int:
403
+ """Returns current capacity of the ca."""
404
+ return self._cap
405
+
406
+ def empty(self) -> None:
407
+ """Empty the ca, keep current capacity."""
408
+ self._data, self._front, self._rear = [None]*self._cap, 0, self._cap
409
+
410
+ def fractionFilled(self) -> float:
411
+ """Returns fractional capacity of the ca."""
412
+ return self._cnt/self._cap
413
+
414
+ def resize(self, minimum_capacity: int=2) -> None:
415
+ """Compact `ca` and resize to `min_cap` if necessary.
416
+
417
+ * to just compact the `ca`, do not provide a min_cap
418
+ """
419
+ self._compact_storage_capacity()
420
+ if (min_cap := minimum_capacity) > self._cap:
421
+ self._cap, self._data = \
422
+ min_cap, self._data + [None]*(min_cap - self._cap)
423
+ if self._cnt == 0:
424
+ self._front, self._rear = 0, self._cap - 1
425
+
426
+ def CA[D](*ds: D) -> ca[D]:
427
+ """Function to produce a `ca` array from a variable number of arguments."""
428
+ return ca(ds)
429
+
File without changes
@@ -0,0 +1,190 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ Copyright (c) 2023-2024 Geoffrey R. Scheller
179
+
180
+ Licensed under the Apache License, Version 2.0 (the "License");
181
+ you may not use this file except in compliance with the License.
182
+ You may obtain a copy of the License at
183
+
184
+ http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+ Unless required by applicable law or agreed to in writing, software
187
+ distributed under the License is distributed on an "AS IS" BASIS,
188
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+ See the License for the specific language governing permissions and
190
+ limitations under the License.
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.3
2
+ Name: dtools.circular-array
3
+ Version: 3.9.0
4
+ Summary: ### Circular Array
5
+ Keywords: circular array,circle array,CA,double ended queue,dequeue,dqueue,pop,push,popL,popR,pushL,pushR,indexable,auto-resizing,auto resizing,resizing
6
+ Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
7
+ Requires-Python: >=3.12
8
+ Description-Content-Type: text/markdown
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
+ Requires-Dist: pytest >=8.3.2 ; extra == "test"
17
+ Project-URL: Changelog, https://github.com/grscheller/dtools-circular-array/blob/main/CHANGELOG.md
18
+ Project-URL: Documentation, https://grscheller.github.io/dtools-docs/circular-array
19
+ Project-URL: Source, https://github.com/grscheller/dtools-circular-array
20
+ Provides-Extra: test
21
+
22
+ # Python Circular Array Implementation
23
+
24
+ Python module implementing an indexable, double sided,
25
+ auto-resizing queue data structure.
26
+
27
+ * **Repositories**
28
+ * [dtools.circular-array][1] project on *PyPI*
29
+ * [Source code][2] on *GitHub*
30
+ * **Detailed documentation**
31
+ * [Detailed API documentation][3] on *GH-Pages*
32
+
33
+ ### Overview
34
+
35
+ Useful if used directly as an improved version of a Python List or in
36
+ a "has-a" relationship when implementing other data structures.
37
+
38
+ * O(1) pushes and pops either end.
39
+ * O(1) indexing
40
+ * now fully supports slicing!
41
+
42
+ ### Usage
43
+
44
+ ```python
45
+ from dtools.circular_array.ca import CA
46
+
47
+ ca = CA(1, 2, 3)
48
+ assert ca.popL() == 1
49
+ assert ca.popR() == 3
50
+ ca.pushR(42, 0)
51
+ ca.pushL(0, 1)
52
+ assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
53
+ assert str(ca) == '(|1, 0, 2, 42, 0|)'
54
+
55
+ ca = CA(*range(1,11))
56
+ assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
57
+ assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
58
+ assert len(ca) == 10
59
+ tup3 = ca.popLT(3)
60
+ tup4 = ca.popRT(4)
61
+ assert tup3 == (1, 2, 3)
62
+ assert tup4 == (10, 9, 8, 7)
63
+
64
+ assert ca == CA(4, 5, 6)
65
+ four, *rest = ca.popFT(1000)
66
+ assert four == 4
67
+ assert rest == [5, 6]
68
+ assert len(ca) == 0
69
+
70
+ ca = CA(1, 2, 3)
71
+ assert ca.popLD(42) == 1
72
+ assert ca.popRD(42) == 3
73
+ assert ca.popLD(42) == 2
74
+ assert ca.popRD(42) == 42
75
+ assert ca.popLD(42) == 42
76
+ assert len(ca) == 0
77
+ ```
78
+
79
+ ---
80
+
81
+ [1]: https://pypi.org/project/dtools.circular-array
82
+ [2]: https://github.com/grscheller/dtools-circular-array
83
+ [3]: https://grscheller.github.io/dtools-namespace-docs/circular-array/
84
+
85
+
@@ -0,0 +1,7 @@
1
+ dtools/circular_array/__init__.py,sha256=yHBn_zsWPfiTPmcdYe4XyO6I6tbx9RTMDBukau6zXWg,917
2
+ dtools/circular_array/ca.py,sha256=qoOjG-m_Z2XlYqRAA4w0HxSUaqz_U3E-7bpzqK-vtQY,15646
3
+ dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dtools_circular_array-3.9.0.dist-info/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
5
+ dtools_circular_array-3.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
6
+ dtools_circular_array-3.9.0.dist-info/METADATA,sha256=UgiPcUtbURZgznj3dUJjcOB5LDfEQeisr5vpN2PoAa4,2512
7
+ dtools_circular_array-3.9.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: flit 3.10.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any