dtools.circular-array 3.9.1__py3-none-any.whl → 3.10.1__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,17 +12,18 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """### Circular Array
15
+ """### Developer Tools - circular array data structure
16
16
 
17
- Package for an indexable, sliceable circular array data structure.
17
+ Package for an indexable, sliceable, auto-resizing circular array
18
+ data structure with amortized O(1) pushes and pops either end.
18
19
 
19
20
  #### Modules
20
21
 
21
22
  * module dtools.circular_array.ca: circular array data structure
22
23
 
23
24
  """
24
- __version__ = "3.9.1"
25
- __author__ = "Geoffrey R. Scheller"
26
- __copyright__ = "Copyright (c) 2023-2025 Geoffrey R. Scheller"
27
- __license__ = "Apache License 2.0"
28
25
 
26
+ __version__ = '3.10.1'
27
+ __author__ = 'Geoffrey R. Scheller'
28
+ __copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
29
+ __license__ = 'Apache License 2.0'
@@ -12,41 +12,45 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- """### Indexable circular array data structure module."""
15
+ """### Module for an indexable circular array data structure."""
16
+
16
17
  from __future__ import annotations
17
18
  from collections.abc import Callable, Iterable, Iterator, Sequence
18
- from typing import Any, cast, Never, overload, TypeVar
19
+ from typing import cast, Never, overload, TypeVar
19
20
 
20
- __all__ = [ 'ca', 'CA' ]
21
+ __all__ = ['ca', 'CA']
21
22
 
22
- D = TypeVar('D')
23
+ D = TypeVar('D') # Not needed for mypy, hint for pdoc.
23
24
  L = TypeVar('L')
24
25
  R = TypeVar('R')
25
26
  U = TypeVar('U')
26
27
 
28
+
27
29
  class ca[D](Sequence[D]):
28
- """
29
- #### Indexable circular array data structure
30
-
31
- * generic, stateful data structure
32
- * amortized O(1) pushing and popping from either end
33
- * O(1) random access any element
34
- * will resize itself as needed
35
- * sliceable
36
- * makes defensive copies of contents for the purposes of iteration
37
- * in boolean context returns true if not empty, false if empty
38
- * in comparisons compare identity before equality (like Python built-ins do)
39
- * lowercase class name choosen to match built-ins like `list` and `tuple`
40
- * raises `IndexError` for out-of-bounds indexing
41
- * raises `ValueError` for popping from or folding an empty `ca`
42
- * raises `TypeError` if 2 or more arguments are passed to constructor
30
+ """Indexable circular array data structure
31
+
32
+ - generic, stateful data structure
33
+ - lowercase class name chosen to match nomenclature for builtins
34
+ - like `list` and `tuple`
35
+ - amortized O(1) pushing and popping from either end
36
+ - O(1) random access any element
37
+ - will resize itself as needed
38
+ - sliceable
39
+ - makes defensive copies of contents for the purposes of iteration
40
+ - in boolean context returns true if not empty, false if empty
41
+ - in comparisons compare identity before equality (like builtins)
42
+ - raises `IndexError` for out-of-bounds indexing
43
+ - raises `ValueError` for popping from or folding an empty `ca`
43
44
 
44
45
  """
46
+
45
47
  __slots__ = '_data', '_cnt', '_cap', '_front', '_rear'
46
48
 
47
49
  def __init__(self, *dss: Iterable[D]) -> None:
48
50
  if len(dss) < 2:
49
- self._data: list[D|None] = [None] + cast(list[D|None], list(*dss)) + [None]
51
+ self._data: list[D | None] = (
52
+ [None] + cast(list[D | None], list(*dss)) + [None]
53
+ )
50
54
  else:
51
55
  msg = f'ca expected at most 1 argument, got {len(dss)}'
52
56
  raise TypeError(msg)
@@ -61,33 +65,55 @@ class ca[D](Sequence[D]):
61
65
 
62
66
  def _double_storage_capacity(self) -> None:
63
67
  if self._front <= self._rear:
64
- self._data += [None]*self._cap
68
+ self._data += [None] * self._cap
65
69
  self._cap *= 2
66
70
  else:
67
- self._data = self._data[:self._front] + [None]*self._cap + self._data[self._front:]
68
- self._front, self._cap = self._front + self._cap, 2*self._cap
71
+ self._data = (
72
+ self._data[: self._front]
73
+ + [None] * self._cap
74
+ + self._data[self._front :]
75
+ )
76
+ self._front, self._cap = self._front + self._cap, 2 * self._cap
69
77
 
70
78
  def _compact_storage_capacity(self) -> None:
71
79
  """Compact the ca."""
72
80
  match self._cnt:
73
81
  case 0:
74
- self._cap, self._front, self._rear, self._data = \
75
- 2, 0, 1, [None, None]
82
+ self._cap, self._front, self._rear, self._data = 2, 0, 1, [None, None]
76
83
  case 1:
77
- self._cap, self._front, self._rear, self._data = \
78
- 3, 1, 1, [None, self._data[self._front], None]
84
+ self._cap, self._front, self._rear, self._data = (
85
+ 3,
86
+ 1,
87
+ 1,
88
+ [None, self._data[self._front], None],
89
+ )
79
90
  case _:
80
91
  if self._front <= self._rear:
81
- self._cap, self._front, self._rear, self._data = \
82
- self._cnt+2, 1, self._cnt, [None] + self._data[self._front:self._rear+1] + [None]
92
+ self._cap, self._front, self._rear, self._data = (
93
+ self._cnt + 2,
94
+ 1,
95
+ self._cnt,
96
+ [None] + self._data[self._front : self._rear + 1] + [None],
97
+ )
83
98
  else:
84
- self._cap, self._front, self._rear, self._data = \
85
- self._cnt+2, 1, self._cnt, [None] + self._data[self._front:] + self._data[:self._rear+1] + [None]
99
+ self._cap, self._front, self._rear, self._data = (
100
+ self._cnt + 2,
101
+ 1,
102
+ self._cnt,
103
+ [None]
104
+ + self._data[self._front :]
105
+ + self._data[: self._rear + 1]
106
+ + [None],
107
+ )
86
108
 
87
109
  def __iter__(self) -> Iterator[D]:
88
110
  if self._cnt > 0:
89
- capacity, rear, position, current_state = \
90
- self._cap, self._rear, self._front, self._data.copy()
111
+ capacity, rear, position, current_state = (
112
+ self._cap,
113
+ self._rear,
114
+ self._front,
115
+ self._data.copy(),
116
+ )
91
117
 
92
118
  while position != rear:
93
119
  yield cast(D, current_state[position])
@@ -96,8 +122,12 @@ class ca[D](Sequence[D]):
96
122
 
97
123
  def __reversed__(self) -> Iterator[D]:
98
124
  if self._cnt > 0:
99
- capacity, front, position, current_state = \
100
- self._cap, self._front, self._rear, self._data.copy()
125
+ capacity, front, position, current_state = (
126
+ self._cap,
127
+ self._front,
128
+ self._rear,
129
+ self._data.copy(),
130
+ )
101
131
 
102
132
  while position != front:
103
133
  yield cast(D, current_state[position])
@@ -121,46 +151,48 @@ class ca[D](Sequence[D]):
121
151
  @overload
122
152
  def __getitem__(self, idx: slice, /) -> ca[D]: ...
123
153
 
124
- def __getitem__(self, idx: int|slice, /) -> D|ca[D]:
154
+ def __getitem__(self, idx: int | slice, /) -> D | ca[D]:
125
155
  if isinstance(idx, slice):
126
156
  return ca(list(self)[idx])
127
157
 
128
158
  cnt = self._cnt
129
159
  if 0 <= idx < cnt:
130
160
  return cast(D, self._data[(self._front + idx) % self._cap])
131
- elif -cnt <= idx < 0:
161
+
162
+ if -cnt <= idx < 0:
132
163
  return cast(D, self._data[(self._front + cnt + idx) % self._cap])
133
- else:
134
- if cnt > 0:
135
- foo = [1, 2, 3]
136
- foo.__setitem__
137
- msg1 = 'Out of bounds: '
138
- msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
139
- msg3 = 'while getting value from a ca.'
140
- raise IndexError(msg1 + msg2 + msg3)
141
- else:
142
- msg0 = 'Trying to get a value from an empty ca.'
143
- raise IndexError(msg0)
164
+
165
+ if cnt == 0:
166
+ msg0 = 'Trying to get a value from an empty ca.'
167
+ raise IndexError(msg0)
168
+
169
+ msg1 = 'Out of bounds: '
170
+ msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
171
+ msg3 = 'while getting value from a ca.'
172
+ raise IndexError(msg1 + msg2 + msg3)
144
173
 
145
174
  @overload
146
175
  def __setitem__(self, idx: int, vals: D, /) -> None: ...
147
176
  @overload
148
177
  def __setitem__(self, idx: slice, vals: Iterable[D], /) -> None: ...
149
178
 
150
- def __setitem__(self, idx: int|slice, vals: D|Iterable[D], /) -> None:
179
+ def __setitem__(self, idx: int | slice, vals: D | Iterable[D], /) -> None:
151
180
  if isinstance(idx, slice):
152
181
  if isinstance(vals, Iterable):
153
182
  data = list(self)
154
183
  data[idx] = vals
155
184
  _ca = ca(data)
156
- self._data, self._cnt, self._cap, self._front, self._rear = \
157
- _ca._data, _ca._cnt, _ca._cap, _ca._front, _ca._rear
185
+ self._data, self._cnt, self._cap, self._front, self._rear = (
186
+ _ca._data,
187
+ _ca._cnt,
188
+ _ca._cap,
189
+ _ca._front,
190
+ _ca._rear,
191
+ )
158
192
  return
159
- else:
160
- msg = 'must assign iterable to extended slice'
161
- foo = [1,2,3]
162
- foo.__delitem__(2)
163
- raise TypeError(msg)
193
+
194
+ msg = 'must assign iterable to extended slice'
195
+ raise TypeError(msg)
164
196
 
165
197
  cnt = self._cnt
166
198
  if 0 <= idx < cnt:
@@ -168,27 +200,31 @@ class ca[D](Sequence[D]):
168
200
  elif -cnt <= idx < 0:
169
201
  self._data[(self._front + cnt + idx) % self._cap] = cast(D, vals)
170
202
  else:
171
- if cnt > 0:
172
- msg1 = 'Out of bounds: '
173
- msg2 = f'index = {idx} not between {-cnt} and {cnt-1} '
174
- msg3 = 'while setting value from a ca.'
175
- raise IndexError(msg1 + msg2 + msg3)
176
- else:
203
+ if cnt < 1:
177
204
  msg0 = 'Trying to set a value from an empty ca.'
178
205
  raise IndexError(msg0)
179
206
 
207
+ msg1 = 'Out of bounds: '
208
+ msg2 = f'index = {idx} not between {-cnt} and {cnt - 1} '
209
+ msg3 = 'while setting value from a ca.'
210
+ raise IndexError(msg1 + msg2 + msg3)
211
+
180
212
  @overload
181
213
  def __delitem__(self, idx: int) -> None: ...
182
214
  @overload
183
215
  def __delitem__(self, idx: slice) -> None: ...
184
216
 
185
- def __delitem__(self, idx: int|slice) -> None:
217
+ def __delitem__(self, idx: int | slice) -> None:
186
218
  data = list(self)
187
219
  del data[idx]
188
220
  _ca = ca(data)
189
- self._data, self._cnt, self._cap, self._front, self._rear = \
190
- _ca._data, _ca._cnt, _ca._cap, _ca._front, _ca._rear
191
- return
221
+ self._data, self._cnt, self._cap, self._front, self._rear = (
222
+ _ca._data,
223
+ _ca._cnt,
224
+ _ca._cap,
225
+ _ca._front,
226
+ _ca._rear,
227
+ )
192
228
 
193
229
  def __eq__(self, other: object, /) -> bool:
194
230
  if self is other:
@@ -196,24 +232,32 @@ class ca[D](Sequence[D]):
196
232
  if not isinstance(other, type(self)):
197
233
  return False
198
234
 
199
- frontL, frontR, \
200
- countL, countR, \
201
- capacityL, capacityR = \
202
- self._front, other._front, \
203
- self._cnt, other._cnt, \
204
- self._cap, other._cap
235
+ front1, front2, count1, count2, capacity1, capacity2 = (
236
+ self._front,
237
+ other._front,
238
+ self._cnt,
239
+ other._cnt,
240
+ self._cap,
241
+ other._cap,
242
+ )
205
243
 
206
- if countL != countR:
244
+ if count1 != count2:
207
245
  return False
208
246
 
209
- for nn in range(countL):
210
- if self._data[(frontL+nn)%capacityL] is other._data[(frontR+nn)%capacityR]:
247
+ for nn in range(count1):
248
+ if (
249
+ self._data[(front1 + nn) % capacity1]
250
+ is other._data[(front2 + nn) % capacity2]
251
+ ):
211
252
  continue
212
- if self._data[(frontL+nn)%capacityL] != other._data[(frontR+nn)%capacityR]:
253
+ if (
254
+ self._data[(front1 + nn) % capacity1]
255
+ != other._data[(front2 + nn) % capacity2]
256
+ ):
213
257
  return False
214
258
  return True
215
259
 
216
- def pushL(self, *ds: D) -> None:
260
+ def pushl(self, *ds: D) -> None:
217
261
  """Push data from the left onto the ca."""
218
262
  for d in ds:
219
263
  if self._cnt == self._cap:
@@ -221,7 +265,7 @@ class ca[D](Sequence[D]):
221
265
  self._front = (self._front - 1) % self._cap
222
266
  self._data[self._front], self._cnt = d, self._cnt + 1
223
267
 
224
- def pushR(self, *ds: D) -> None:
268
+ def pushr(self, *ds: D) -> None:
225
269
  """Push data from the right onto the ca."""
226
270
  for d in ds:
227
271
  if self._cnt == self._cap:
@@ -229,73 +273,96 @@ class ca[D](Sequence[D]):
229
273
  self._rear = (self._rear + 1) % self._cap
230
274
  self._data[self._rear], self._cnt = d, self._cnt + 1
231
275
 
232
- def popL(self) -> D|Never:
276
+ def popl(self) -> D | Never:
233
277
  """Pop one value off the left side of the ca.
234
278
 
235
- * raises `ValueError` when called on an empty ca
279
+ Raises `ValueError` when called on an empty ca.
280
+
236
281
  """
237
282
  if self._cnt > 1:
238
- d, self._data[self._front], self._front, self._cnt = \
239
- self._data[self._front], None, (self._front+1) % self._cap, self._cnt - 1
240
- elif self._cnt < 1:
241
- msg = 'Method popL called on an empty ca'
242
- raise ValueError(msg)
283
+ d, self._data[self._front], self._front, self._cnt = (
284
+ self._data[self._front],
285
+ None,
286
+ (self._front + 1) % self._cap,
287
+ self._cnt - 1,
288
+ )
289
+ elif self._cnt == 1:
290
+ d, self._data[self._front], self._cnt, self._front, self._rear = (
291
+ self._data[self._front],
292
+ None,
293
+ 0,
294
+ 0,
295
+ self._cap - 1,
296
+ )
243
297
  else:
244
- d, self._data[self._front], self._cnt, self._front, self._rear = \
245
- self._data[self._front], None, 0, 0, self._cap - 1
298
+ msg = 'Method popl called on an empty ca'
299
+ raise ValueError(msg)
246
300
  return cast(D, d)
247
301
 
248
- def popR(self) -> D|Never:
302
+ def popr(self) -> D | Never:
249
303
  """Pop one value off the right side of the ca.
250
304
 
251
- * raises `ValueError` when called on an empty ca
305
+ Raises `ValueError` when called on an empty ca.
306
+
252
307
  """
253
- if self._cnt > 0:
254
- d, self._data[self._rear], self._rear, self._cnt = \
255
- self._data[self._rear], None, (self._rear - 1) % self._cap, self._cnt - 1
256
- elif self._cnt < 1:
257
- msg = 'Method popR called on an empty ca'
258
- raise ValueError(msg)
308
+ if self._cnt > 1:
309
+ d, self._data[self._rear], self._rear, self._cnt = (
310
+ self._data[self._rear],
311
+ None,
312
+ (self._rear - 1) % self._cap,
313
+ self._cnt - 1,
314
+ )
315
+ elif self._cnt == 1:
316
+ d, self._data[self._front], self._cnt, self._front, self._rear = (
317
+ self._data[self._front],
318
+ None,
319
+ 0,
320
+ 0,
321
+ self._cap - 1,
322
+ )
259
323
  else:
260
- d, self._data[self._front], self._cnt, self._front, self._rear = \
261
- self._data[self._front], None, 0, 0, self._cap - 1
324
+ msg = 'Method popr called on an empty ca'
325
+ raise ValueError(msg)
262
326
  return cast(D, d)
263
327
 
264
- def popLD(self, default: D, /) -> D:
328
+ def popld(self, default: D, /) -> D:
265
329
  """Pop one value from left, provide a mandatory default value.
266
330
 
267
- * safe version of popL
268
- * returns a default value in the event the `ca` is empty
331
+ - safe version of popl
332
+ - returns a default value in the event the `ca` is empty
333
+
269
334
  """
270
335
  try:
271
- return self.popL()
336
+ return self.popl()
272
337
  except ValueError:
273
338
  return default
274
339
 
275
- def popRD(self, default: D, /) -> D:
340
+ def poprd(self, default: D, /) -> D:
276
341
  """Pop one value from right, provide a mandatory default value.
277
342
 
278
- * safe version of popR
279
- * returns a default value in the event the `ca` is empty
343
+ - safe version of popr
344
+ - returns a default value in the event the `ca` is empty
345
+
280
346
  """
281
347
  try:
282
- return self.popR()
348
+ return self.popr()
283
349
  except ValueError:
284
350
  return default
285
351
 
286
- def popLT(self, max: int) -> tuple[D, ...]:
352
+ def poplt(self, max: int) -> tuple[D, ...]:
287
353
  """Pop multiple values from left side of ca.
288
354
 
289
- * returns the results in a tuple of type `tuple[~D, ...]`
290
- * returns an empty tuple if `ca` is empty
291
- * pop no more that `max` values
292
- * will pop less if `ca` becomes empty
355
+ - returns the results in a tuple of type `tuple[~D, ...]`
356
+ - returns an empty tuple if `ca` is empty
357
+ - pop no more that `max` values
358
+ - will pop less if `ca` becomes empty
359
+
293
360
  """
294
361
  ds: list[D] = []
295
362
 
296
363
  while max > 0:
297
364
  try:
298
- ds.append(self.popL())
365
+ ds.append(self.popl())
299
366
  except ValueError:
300
367
  break
301
368
  else:
@@ -303,18 +370,19 @@ class ca[D](Sequence[D]):
303
370
 
304
371
  return tuple(ds)
305
372
 
306
- def popRT(self, max: int) -> tuple[D, ...]:
373
+ def poprt(self, max: int) -> tuple[D, ...]:
307
374
  """Pop multiple values from right side of `ca`.
308
375
 
309
- * returns the results in a tuple of type `tuple[~D, ...]`
310
- * returns an empty tuple if `ca` is empty
311
- * pop no more that `max` values
312
- * will pop less if `ca` becomes empty
376
+ - returns the results in a tuple of type `tuple[~D, ...]`
377
+ - returns an empty tuple if `ca` is empty
378
+ - pop no more that `max` values
379
+ - will pop less if `ca` becomes empty
380
+
313
381
  """
314
382
  ds: list[D] = []
315
383
  while max > 0:
316
384
  try:
317
- ds.append(self.popR())
385
+ ds.append(self.popr())
318
386
  except ValueError:
319
387
  break
320
388
  else:
@@ -322,87 +390,88 @@ class ca[D](Sequence[D]):
322
390
 
323
391
  return tuple(ds)
324
392
 
325
- def rotL(self, n: int=1) -> None:
393
+ def rotl(self, n: int = 1) -> None:
326
394
  """Rotate ca arguments left n times."""
327
395
  if self._cnt < 2:
328
396
  return
329
397
  while n > 0:
330
- self.pushR(self.popL())
398
+ self.pushr(self.popl())
331
399
  n -= 1
332
400
 
333
- def rotR(self, n: int=1) -> None:
401
+ def rotr(self, n: int = 1) -> None:
334
402
  """Rotate ca arguments right n times."""
335
403
  if self._cnt < 2:
336
404
  return
337
405
  while n > 0:
338
- self.pushL(self.popR())
406
+ self.pushl(self.popr())
339
407
  n -= 1
340
408
 
341
409
  def map[U](self, f: Callable[[D], U], /) -> ca[U]:
342
410
  """Apply function f over contents, returns new `ca` instance.
343
411
 
344
- * parameter `f` function of type `f[~D, ~U] -> ca[~U]`
345
- * returns a new instance of type `ca[~U]`
412
+ - parameter `f` function of type `f[~D, ~U] -> ca[~U]`
413
+ - returns a new instance of type `ca[~U]`
414
+
346
415
  """
347
416
  return ca(map(f, self))
348
417
 
349
- def foldL[L](self, f: Callable[[L, D], L], /, initial: L|None=None) -> L:
418
+ def foldl[L](self, f: Callable[[L, D], L], /, initial: L | None = None) -> L:
350
419
  """Left fold ca via function and optional initial value.
351
420
 
352
- * parameter `f` function of type `f[~L, ~D] -> ~L`
353
- * the first argument to `f` is for the accumulated value.
354
- * parameter `initial` is an optional initial value
355
- * returns the reduced value of type `~L`
356
- * note that `~L` and `~D` can be the same type
357
- * if an initial value is not given then by necessity `~L = ~D`
358
- * raises `ValueError` when called on an empty `ca` and `initial` not given
421
+ - parameter `f` function of type `f[~L, ~D] -> ~L`
422
+ - the first argument to `f` is for the accumulated value.
423
+ - parameter `initial` is an optional initial value
424
+ - returns the reduced value of type `~L`
425
+ - note that `~L` and `~D` can be the same type
426
+ - if an initial value is not given then by necessity `~L = ~D`
427
+ - raises `ValueError` when called on an empty `ca` and `initial` not given
428
+
359
429
  """
360
430
  if self._cnt == 0:
361
431
  if initial is None:
362
432
  msg = 'Method foldL called on an empty ca without an initial value.'
363
433
  raise ValueError(msg)
364
- else:
365
- return initial
366
- else:
367
- if initial is None:
368
- acc = cast(L, self[0]) # in this case D = L
369
- for idx in range(1, self._cnt):
370
- acc = f(acc, self[idx])
371
- return acc
372
- else:
373
- acc = initial
374
- for d in self:
375
- acc = f(acc, d)
376
- return acc
434
+ return initial
377
435
 
378
- def foldR[R](self, f: Callable[[D, R], R], /, initial: R|None=None) -> R:
436
+ if initial is None:
437
+ acc = cast(L, self[0]) # in this case D = L
438
+ for idx in range(1, self._cnt):
439
+ acc = f(acc, self[idx])
440
+ return acc
441
+
442
+ acc = initial
443
+ for d in self:
444
+ acc = f(acc, d)
445
+ return acc
446
+
447
+ def foldr[R](self, f: Callable[[D, R], R], /, initial: R | None = None) -> R:
379
448
  """Right fold ca via function and optional initial value.
380
449
 
381
- * parameter `f` function of type `f[~D, ~R] -> ~R`
382
- * the second argument to f is for the accumulated value
383
- * parameter `initial` is an optional initial value
384
- * returns the reduced value of type `~R`
385
- * note that `~R` and `~D` can be the same type
386
- * if an initial value is not given then by necessity `~R = ~D`
387
- * raises `ValueError` when called on an empty `ca` and `initial` not given
450
+ - parameter `f` function of type `f[~D, ~R] -> ~R`
451
+ - the second argument to f is for the accumulated value
452
+ - parameter `initial` is an optional initial value
453
+ - returns the reduced value of type `~R`
454
+ - note that `~R` and `~D` can be the same type
455
+ - if an initial value is not given then by necessity `~R = ~D`
456
+ - raises `ValueError` when called on an empty `ca` and `initial` not given
457
+
388
458
  """
389
459
  if self._cnt == 0:
390
460
  if initial is None:
391
- msg = 'Method foldR called on an empty ca without an initial value.'
461
+ msg = 'Method foldr called on an empty ca without an initial value.'
392
462
  raise ValueError(msg)
393
- else:
394
- return initial
395
- else:
396
- if initial is None:
397
- acc = cast(R, self[-1]) # in this case D = R
398
- for idx in range(self._cnt-2, -1, -1):
399
- acc = f(self[idx], acc)
400
- return acc
401
- else:
402
- acc = initial
403
- for d in reversed(self):
404
- acc = f(d, acc)
405
- return acc
463
+ return initial
464
+
465
+ if initial is None:
466
+ acc = cast(R, self[-1]) # in this case D = R
467
+ for idx in range(self._cnt - 2, -1, -1):
468
+ acc = f(self[idx], acc)
469
+ return acc
470
+
471
+ acc = initial
472
+ for d in reversed(self):
473
+ acc = f(d, acc)
474
+ return acc
406
475
 
407
476
  def capacity(self) -> int:
408
477
  """Returns current capacity of the ca."""
@@ -410,25 +479,30 @@ class ca[D](Sequence[D]):
410
479
 
411
480
  def empty(self) -> None:
412
481
  """Empty the ca, keep current capacity."""
413
- self._data, self._front, self._rear = [None]*self._cap, 0, self._cap
482
+ self._data, self._front, self._rear = [None] * self._cap, 0, self._cap
414
483
 
415
- def fractionFilled(self) -> float:
484
+ def fraction_filled(self) -> float:
416
485
  """Returns fractional capacity of the ca."""
417
- return self._cnt/self._cap
486
+ return self._cnt / self._cap
418
487
 
419
- def resize(self, minimum_capacity: int=2) -> None:
420
- """Compact `ca` and resize to `min_cap` if necessary.
488
+ def resize(self, minimum_capacity: int = 2) -> None:
489
+ """Compact `ca` and resize to `minimum_capacity` if necessary.
490
+
491
+ To just compact the `ca`, do not provide a minimum capacity.
421
492
 
422
- * to just compact the `ca`, do not provide a min_cap
423
493
  """
424
494
  self._compact_storage_capacity()
425
495
  if (min_cap := minimum_capacity) > self._cap:
426
- self._cap, self._data = \
427
- min_cap, self._data + [None]*(min_cap - self._cap)
496
+ self._cap, self._data = min_cap, self._data + [None] * (min_cap - self._cap)
428
497
  if self._cnt == 0:
429
498
  self._front, self._rear = 0, self._cap - 1
430
499
 
500
+
431
501
  def CA[D](*ds: D) -> ca[D]:
432
- """Function to produce a `ca` array from a variable number of arguments."""
433
- return ca(ds)
502
+ """Function to produce a `ca` array from a variable number of arguments.
434
503
 
504
+ Upper case function name used to stand out in place of syntactic sugar
505
+ used by builtins, like `[]` for list or `{}` for dict or set.
506
+
507
+ """
508
+ return ca(ds)
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: dtools.circular-array
3
+ Version: 3.10.1
4
+ Summary: ### Developer Tools - circular array data structure
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
+ License-File: LICENSE
17
+ Requires-Dist: pytest >=8.3.5 ; extra == "test"
18
+ Project-URL: Changelog, https://github.com/grscheller/dtools-circular-array/blob/main/CHANGELOG.md
19
+ Project-URL: Documentation, https://grscheller.github.io/dtools-docs/circular-array
20
+ Project-URL: Source, https://github.com/grscheller/dtools-circular-array
21
+ Provides-Extra: test
22
+
23
+ # Developer Tools - Circular Array
24
+
25
+ Python package containing a module implementing a circular array data
26
+ structure,
27
+
28
+ - **Repositories**
29
+ - [dtools.circular-array][1] project on *PyPI*
30
+ - [Source code][2] on *GitHub*
31
+ - **Detailed documentation**
32
+ - [Detailed API documentation][3] on *GH-Pages*
33
+
34
+ This project is part of the [Developer Tools for Python][4] **dtools.**
35
+ namespace project.
36
+
37
+ ## Overview
38
+
39
+ - O(1) amortized pushes and pops either end.
40
+ - O(1) indexing
41
+ - fully supports slicing
42
+ - safely mutates while iterating over copies of previous state
43
+
44
+ ### Module circular_array
45
+
46
+ A full featured, auto resizing circular array. Python package containing
47
+ a module implementing a full featured, indexable, sliceable, double
48
+ sided, auto-resizing circular array data structure.
49
+
50
+ Useful either if used directly as an improved version of a Python List
51
+ or in a "has-a" relationship when implementing other data structures.
52
+
53
+ - *module* dtools.circular_array
54
+ - *class* ca: circular array data structure
55
+ - *function* CA: factory function to produce a ca from data
56
+
57
+ Above nomenclature modeled after of a builtin data type like `list`, where
58
+ `ca` takes an optional iterator as an argument and CA is all caps to represent
59
+ syntactic sugar like `[]` or `{}`.
60
+
61
+ #### Usage
62
+
63
+ ```python
64
+ from dtools.circular_array.ca import CA
65
+
66
+ ca = CA(1, 2, 3)
67
+ assert ca.popl() == 1
68
+ assert ca.popr() == 3
69
+ ca.pushr(42, 0)
70
+ ca.pushl(0, 1)
71
+ assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
72
+ assert str(ca) == '(|1, 0, 2, 42, 0|)'
73
+
74
+ ca = ca(range(1,11))
75
+ assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
76
+ assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
77
+ assert len(ca) == 10
78
+ tup3 = ca.poplt(3)
79
+ tup4 = ca.poprt(4)
80
+ assert tup3 == (1, 2, 3)
81
+ assert tup4 == (10, 9, 8, 7)
82
+
83
+ assert ca == CA(4, 5, 6)
84
+ four, *rest = ca.popft(1000)
85
+ assert four == 4
86
+ assert rest == [5, 6]
87
+ assert len(ca) == 0
88
+
89
+ ca = ca([1, 2, 3])
90
+ assert ca.popld(42) == 1
91
+ assert ca.poprd(42) == 3
92
+ assert ca.popld(42) == 2
93
+ assert ca.poprd(42) == 42
94
+ assert ca.popld(42) == 42
95
+ assert len(ca) == 0
96
+ ```
97
+
98
+ ______________________________________________________________________
99
+
100
+ [1]: https://pypi.org/project/dtools.circular-array
101
+ [2]: https://github.com/grscheller/dtools-circular-array
102
+ [3]: https://grscheller.github.io/dtools-docs/circular-array
103
+ [4]: https://github.com/grscheller/dtools-docs
104
+
@@ -0,0 +1,7 @@
1
+ dtools/circular_array/__init__.py,sha256=-GZLNI-R4UvHgg5pLNGxdNPYO8LX4Aq6eUp9rYB3Vkg,1018
2
+ dtools/circular_array/ca.py,sha256=FZe0ksC9BFzQXqqRY8YXaEpF0g38CR6-rdMOWcReKcw,16534
3
+ dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dtools_circular_array-3.10.1.dist-info/licenses/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
5
+ dtools_circular_array-3.10.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
6
+ dtools_circular_array-3.10.1.dist-info/METADATA,sha256=49FC3FiNoCuhYf7e_gf2KNlYDYZ9Gj8m4M25g2fTCjQ,3505
7
+ dtools_circular_array-3.10.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.10.1
2
+ Generator: flit 3.12.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,90 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: dtools.circular-array
3
- Version: 3.9.1
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
- # Developer Tools - Circular Array Implementation
23
-
24
- Python module implementing a full featured, indexable,
25
- double sided, 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
- This project is part of the
34
- [Developer Tools for Python][4] **dtools.** namespace project.
35
-
36
- ### Overview
37
-
38
- Useful if used directly as an improved version of a Python List or in
39
- a "has-a" relationship when implementing other data structures.
40
-
41
- * O(1) pushes and pops either end.
42
- * O(1) indexing
43
- * fully supports slicing
44
- * iterates over copies of the data allowing ca to mutate
45
-
46
- ### Usage
47
-
48
- ```python
49
- from dtools.circular_array.ca import CA
50
-
51
- ca = CA(1, 2, 3)
52
- assert ca.popL() == 1
53
- assert ca.popR() == 3
54
- ca.pushR(42, 0)
55
- ca.pushL(0, 1)
56
- assert repr(ca) == 'CA(1, 0, 2, 42, 0)'
57
- assert str(ca) == '(|1, 0, 2, 42, 0|)'
58
-
59
- ca = CA(*range(1,11))
60
- assert repr(ca) == 'CA(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
61
- assert str(ca) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
62
- assert len(ca) == 10
63
- tup3 = ca.popLT(3)
64
- tup4 = ca.popRT(4)
65
- assert tup3 == (1, 2, 3)
66
- assert tup4 == (10, 9, 8, 7)
67
-
68
- assert ca == CA(4, 5, 6)
69
- four, *rest = ca.popFT(1000)
70
- assert four == 4
71
- assert rest == [5, 6]
72
- assert len(ca) == 0
73
-
74
- ca = CA(1, 2, 3)
75
- assert ca.popLD(42) == 1
76
- assert ca.popRD(42) == 3
77
- assert ca.popLD(42) == 2
78
- assert ca.popRD(42) == 42
79
- assert ca.popLD(42) == 42
80
- assert len(ca) == 0
81
- ```
82
-
83
- ---
84
-
85
- [1]: https://pypi.org/project/dtools.circular-array
86
- [2]: https://github.com/grscheller/dtools-circular-array
87
- [3]: https://grscheller.github.io/dtools-docs/circular-array
88
- [4]: https://github.com/grscheller/dtools-docs
89
-
90
-
@@ -1,7 +0,0 @@
1
- dtools/circular_array/__init__.py,sha256=HdCpPnMesO8z3CGuoNfqMjYLRmiedHYdcQTGE6e6qf8,922
2
- dtools/circular_array/ca.py,sha256=a-hcJ4hQhLexqX2fqsOogEIPjpzR8PQ5zAE49-Xi8tc,15722
3
- dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- dtools_circular_array-3.9.1.dist-info/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
5
- dtools_circular_array-3.9.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
6
- dtools_circular_array-3.9.1.dist-info/METADATA,sha256=r80WBcYr2g_k-1uH27NICHx3be6BuKivHiI5tGyMtBY,2717
7
- dtools_circular_array-3.9.1.dist-info/RECORD,,