dtools.circular-array 3.9.1__py3-none-any.whl → 3.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,17 +12,23 @@
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
- #### Modules
20
+ #### Circular Array Data Structure
20
21
 
21
- * module dtools.circular_array.ca: circular array data structure
22
+ * *module* dtools.circular_array.ca
23
+ * circular array data structure
24
+ * *class* dtools.circular_array.ca.CA
25
+ * initializer takes 1 or 0 iterables
26
+ * *function* dtools.circular_array.ca.ca
27
+ * factory function taking a variable number of arguments
22
28
 
23
29
  """
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
30
 
31
+ __version__ = '3.11.0'
32
+ __author__ = 'Geoffrey R. Scheller'
33
+ __copyright__ = 'Copyright (c) 2023-2025 Geoffrey R. Scheller'
34
+ __license__ = 'Apache License 2.0'
@@ -12,43 +12,47 @@
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
- L = TypeVar('L')
24
- R = TypeVar('R')
25
- U = TypeVar('U')
23
+ D = TypeVar('D') # type: ignore[unused-ignore] # Needed only for pdoc
24
+ L = TypeVar('L') # type: ignore[unused-ignore] # documentation generation.
25
+ R = TypeVar('R') # type: ignore[unused-ignore] # Otherwise, not needed
26
+ U = TypeVar('U') # type: ignore[unused-ignore] # by either MyPy or Python.
26
27
 
27
- 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
28
+
29
+ class CA[D](Sequence[D]):
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
- msg = f'ca expected at most 1 argument, got {len(dss)}'
55
+ msg = f'CA expected at most 1 argument, got {len(dss)}'
52
56
  raise TypeError(msg)
53
57
  self._cap = cap = len(self._data)
54
58
  self._cnt = cap - 2
@@ -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
- """Compact the ca."""
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])
@@ -105,7 +135,7 @@ class ca[D](Sequence[D]):
105
135
  yield cast(D, current_state[position])
106
136
 
107
137
  def __repr__(self) -> str:
108
- return 'CA(' + ', '.join(map(repr, self)) + ')'
138
+ return 'ca(' + ', '.join(map(repr, self)) + ')'
109
139
 
110
140
  def __str__(self) -> str:
111
141
  return '(|' + ', '.join(map(str, self)) + '|)'
@@ -119,48 +149,50 @@ class ca[D](Sequence[D]):
119
149
  @overload
120
150
  def __getitem__(self, idx: int, /) -> D: ...
121
151
  @overload
122
- def __getitem__(self, idx: slice, /) -> ca[D]: ...
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
- return ca(list(self)[idx])
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
- _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
184
+ _ca = CA(data)
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:
177
- msg0 = 'Trying to set a value from an empty ca.'
203
+ if cnt < 1:
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
- _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
220
+ _ca = CA(data)
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,125 +232,157 @@ 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:
217
- """Push data from the left onto the ca."""
260
+ def pushl(self, *ds: D) -> None:
261
+ """Push data from the left onto the CA."""
218
262
  for d in ds:
219
263
  if self._cnt == self._cap:
220
264
  self._double_storage_capacity()
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:
225
- """Push data from the right onto the ca."""
268
+ def pushr(self, *ds: D) -> None:
269
+ """Push data from the right onto the CA."""
226
270
  for d in ds:
227
271
  if self._cnt == self._cap:
228
272
  self._double_storage_capacity()
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:
233
- """Pop one value off the left side of the ca.
276
+ def popl(self) -> D | Never:
277
+ """Pop one value off the left side of the CA.
278
+
279
+ Raises `ValueError` when called on an empty CA.
234
280
 
235
- * raises `ValueError` when called on an empty ca
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:
249
- """Pop one value off the right side of the ca.
302
+ def popr(self) -> D | Never:
303
+ """Pop one value off the right side of the CA.
304
+
305
+ Raises `ValueError` when called on an empty CA.
250
306
 
251
- * raises `ValueError` when called on an empty ca
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, ...]:
287
- """Pop multiple values from left side of ca.
352
+ def poplt(self, maximum: int) -> tuple[D, ...]:
353
+ """Pop multiple values from left side of `CA`.
354
+
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
288
359
 
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
293
360
  """
294
361
  ds: list[D] = []
295
362
 
296
- while max > 0:
363
+ while maximum > 0:
297
364
  try:
298
- ds.append(self.popL())
365
+ ds.append(self.popl())
299
366
  except ValueError:
300
367
  break
301
368
  else:
302
- max -= 1
369
+ maximum -= 1
303
370
 
304
371
  return tuple(ds)
305
372
 
306
- def popRT(self, max: int) -> tuple[D, ...]:
307
- """Pop multiple values from right side of `ca`.
373
+ def poprt(self, max: int) -> tuple[D, ...]:
374
+ """Pop multiple values from right side of `CA`.
375
+
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
308
380
 
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
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,113 +390,114 @@ class ca[D](Sequence[D]):
322
390
 
323
391
  return tuple(ds)
324
392
 
325
- def rotL(self, n: int=1) -> None:
326
- """Rotate ca arguments left n times."""
393
+ def rotl(self, n: int = 1) -> None:
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:
334
- """Rotate ca arguments right n times."""
401
+ def rotr(self, n: int = 1) -> None:
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
- def map[U](self, f: Callable[[D], U], /) -> ca[U]:
342
- """Apply function f over contents, returns new `ca` instance.
409
+ def map[U](self, f: Callable[[D], U], /) -> CA[U]:
410
+ """Apply function f over contents, returns new `CA` instance.
411
+
412
+ - parameter `f` function of type `f[~D, ~U] -> CA[~U]`
413
+ - returns a new instance of type `CA[~U]`
343
414
 
344
- * parameter `f` function of type `f[~D, ~U] -> ca[~U]`
345
- * returns a new instance of type `ca[~U]`
346
415
  """
347
- return ca(map(f, self))
348
-
349
- def foldL[L](self, f: Callable[[L, D], L], /, initial: L|None=None) -> L:
350
- """Left fold ca via function and optional initial value.
351
-
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
416
+ return CA(map(f, self))
417
+
418
+ def foldl[L](self, f: Callable[[L, D], L], /, initial: L | None = None) -> L:
419
+ """Left fold `CA` via function and optional initial value.
420
+
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
- msg = 'Method foldL called on an empty ca without an initial value.'
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
377
-
378
- def foldR[R](self, f: Callable[[D, R], R], /, initial: R|None=None) -> R:
379
- """Right fold ca via function and optional initial value.
380
-
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
434
+ return initial
435
+
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:
448
+ """Right fold `CA` via function and optional initial value.
449
+
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 empty `CA` without 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
- """Returns current capacity of the ca."""
477
+ """Returns current capacity of the `CA`."""
409
478
  return self._cap
410
479
 
411
480
  def empty(self) -> None:
412
- """Empty the ca, keep current capacity."""
413
- self._data, self._front, self._rear = [None]*self._cap, 0, self._cap
481
+ """Empty the `CA`, keep current capacity."""
482
+ self._data, self._front, self._rear = [None] * self._cap, 0, self._cap
483
+
484
+ def fraction_filled(self) -> float:
485
+ """Returns fractional capacity of the `CA`."""
486
+ return self._cnt / self._cap
414
487
 
415
- def fractionFilled(self) -> float:
416
- """Returns fractional capacity of the ca."""
417
- return self._cnt/self._cap
488
+ def resize(self, minimum_capacity: int = 2) -> None:
489
+ """Compact `CA` and resize to `minimum_capacity` if necessary.
418
490
 
419
- def resize(self, minimum_capacity: int=2) -> None:
420
- """Compact `ca` and resize to `min_cap` if necessary.
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
 
431
- def CA[D](*ds: D) -> ca[D]:
432
- """Function to produce a `ca` array from a variable number of arguments."""
433
- return ca(ds)
434
500
 
501
+ def ca[D](*ds: D) -> CA[D]:
502
+ """Function to produce a `CA` array from a variable number of arguments."""
503
+ return CA(ds)
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: dtools.circular-array
3
+ Version: 3.11.0
4
+ Summary: ### Developer Tools - circular array data structure
5
+ Keywords: circular array,circle array,CA,dequeue,dqueue,FIFO,LIFO,pop,push,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
+ ca1 = ca(1, 2, 3)
67
+ assert ca1.popl() == 1
68
+ assert ca1.popr() == 3
69
+ ca1.pushr(42, 0)
70
+ ca1.pushl(0, 1)
71
+ assert repr(ca1) == 'ca(1, 0, 2, 42, 0)'
72
+ assert str(ca1) == '(|1, 0, 2, 42, 0|)'
73
+
74
+ ca2 = CA(range(1,11))
75
+ assert repr(ca2) == 'ca(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)'
76
+ assert str(ca2) == '(|1, 2, 3, 4, 5, 6, 7, 8, 9, 10|)'
77
+ assert len(ca2) == 10
78
+ tup3 = ca2.poplt(3)
79
+ tup4 = ca2.poprt(4)
80
+ assert tup3 == (1, 2, 3)
81
+ assert tup4 == (10, 9, 8, 7)
82
+ assert ca2 == CA(4, 5, 6)
83
+ four, *rest = ca.popft(1000)
84
+ assert four == 4
85
+ assert rest == [5, 6]
86
+ assert len(ca2) == 0
87
+
88
+ ca3 = CA([1, 2, 3])
89
+ assert ca3.popld(42) == 1
90
+ assert ca3.poprd(42) == 3
91
+ assert ca3.popld(42) == 2
92
+ assert ca3.poprd(42) == 42
93
+ assert ca3.popld(42) == 42
94
+ assert len(ca2) == 0
95
+ ```
96
+
97
+ ______________________________________________________________________
98
+
99
+ [1]: https://pypi.org/project/dtools.circular-array
100
+ [2]: https://github.com/grscheller/dtools-circular-array
101
+ [3]: https://grscheller.github.io/dtools-docs/circular-array
102
+ [4]: https://github.com/grscheller/dtools-docs
103
+
@@ -0,0 +1,7 @@
1
+ dtools/circular_array/__init__.py,sha256=JfokAp0Jh8b5oU6aYQKNG1_8FB1OFT4geAowCLQSGQ0,1222
2
+ dtools/circular_array/ca.py,sha256=nAyi-fuIriMt2R08Ibq_KCdKuMNM199hTfye7Ap9qj8,16604
3
+ dtools/circular_array/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ dtools_circular_array-3.11.0.dist-info/licenses/LICENSE,sha256=csqbZRvA3Nyuav1aszWvswE8CZtaKr-hMjjjcKqms7w,10774
5
+ dtools_circular_array-3.11.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
6
+ dtools_circular_array-3.11.0.dist-info/METADATA,sha256=lE6GWKO2BQU_EEKseM5UKSLqsz5Rc-s8YdAiQt8Gjvw,3491
7
+ dtools_circular_array-3.11.0.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,,