dtools.circular-array 3.9.0__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.
@@ -1,4 +1,4 @@
1
- # Copyright 2016-2025 Geoffrey R. Scheller
1
+ # Copyright 2023-2025 Geoffrey R. Scheller
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,16 +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 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
- * module dtools.circular_array.ca: circular array Python data structure
22
+ * module dtools.circular_array.ca: circular array data structure
22
23
 
23
24
  """
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"
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,36 +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
19
+ from typing import cast, Never, overload, TypeVar
20
+
21
+ __all__ = ['ca', 'CA']
22
+
23
+ D = TypeVar('D') # Not needed for mypy, hint for pdoc.
24
+ L = TypeVar('L')
25
+ R = TypeVar('R')
26
+ U = TypeVar('U')
19
27
 
20
- __all__ = [ 'ca', 'CA' ]
21
28
 
22
29
  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
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`
38
44
 
39
45
  """
46
+
40
47
  __slots__ = '_data', '_cnt', '_cap', '_front', '_rear'
41
48
 
42
49
  def __init__(self, *dss: Iterable[D]) -> None:
43
50
  if len(dss) < 2:
44
- 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
+ )
45
54
  else:
46
55
  msg = f'ca expected at most 1 argument, got {len(dss)}'
47
56
  raise TypeError(msg)
@@ -56,33 +65,55 @@ class ca[D](Sequence[D]):
56
65
 
57
66
  def _double_storage_capacity(self) -> None:
58
67
  if self._front <= self._rear:
59
- self._data += [None]*self._cap
68
+ self._data += [None] * self._cap
60
69
  self._cap *= 2
61
70
  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
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
64
77
 
65
78
  def _compact_storage_capacity(self) -> None:
66
79
  """Compact the ca."""
67
80
  match self._cnt:
68
81
  case 0:
69
- self._cap, self._front, self._rear, self._data = \
70
- 2, 0, 1, [None, None]
82
+ self._cap, self._front, self._rear, self._data = 2, 0, 1, [None, None]
71
83
  case 1:
72
- self._cap, self._front, self._rear, self._data = \
73
- 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
+ )
74
90
  case _:
75
91
  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]
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
+ )
78
98
  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]
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
+ )
81
108
 
82
109
  def __iter__(self) -> Iterator[D]:
83
110
  if self._cnt > 0:
84
- capacity, rear, position, current_state = \
85
- 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
+ )
86
117
 
87
118
  while position != rear:
88
119
  yield cast(D, current_state[position])
@@ -91,8 +122,12 @@ class ca[D](Sequence[D]):
91
122
 
92
123
  def __reversed__(self) -> Iterator[D]:
93
124
  if self._cnt > 0:
94
- capacity, front, position, current_state = \
95
- 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
+ )
96
131
 
97
132
  while position != front:
98
133
  yield cast(D, current_state[position])
@@ -116,46 +151,48 @@ class ca[D](Sequence[D]):
116
151
  @overload
117
152
  def __getitem__(self, idx: slice, /) -> ca[D]: ...
118
153
 
119
- def __getitem__(self, idx: int|slice, /) -> D|ca[D]:
154
+ def __getitem__(self, idx: int | slice, /) -> D | ca[D]:
120
155
  if isinstance(idx, slice):
121
156
  return ca(list(self)[idx])
122
157
 
123
158
  cnt = self._cnt
124
159
  if 0 <= idx < cnt:
125
160
  return cast(D, self._data[(self._front + idx) % self._cap])
126
- elif -cnt <= idx < 0:
161
+
162
+ if -cnt <= idx < 0:
127
163
  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)
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)
139
173
 
140
174
  @overload
141
175
  def __setitem__(self, idx: int, vals: D, /) -> None: ...
142
176
  @overload
143
177
  def __setitem__(self, idx: slice, vals: Iterable[D], /) -> None: ...
144
178
 
145
- def __setitem__(self, idx: int|slice, vals: D|Iterable[D], /) -> None:
179
+ def __setitem__(self, idx: int | slice, vals: D | Iterable[D], /) -> None:
146
180
  if isinstance(idx, slice):
147
181
  if isinstance(vals, Iterable):
148
182
  data = list(self)
149
183
  data[idx] = vals
150
184
  _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
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
+ )
153
192
  return
154
- else:
155
- msg = 'must assign iterable to extended slice'
156
- foo = [1,2,3]
157
- foo.__delitem__(2)
158
- raise TypeError(msg)
193
+
194
+ msg = 'must assign iterable to extended slice'
195
+ raise TypeError(msg)
159
196
 
160
197
  cnt = self._cnt
161
198
  if 0 <= idx < cnt:
@@ -163,27 +200,31 @@ class ca[D](Sequence[D]):
163
200
  elif -cnt <= idx < 0:
164
201
  self._data[(self._front + cnt + idx) % self._cap] = cast(D, vals)
165
202
  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:
203
+ if cnt < 1:
172
204
  msg0 = 'Trying to set a value from an empty ca.'
173
205
  raise IndexError(msg0)
174
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
+
175
212
  @overload
176
213
  def __delitem__(self, idx: int) -> None: ...
177
214
  @overload
178
215
  def __delitem__(self, idx: slice) -> None: ...
179
216
 
180
- def __delitem__(self, idx: int|slice) -> None:
217
+ def __delitem__(self, idx: int | slice) -> None:
181
218
  data = list(self)
182
219
  del data[idx]
183
220
  _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
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
+ )
187
228
 
188
229
  def __eq__(self, other: object, /) -> bool:
189
230
  if self is other:
@@ -191,24 +232,32 @@ class ca[D](Sequence[D]):
191
232
  if not isinstance(other, type(self)):
192
233
  return False
193
234
 
194
- frontL, frontR, \
195
- countL, countR, \
196
- capacityL, capacityR = \
197
- self._front, other._front, \
198
- self._cnt, other._cnt, \
199
- 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
+ )
200
243
 
201
- if countL != countR:
244
+ if count1 != count2:
202
245
  return False
203
246
 
204
- for nn in range(countL):
205
- 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
+ ):
206
252
  continue
207
- 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
+ ):
208
257
  return False
209
258
  return True
210
259
 
211
- def pushL(self, *ds: D) -> None:
260
+ def pushl(self, *ds: D) -> None:
212
261
  """Push data from the left onto the ca."""
213
262
  for d in ds:
214
263
  if self._cnt == self._cap:
@@ -216,7 +265,7 @@ class ca[D](Sequence[D]):
216
265
  self._front = (self._front - 1) % self._cap
217
266
  self._data[self._front], self._cnt = d, self._cnt + 1
218
267
 
219
- def pushR(self, *ds: D) -> None:
268
+ def pushr(self, *ds: D) -> None:
220
269
  """Push data from the right onto the ca."""
221
270
  for d in ds:
222
271
  if self._cnt == self._cap:
@@ -224,73 +273,96 @@ class ca[D](Sequence[D]):
224
273
  self._rear = (self._rear + 1) % self._cap
225
274
  self._data[self._rear], self._cnt = d, self._cnt + 1
226
275
 
227
- def popL(self) -> D|Never:
276
+ def popl(self) -> D | Never:
228
277
  """Pop one value off the left side of the ca.
229
278
 
230
- * raises `ValueError` when called on an empty ca
279
+ Raises `ValueError` when called on an empty ca.
280
+
231
281
  """
232
282
  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)
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
+ )
238
297
  else:
239
- d, self._data[self._front], self._cnt, self._front, self._rear = \
240
- self._data[self._front], None, 0, 0, self._cap - 1
298
+ msg = 'Method popl called on an empty ca'
299
+ raise ValueError(msg)
241
300
  return cast(D, d)
242
301
 
243
- def popR(self) -> D|Never:
302
+ def popr(self) -> D | Never:
244
303
  """Pop one value off the right side of the ca.
245
304
 
246
- * raises `ValueError` when called on an empty ca
305
+ Raises `ValueError` when called on an empty ca.
306
+
247
307
  """
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)
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
+ )
254
323
  else:
255
- d, self._data[self._front], self._cnt, self._front, self._rear = \
256
- self._data[self._front], None, 0, 0, self._cap - 1
324
+ msg = 'Method popr called on an empty ca'
325
+ raise ValueError(msg)
257
326
  return cast(D, d)
258
327
 
259
- def popLD(self, default: D, /) -> D:
328
+ def popld(self, default: D, /) -> D:
260
329
  """Pop one value from left, provide a mandatory default value.
261
330
 
262
- * safe version of popL
263
- * 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
+
264
334
  """
265
335
  try:
266
- return self.popL()
336
+ return self.popl()
267
337
  except ValueError:
268
338
  return default
269
339
 
270
- def popRD(self, default: D, /) -> D:
340
+ def poprd(self, default: D, /) -> D:
271
341
  """Pop one value from right, provide a mandatory default value.
272
342
 
273
- * safe version of popR
274
- * 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
+
275
346
  """
276
347
  try:
277
- return self.popR()
348
+ return self.popr()
278
349
  except ValueError:
279
350
  return default
280
351
 
281
- def popLT(self, max: int) -> tuple[D, ...]:
352
+ def poplt(self, max: int) -> tuple[D, ...]:
282
353
  """Pop multiple values from left side of ca.
283
354
 
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
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
+
288
360
  """
289
361
  ds: list[D] = []
290
362
 
291
363
  while max > 0:
292
364
  try:
293
- ds.append(self.popL())
365
+ ds.append(self.popl())
294
366
  except ValueError:
295
367
  break
296
368
  else:
@@ -298,18 +370,19 @@ class ca[D](Sequence[D]):
298
370
 
299
371
  return tuple(ds)
300
372
 
301
- def popRT(self, max: int) -> tuple[D, ...]:
373
+ def poprt(self, max: int) -> tuple[D, ...]:
302
374
  """Pop multiple values from right side of `ca`.
303
375
 
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
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
+
308
381
  """
309
382
  ds: list[D] = []
310
383
  while max > 0:
311
384
  try:
312
- ds.append(self.popR())
385
+ ds.append(self.popr())
313
386
  except ValueError:
314
387
  break
315
388
  else:
@@ -317,87 +390,88 @@ class ca[D](Sequence[D]):
317
390
 
318
391
  return tuple(ds)
319
392
 
320
- def rotL(self, n: int=1) -> None:
393
+ def rotl(self, n: int = 1) -> None:
321
394
  """Rotate ca arguments left n times."""
322
395
  if self._cnt < 2:
323
396
  return
324
397
  while n > 0:
325
- self.pushR(self.popL())
398
+ self.pushr(self.popl())
326
399
  n -= 1
327
400
 
328
- def rotR(self, n: int=1) -> None:
401
+ def rotr(self, n: int = 1) -> None:
329
402
  """Rotate ca arguments right n times."""
330
403
  if self._cnt < 2:
331
404
  return
332
405
  while n > 0:
333
- self.pushL(self.popR())
406
+ self.pushl(self.popr())
334
407
  n -= 1
335
408
 
336
409
  def map[U](self, f: Callable[[D], U], /) -> ca[U]:
337
410
  """Apply function f over contents, returns new `ca` instance.
338
411
 
339
- * parameter `f` function of type `f[~D, ~U] -> ca[~U]`
340
- * 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
+
341
415
  """
342
416
  return ca(map(f, self))
343
417
 
344
- 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:
345
419
  """Left fold ca via function and optional initial value.
346
420
 
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
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
+
354
429
  """
355
430
  if self._cnt == 0:
356
431
  if initial is None:
357
432
  msg = 'Method foldL called on an empty ca without an initial value.'
358
433
  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
434
+ return initial
372
435
 
373
- 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:
374
448
  """Right fold ca via function and optional initial value.
375
449
 
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
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
+
383
458
  """
384
459
  if self._cnt == 0:
385
460
  if initial is None:
386
- 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.'
387
462
  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
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
401
475
 
402
476
  def capacity(self) -> int:
403
477
  """Returns current capacity of the ca."""
@@ -405,25 +479,30 @@ class ca[D](Sequence[D]):
405
479
 
406
480
  def empty(self) -> None:
407
481
  """Empty the ca, keep current capacity."""
408
- 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
409
483
 
410
- def fractionFilled(self) -> float:
484
+ def fraction_filled(self) -> float:
411
485
  """Returns fractional capacity of the ca."""
412
- return self._cnt/self._cap
486
+ return self._cnt / self._cap
413
487
 
414
- def resize(self, minimum_capacity: int=2) -> None:
415
- """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.
416
492
 
417
- * to just compact the `ca`, do not provide a min_cap
418
493
  """
419
494
  self._compact_storage_capacity()
420
495
  if (min_cap := minimum_capacity) > self._cap:
421
- self._cap, self._data = \
422
- min_cap, self._data + [None]*(min_cap - self._cap)
496
+ self._cap, self._data = min_cap, self._data + [None] * (min_cap - self._cap)
423
497
  if self._cnt == 0:
424
498
  self._front, self._rear = 0, self._cap - 1
425
499
 
500
+
426
501
  def CA[D](*ds: D) -> ca[D]:
427
- """Function to produce a `ca` array from a variable number of arguments."""
428
- return ca(ds)
502
+ """Function to produce a `ca` array from a variable number of arguments.
429
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,85 +0,0 @@
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
-
@@ -1,7 +0,0 @@
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,,