grscheller.circular_array.ca

Module for an indexable circular array data structure.

  1# Copyright 2023-2024 Geoffrey R. Scheller
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14
 15"""
 16### Module for an indexable circular array data structure.
 17"""
 18from __future__ import annotations
 19from typing import Callable, cast, Generic, Iterator, Optional, TypeVar, Never
 20
 21__all__ = ['CA']
 22
 23D = TypeVar('D')
 24U = TypeVar('U')
 25L = TypeVar('L')
 26R = TypeVar('R')
 27
 28class CA(Generic[D]):
 29    """
 30    #### Indexable circular array data structure
 31
 32    * generic, stateful data structure
 33    * amortized O(1) pushing and popping from either end
 34    * O(1) random access any element
 35    * will resize itself as needed
 36    * makes defensive copies of contents for the purposes of iteration
 37    * not sliceable
 38    * in boolean context returns true if not empty, false if empty
 39    * in comparisons contents compare with identity before equality
 40      * as do Python tuples, lists, and dicts
 41    * raises `IndexError` for out-of-bounds indexing
 42    * raises `ValueError` for popping from or folding an empty CA
 43    """
 44    __slots__ = '_list', '_count', '_capacity', '_front', '_rear'
 45
 46    def __init__(self, *ds: D) -> None:
 47        self._list: list[D|None] = [None] + list(ds) + [None]
 48        self._capacity = capacity = len(self._list)
 49        self._count = capacity - 2
 50        if capacity == 2:
 51            self._front = 0
 52            self._rear = 1
 53        else:
 54            self._front = 1
 55            self._rear = capacity - 2
 56
 57    def _double_storage_capacity(self) -> None:
 58        if self._front <= self._rear:
 59            self._list += [None]*self._capacity
 60            self._capacity *= 2
 61        else:
 62            self._list = self._list[:self._front] + [None]*self._capacity + self._list[self._front:]
 63            self._front, self._capacity = self._front + self._capacity, 2*self._capacity
 64
 65    def __iter__(self) -> Iterator[D]:
 66        if self._count > 0:
 67            capacity, rear, position, current_state = \
 68                self._capacity, self._rear, self._front, self._list.copy()
 69
 70            while position != rear:
 71                yield cast(D, current_state[position])
 72                position = (position + 1) % capacity
 73            yield cast(D, current_state[position])
 74
 75    def __reversed__(self) -> Iterator[D]:
 76        if self._count > 0:
 77            capacity, front, position, current_state = \
 78                self._capacity, self._front, self._rear, self._list.copy()
 79
 80            while position != front:
 81                yield cast(D, current_state[position])
 82                position = (position - 1) % capacity
 83            yield cast(D, current_state[position])
 84
 85    def __repr__(self) -> str:
 86        return 'CA(' + ', '.join(map(repr, self)) + ')'
 87
 88    def __str__(self) -> str:
 89        return '(|' + ', '.join(map(str, self)) + '|)'
 90
 91    def __bool__(self) -> bool:
 92        return self._count > 0
 93
 94    def __len__(self) -> int:
 95        return self._count
 96
 97    def __getitem__(self, index: int) -> D:
 98        cnt = self._count
 99        if 0 <= index < cnt:
100            return cast(D, self._list[(self._front + index) % self._capacity])
101        elif -cnt <= index < 0:
102            return cast(D, self._list[(self._front + cnt + index) % self._capacity])
103        else:
104            if cnt > 0:
105                msg1 = 'Out of bounds: '
106                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
107                msg3 = 'while getting value from a CA.'
108                raise IndexError(msg1 + msg2 + msg3)
109            else:
110                msg0 = 'Trying to get a value from an empty CA.'
111                raise IndexError(msg0)
112
113    def __setitem__(self, index: int, value: D) -> None:
114        cnt = self._count
115        if 0 <= index < cnt:
116            self._list[(self._front + index) % self._capacity] = value
117        elif -cnt <= index < 0:
118            self._list[(self._front + cnt + index) % self._capacity] = value
119        else:
120            if cnt > 0:
121                msg1 = 'Out of bounds: '
122                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
123                msg3 = 'while setting value from a CA.'
124                raise IndexError(msg1 + msg2 + msg3)
125            else:
126                msg0 = 'Trying to set a value from an empty CA.'
127                raise IndexError(msg0)
128
129    def __eq__(self, other: object) -> bool:
130        if self is other:
131            return True
132        if not isinstance(other, type(self)):
133            return False
134
135        frontL, frontR, \
136        countL, countR, \
137        capacityL, capacityR = \
138            self._front, other._front, \
139            self._count, other._count, \
140            self._capacity, other._capacity
141
142        if countL != countR:
143            return False
144
145        for nn in range(countL):
146            if self._list[(frontL+nn)%capacityL] is other._list[(frontR+nn)%capacityR]:
147                continue
148            if self._list[(frontL+nn)%capacityL] != other._list[(frontR+nn)%capacityR]:
149                return False
150        return True
151
152    def pushL(self, *ds: D) -> None:
153        """Push data from the left onto the CA."""
154        for d in ds:
155            if self._count == self._capacity:
156                self._double_storage_capacity()
157            self._front = (self._front - 1) % self._capacity
158            self._list[self._front], self._count = d, self._count + 1
159
160    def pushR(self, *ds: D) -> None:
161        """Push data from the right onto the CA."""
162        for d in ds:
163            if self._count == self._capacity:
164                self._double_storage_capacity()
165            self._rear = (self._rear + 1) % self._capacity
166            self._list[self._rear], self._count = d, self._count + 1
167
168    def popL(self) -> D|Never:
169        """Pop one value off the left side of the CA.
170
171        * raises `ValueError` when called on an empty CA
172        """
173        if self._count > 1:
174            d, self._list[self._front], self._front, self._count = \
175                self._list[self._front], None, (self._front+1) % self._capacity, self._count - 1
176        elif self._count < 1:
177            msg = 'Method popL called on an empty CA'
178            raise ValueError(msg)
179        else:
180            d, self._list[self._front], self._count, self._front, self._rear = \
181                self._list[self._front], None, 0, 0, self._capacity - 1
182        return cast(D, d)
183
184    def popR(self) -> D|Never:
185        """Pop one value off the right side of the CA.
186
187        * raises `ValueError` when called on an empty CA
188        """
189        if self._count > 0:
190            d, self._list[self._rear], self._rear, self._count = \
191                self._list[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
192        elif self._count < 1:
193            msg = 'Method popR called on an empty CA'
194            raise ValueError(msg)
195        else:
196            d, self._list[self._front], self._count, self._front, self._rear = \
197                self._list[self._front], None, 0, 0, self._capacity - 1
198        return cast(D, d)
199
200    def popLD(self, default: D) -> D:
201        """Pop one value from left, provide a mandatory default value.
202
203        * safe version of popL
204        * returns a default value in the event the `CA` is empty
205        """
206        try:
207            return self.popL()
208        except ValueError:
209            return default
210
211    def popRD(self, default: D) -> D:
212        """Pop one value from right, provide a mandatory default value.
213
214        * safe version of popR
215        * returns a default value in the event the `CA` is empty
216        """
217        try:
218            return self.popR()
219        except ValueError:
220            return default
221
222    def popLT(self, max: int=1) -> tuple[D, ...]:
223        """Pop multiple values from left side of CA.
224
225        * returns the results in a `tuple` of type `tuple[~D, ...]`
226        * returns an empty tuple if `CA` is empty
227        * pop no more that `max` values
228        * will pop less if `CA` becomes empty
229        """
230        ds: list[D] = []
231
232        while max > 0:
233            try:
234                ds.append(self.popL())
235            except ValueError:
236                break
237            else:
238                max -= 1
239
240        return tuple(ds)
241
242    def popRT(self, max: int=1) -> tuple[D, ...]:
243        """Pop multiple values from right side of CA.
244
245        * returns the results in a `tuple` of type `tuple[~D, ...]`
246        * returns an empty tuple if `CA` is empty
247        * pop no more that `max` values
248        * will pop less if `CA` becomes empty
249        """
250        ds: list[D] = []
251        while max > 0:
252            try:
253                ds.append(self.popR())
254            except ValueError:
255                break
256            else:
257                max -= 1
258
259        return tuple(ds)
260
261    def map(self, f: Callable[[D], U]) -> CA[U]:
262        """Apply function f over contents, returns new CA instance.
263
264        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
265        * returns a new instance of type `CA[~U]``
266        """
267        return CA(*map(f, self))
268
269    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
270        """Left fold CA via function and optional initial value.
271
272        * parameter `f` function of type `f[~L, ~D] -> ~L`
273          * the first argument to `f` is for the accumulated value.
274        * parameter `initial` is an optional initial value
275        * returns the reduced value of type `~L`
276          * note that `~L` and `~D` can be the same type
277          * if an initial value is not given then by necessity `~L = ~D` 
278        * raises `ValueError` when called on an empty `CA` and `initial` not given
279        """
280        if self._count == 0:
281            if initial is None:
282                msg = 'Method foldL called on an empty CA without an initial value.'
283                raise ValueError(msg)
284            else:
285                return initial
286        else:
287            if initial is None:
288                acc = cast(L, self[0])  # in this case D = L
289                for idx in range(1, self._count):
290                    acc = f(acc, self[idx])
291                return acc
292            else:
293                acc = initial
294                for d in self:
295                    acc = f(acc, d)
296                return acc
297
298    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
299        """Right fold CA via function and optional initial value.
300
301        * parameter `f` function of type `f[~D, ~R] -> ~R`
302          * the second argument to f is for the accumulated value
303        * parameter `initial` is an optional initial value
304        * returns the reduced value of type `~R`
305          * note that `~R` and `~D` can be the same type
306          * if an initial value is not given then by necessity `~R = ~D`
307        * raises `ValueError` when called on an empty `CA` and `initial` not given
308        """
309        if self._count == 0:
310            if initial is None:
311                msg = 'Method foldR called on an empty CA without an initial value.'
312                raise ValueError(msg)
313            else:
314                return initial
315        else:
316            if initial is None:
317                acc = cast(R, self[-1])  # in this case D = R
318                for idx in range(self._count-2, -1, -1):
319                    acc = f(self[idx], acc)
320                return acc
321            else:
322                acc = initial
323                for d in reversed(self):
324                    acc = f(d, acc)
325                return acc
326
327    def capacity(self) -> int:
328        """Returns current capacity of the CA."""
329        return self._capacity
330
331    def compact(self) -> None:
332        """Compact the CA."""
333        match self._count:
334            case 0:
335                self._capacity, self._front, self._rear, self._list = 2, 0, 1, [None, None]
336            case 1:
337                self._capacity, self._front, self._rear, self._list = 3, 1, 1, [None, self._list[self._front], None]
338            case _:
339                if self._front <= self._rear:
340                    self._capacity, self._front, self._rear, self._list = \
341                        self._count+2, 1, self._count, [None] + self._list[self._front:self._rear+1] + [None]
342                else:
343                    self._capacity, self._front, self._rear, self._list = \
344                        self._count+2, 1, self._count, [None] + self._list[self._front:] + self._list[:self._rear+1] + [None]
345
346    def empty(self) -> None:
347        """Empty the CA, keep current capacity."""
348        self._list, self._front, self._rear = [None]*self._capacity, 0, self._capacity-1
349
350    def fractionFilled(self) -> float:
351        """Returns fractional capacity of the CA."""
352        return self._count/self._capacity
353
354    def resize(self, new_capacity: int=2) -> None:
355        """Compact `CA` and resize to `new_capacity` if necessary."""
356        self.compact()
357        if new_capacity > self._capacity:
358            self._capacity, self._list = \
359                new_capacity, self._list + [None]*(new_capacity-self._capacity)
360            if self._count == 0:
361                self._front = 0
362                self._rear = self._capacity - 1
class CA(typing.Generic[~D]):
 29class CA(Generic[D]):
 30    """
 31    #### Indexable circular array data structure
 32
 33    * generic, stateful data structure
 34    * amortized O(1) pushing and popping from either end
 35    * O(1) random access any element
 36    * will resize itself as needed
 37    * makes defensive copies of contents for the purposes of iteration
 38    * not sliceable
 39    * in boolean context returns true if not empty, false if empty
 40    * in comparisons contents compare with identity before equality
 41      * as do Python tuples, lists, and dicts
 42    * raises `IndexError` for out-of-bounds indexing
 43    * raises `ValueError` for popping from or folding an empty CA
 44    """
 45    __slots__ = '_list', '_count', '_capacity', '_front', '_rear'
 46
 47    def __init__(self, *ds: D) -> None:
 48        self._list: list[D|None] = [None] + list(ds) + [None]
 49        self._capacity = capacity = len(self._list)
 50        self._count = capacity - 2
 51        if capacity == 2:
 52            self._front = 0
 53            self._rear = 1
 54        else:
 55            self._front = 1
 56            self._rear = capacity - 2
 57
 58    def _double_storage_capacity(self) -> None:
 59        if self._front <= self._rear:
 60            self._list += [None]*self._capacity
 61            self._capacity *= 2
 62        else:
 63            self._list = self._list[:self._front] + [None]*self._capacity + self._list[self._front:]
 64            self._front, self._capacity = self._front + self._capacity, 2*self._capacity
 65
 66    def __iter__(self) -> Iterator[D]:
 67        if self._count > 0:
 68            capacity, rear, position, current_state = \
 69                self._capacity, self._rear, self._front, self._list.copy()
 70
 71            while position != rear:
 72                yield cast(D, current_state[position])
 73                position = (position + 1) % capacity
 74            yield cast(D, current_state[position])
 75
 76    def __reversed__(self) -> Iterator[D]:
 77        if self._count > 0:
 78            capacity, front, position, current_state = \
 79                self._capacity, self._front, self._rear, self._list.copy()
 80
 81            while position != front:
 82                yield cast(D, current_state[position])
 83                position = (position - 1) % capacity
 84            yield cast(D, current_state[position])
 85
 86    def __repr__(self) -> str:
 87        return 'CA(' + ', '.join(map(repr, self)) + ')'
 88
 89    def __str__(self) -> str:
 90        return '(|' + ', '.join(map(str, self)) + '|)'
 91
 92    def __bool__(self) -> bool:
 93        return self._count > 0
 94
 95    def __len__(self) -> int:
 96        return self._count
 97
 98    def __getitem__(self, index: int) -> D:
 99        cnt = self._count
100        if 0 <= index < cnt:
101            return cast(D, self._list[(self._front + index) % self._capacity])
102        elif -cnt <= index < 0:
103            return cast(D, self._list[(self._front + cnt + index) % self._capacity])
104        else:
105            if cnt > 0:
106                msg1 = 'Out of bounds: '
107                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
108                msg3 = 'while getting value from a CA.'
109                raise IndexError(msg1 + msg2 + msg3)
110            else:
111                msg0 = 'Trying to get a value from an empty CA.'
112                raise IndexError(msg0)
113
114    def __setitem__(self, index: int, value: D) -> None:
115        cnt = self._count
116        if 0 <= index < cnt:
117            self._list[(self._front + index) % self._capacity] = value
118        elif -cnt <= index < 0:
119            self._list[(self._front + cnt + index) % self._capacity] = value
120        else:
121            if cnt > 0:
122                msg1 = 'Out of bounds: '
123                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
124                msg3 = 'while setting value from a CA.'
125                raise IndexError(msg1 + msg2 + msg3)
126            else:
127                msg0 = 'Trying to set a value from an empty CA.'
128                raise IndexError(msg0)
129
130    def __eq__(self, other: object) -> bool:
131        if self is other:
132            return True
133        if not isinstance(other, type(self)):
134            return False
135
136        frontL, frontR, \
137        countL, countR, \
138        capacityL, capacityR = \
139            self._front, other._front, \
140            self._count, other._count, \
141            self._capacity, other._capacity
142
143        if countL != countR:
144            return False
145
146        for nn in range(countL):
147            if self._list[(frontL+nn)%capacityL] is other._list[(frontR+nn)%capacityR]:
148                continue
149            if self._list[(frontL+nn)%capacityL] != other._list[(frontR+nn)%capacityR]:
150                return False
151        return True
152
153    def pushL(self, *ds: D) -> None:
154        """Push data from the left onto the CA."""
155        for d in ds:
156            if self._count == self._capacity:
157                self._double_storage_capacity()
158            self._front = (self._front - 1) % self._capacity
159            self._list[self._front], self._count = d, self._count + 1
160
161    def pushR(self, *ds: D) -> None:
162        """Push data from the right onto the CA."""
163        for d in ds:
164            if self._count == self._capacity:
165                self._double_storage_capacity()
166            self._rear = (self._rear + 1) % self._capacity
167            self._list[self._rear], self._count = d, self._count + 1
168
169    def popL(self) -> D|Never:
170        """Pop one value off the left side of the CA.
171
172        * raises `ValueError` when called on an empty CA
173        """
174        if self._count > 1:
175            d, self._list[self._front], self._front, self._count = \
176                self._list[self._front], None, (self._front+1) % self._capacity, self._count - 1
177        elif self._count < 1:
178            msg = 'Method popL called on an empty CA'
179            raise ValueError(msg)
180        else:
181            d, self._list[self._front], self._count, self._front, self._rear = \
182                self._list[self._front], None, 0, 0, self._capacity - 1
183        return cast(D, d)
184
185    def popR(self) -> D|Never:
186        """Pop one value off the right side of the CA.
187
188        * raises `ValueError` when called on an empty CA
189        """
190        if self._count > 0:
191            d, self._list[self._rear], self._rear, self._count = \
192                self._list[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
193        elif self._count < 1:
194            msg = 'Method popR called on an empty CA'
195            raise ValueError(msg)
196        else:
197            d, self._list[self._front], self._count, self._front, self._rear = \
198                self._list[self._front], None, 0, 0, self._capacity - 1
199        return cast(D, d)
200
201    def popLD(self, default: D) -> D:
202        """Pop one value from left, provide a mandatory default value.
203
204        * safe version of popL
205        * returns a default value in the event the `CA` is empty
206        """
207        try:
208            return self.popL()
209        except ValueError:
210            return default
211
212    def popRD(self, default: D) -> D:
213        """Pop one value from right, provide a mandatory default value.
214
215        * safe version of popR
216        * returns a default value in the event the `CA` is empty
217        """
218        try:
219            return self.popR()
220        except ValueError:
221            return default
222
223    def popLT(self, max: int=1) -> tuple[D, ...]:
224        """Pop multiple values from left side of CA.
225
226        * returns the results in a `tuple` of type `tuple[~D, ...]`
227        * returns an empty tuple if `CA` is empty
228        * pop no more that `max` values
229        * will pop less if `CA` becomes empty
230        """
231        ds: list[D] = []
232
233        while max > 0:
234            try:
235                ds.append(self.popL())
236            except ValueError:
237                break
238            else:
239                max -= 1
240
241        return tuple(ds)
242
243    def popRT(self, max: int=1) -> tuple[D, ...]:
244        """Pop multiple values from right side of CA.
245
246        * returns the results in a `tuple` of type `tuple[~D, ...]`
247        * returns an empty tuple if `CA` is empty
248        * pop no more that `max` values
249        * will pop less if `CA` becomes empty
250        """
251        ds: list[D] = []
252        while max > 0:
253            try:
254                ds.append(self.popR())
255            except ValueError:
256                break
257            else:
258                max -= 1
259
260        return tuple(ds)
261
262    def map(self, f: Callable[[D], U]) -> CA[U]:
263        """Apply function f over contents, returns new CA instance.
264
265        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
266        * returns a new instance of type `CA[~U]``
267        """
268        return CA(*map(f, self))
269
270    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
271        """Left fold CA via function and optional initial value.
272
273        * parameter `f` function of type `f[~L, ~D] -> ~L`
274          * the first argument to `f` is for the accumulated value.
275        * parameter `initial` is an optional initial value
276        * returns the reduced value of type `~L`
277          * note that `~L` and `~D` can be the same type
278          * if an initial value is not given then by necessity `~L = ~D` 
279        * raises `ValueError` when called on an empty `CA` and `initial` not given
280        """
281        if self._count == 0:
282            if initial is None:
283                msg = 'Method foldL called on an empty CA without an initial value.'
284                raise ValueError(msg)
285            else:
286                return initial
287        else:
288            if initial is None:
289                acc = cast(L, self[0])  # in this case D = L
290                for idx in range(1, self._count):
291                    acc = f(acc, self[idx])
292                return acc
293            else:
294                acc = initial
295                for d in self:
296                    acc = f(acc, d)
297                return acc
298
299    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
300        """Right fold CA via function and optional initial value.
301
302        * parameter `f` function of type `f[~D, ~R] -> ~R`
303          * the second argument to f is for the accumulated value
304        * parameter `initial` is an optional initial value
305        * returns the reduced value of type `~R`
306          * note that `~R` and `~D` can be the same type
307          * if an initial value is not given then by necessity `~R = ~D`
308        * raises `ValueError` when called on an empty `CA` and `initial` not given
309        """
310        if self._count == 0:
311            if initial is None:
312                msg = 'Method foldR called on an empty CA without an initial value.'
313                raise ValueError(msg)
314            else:
315                return initial
316        else:
317            if initial is None:
318                acc = cast(R, self[-1])  # in this case D = R
319                for idx in range(self._count-2, -1, -1):
320                    acc = f(self[idx], acc)
321                return acc
322            else:
323                acc = initial
324                for d in reversed(self):
325                    acc = f(d, acc)
326                return acc
327
328    def capacity(self) -> int:
329        """Returns current capacity of the CA."""
330        return self._capacity
331
332    def compact(self) -> None:
333        """Compact the CA."""
334        match self._count:
335            case 0:
336                self._capacity, self._front, self._rear, self._list = 2, 0, 1, [None, None]
337            case 1:
338                self._capacity, self._front, self._rear, self._list = 3, 1, 1, [None, self._list[self._front], None]
339            case _:
340                if self._front <= self._rear:
341                    self._capacity, self._front, self._rear, self._list = \
342                        self._count+2, 1, self._count, [None] + self._list[self._front:self._rear+1] + [None]
343                else:
344                    self._capacity, self._front, self._rear, self._list = \
345                        self._count+2, 1, self._count, [None] + self._list[self._front:] + self._list[:self._rear+1] + [None]
346
347    def empty(self) -> None:
348        """Empty the CA, keep current capacity."""
349        self._list, self._front, self._rear = [None]*self._capacity, 0, self._capacity-1
350
351    def fractionFilled(self) -> float:
352        """Returns fractional capacity of the CA."""
353        return self._count/self._capacity
354
355    def resize(self, new_capacity: int=2) -> None:
356        """Compact `CA` and resize to `new_capacity` if necessary."""
357        self.compact()
358        if new_capacity > self._capacity:
359            self._capacity, self._list = \
360                new_capacity, self._list + [None]*(new_capacity-self._capacity)
361            if self._count == 0:
362                self._front = 0
363                self._rear = self._capacity - 1

Indexable circular array data structure

  • generic, stateful data structure
  • amortized O(1) pushing and popping from either end
  • O(1) random access any element
  • will resize itself as needed
  • makes defensive copies of contents for the purposes of iteration
  • not sliceable
  • in boolean context returns true if not empty, false if empty
  • in comparisons contents compare with identity before equality
    • as do Python tuples, lists, and dicts
  • raises IndexError for out-of-bounds indexing
  • raises ValueError for popping from or folding an empty CA
CA(*ds: ~D)
47    def __init__(self, *ds: D) -> None:
48        self._list: list[D|None] = [None] + list(ds) + [None]
49        self._capacity = capacity = len(self._list)
50        self._count = capacity - 2
51        if capacity == 2:
52            self._front = 0
53            self._rear = 1
54        else:
55            self._front = 1
56            self._rear = capacity - 2
def pushL(self, *ds: ~D) -> None:
153    def pushL(self, *ds: D) -> None:
154        """Push data from the left onto the CA."""
155        for d in ds:
156            if self._count == self._capacity:
157                self._double_storage_capacity()
158            self._front = (self._front - 1) % self._capacity
159            self._list[self._front], self._count = d, self._count + 1

Push data from the left onto the CA.

def pushR(self, *ds: ~D) -> None:
161    def pushR(self, *ds: D) -> None:
162        """Push data from the right onto the CA."""
163        for d in ds:
164            if self._count == self._capacity:
165                self._double_storage_capacity()
166            self._rear = (self._rear + 1) % self._capacity
167            self._list[self._rear], self._count = d, self._count + 1

Push data from the right onto the CA.

def popL(self) -> Union[~D, Never]:
169    def popL(self) -> D|Never:
170        """Pop one value off the left side of the CA.
171
172        * raises `ValueError` when called on an empty CA
173        """
174        if self._count > 1:
175            d, self._list[self._front], self._front, self._count = \
176                self._list[self._front], None, (self._front+1) % self._capacity, self._count - 1
177        elif self._count < 1:
178            msg = 'Method popL called on an empty CA'
179            raise ValueError(msg)
180        else:
181            d, self._list[self._front], self._count, self._front, self._rear = \
182                self._list[self._front], None, 0, 0, self._capacity - 1
183        return cast(D, d)

Pop one value off the left side of the CA.

  • raises ValueError when called on an empty CA
def popR(self) -> Union[~D, Never]:
185    def popR(self) -> D|Never:
186        """Pop one value off the right side of the CA.
187
188        * raises `ValueError` when called on an empty CA
189        """
190        if self._count > 0:
191            d, self._list[self._rear], self._rear, self._count = \
192                self._list[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
193        elif self._count < 1:
194            msg = 'Method popR called on an empty CA'
195            raise ValueError(msg)
196        else:
197            d, self._list[self._front], self._count, self._front, self._rear = \
198                self._list[self._front], None, 0, 0, self._capacity - 1
199        return cast(D, d)

Pop one value off the right side of the CA.

  • raises ValueError when called on an empty CA
def popLD(self, default: ~D) -> ~D:
201    def popLD(self, default: D) -> D:
202        """Pop one value from left, provide a mandatory default value.
203
204        * safe version of popL
205        * returns a default value in the event the `CA` is empty
206        """
207        try:
208            return self.popL()
209        except ValueError:
210            return default

Pop one value from left, provide a mandatory default value.

  • safe version of popL
  • returns a default value in the event the CA is empty
def popRD(self, default: ~D) -> ~D:
212    def popRD(self, default: D) -> D:
213        """Pop one value from right, provide a mandatory default value.
214
215        * safe version of popR
216        * returns a default value in the event the `CA` is empty
217        """
218        try:
219            return self.popR()
220        except ValueError:
221            return default

Pop one value from right, provide a mandatory default value.

  • safe version of popR
  • returns a default value in the event the CA is empty
def popLT(self, max: int = 1) -> tuple[~D, ...]:
223    def popLT(self, max: int=1) -> tuple[D, ...]:
224        """Pop multiple values from left side of CA.
225
226        * returns the results in a `tuple` of type `tuple[~D, ...]`
227        * returns an empty tuple if `CA` is empty
228        * pop no more that `max` values
229        * will pop less if `CA` becomes empty
230        """
231        ds: list[D] = []
232
233        while max > 0:
234            try:
235                ds.append(self.popL())
236            except ValueError:
237                break
238            else:
239                max -= 1
240
241        return tuple(ds)

Pop multiple values from left side of CA.

  • returns the results in a tuple of type tuple[~D, ...]
  • returns an empty tuple if CA is empty
  • pop no more that max values
  • will pop less if CA becomes empty
def popRT(self, max: int = 1) -> tuple[~D, ...]:
243    def popRT(self, max: int=1) -> tuple[D, ...]:
244        """Pop multiple values from right side of CA.
245
246        * returns the results in a `tuple` of type `tuple[~D, ...]`
247        * returns an empty tuple if `CA` is empty
248        * pop no more that `max` values
249        * will pop less if `CA` becomes empty
250        """
251        ds: list[D] = []
252        while max > 0:
253            try:
254                ds.append(self.popR())
255            except ValueError:
256                break
257            else:
258                max -= 1
259
260        return tuple(ds)

Pop multiple values from right side of CA.

  • returns the results in a tuple of type tuple[~D, ...]
  • returns an empty tuple if CA is empty
  • pop no more that max values
  • will pop less if CA becomes empty
def map(self, f: Callable[[~D], ~U]) -> CA[~U]:
262    def map(self, f: Callable[[D], U]) -> CA[U]:
263        """Apply function f over contents, returns new CA instance.
264
265        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
266        * returns a new instance of type `CA[~U]``
267        """
268        return CA(*map(f, self))

Apply function f over contents, returns new CA instance.

  • parameter f function of type f[~D, ~U] -> CA[~U]
  • returns a new instance of type `CA[~U]``
def foldL(self, f: Callable[[~L, ~D], ~L], initial: Optional[~L] = None) -> ~L:
270    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
271        """Left fold CA via function and optional initial value.
272
273        * parameter `f` function of type `f[~L, ~D] -> ~L`
274          * the first argument to `f` is for the accumulated value.
275        * parameter `initial` is an optional initial value
276        * returns the reduced value of type `~L`
277          * note that `~L` and `~D` can be the same type
278          * if an initial value is not given then by necessity `~L = ~D` 
279        * raises `ValueError` when called on an empty `CA` and `initial` not given
280        """
281        if self._count == 0:
282            if initial is None:
283                msg = 'Method foldL called on an empty CA without an initial value.'
284                raise ValueError(msg)
285            else:
286                return initial
287        else:
288            if initial is None:
289                acc = cast(L, self[0])  # in this case D = L
290                for idx in range(1, self._count):
291                    acc = f(acc, self[idx])
292                return acc
293            else:
294                acc = initial
295                for d in self:
296                    acc = f(acc, d)
297                return acc

Left fold CA via function and optional initial value.

  • parameter f function of type f[~L, ~D] -> ~L
    • the first argument to f is for the accumulated value.
  • parameter initial is an optional initial value
  • returns the reduced value of type ~L
    • note that ~L and ~D can be the same type
    • if an initial value is not given then by necessity ~L = ~D
  • raises ValueError when called on an empty CA and initial not given
def foldR(self, f: Callable[[~D, ~R], ~R], initial: Optional[~R] = None) -> ~R:
299    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
300        """Right fold CA via function and optional initial value.
301
302        * parameter `f` function of type `f[~D, ~R] -> ~R`
303          * the second argument to f is for the accumulated value
304        * parameter `initial` is an optional initial value
305        * returns the reduced value of type `~R`
306          * note that `~R` and `~D` can be the same type
307          * if an initial value is not given then by necessity `~R = ~D`
308        * raises `ValueError` when called on an empty `CA` and `initial` not given
309        """
310        if self._count == 0:
311            if initial is None:
312                msg = 'Method foldR called on an empty CA without an initial value.'
313                raise ValueError(msg)
314            else:
315                return initial
316        else:
317            if initial is None:
318                acc = cast(R, self[-1])  # in this case D = R
319                for idx in range(self._count-2, -1, -1):
320                    acc = f(self[idx], acc)
321                return acc
322            else:
323                acc = initial
324                for d in reversed(self):
325                    acc = f(d, acc)
326                return acc

Right fold CA via function and optional initial value.

  • parameter f function of type f[~D, ~R] -> ~R
    • the second argument to f is for the accumulated value
  • parameter initial is an optional initial value
  • returns the reduced value of type ~R
    • note that ~R and ~D can be the same type
    • if an initial value is not given then by necessity ~R = ~D
  • raises ValueError when called on an empty CA and initial not given
def capacity(self) -> int:
328    def capacity(self) -> int:
329        """Returns current capacity of the CA."""
330        return self._capacity

Returns current capacity of the CA.

def compact(self) -> None:
332    def compact(self) -> None:
333        """Compact the CA."""
334        match self._count:
335            case 0:
336                self._capacity, self._front, self._rear, self._list = 2, 0, 1, [None, None]
337            case 1:
338                self._capacity, self._front, self._rear, self._list = 3, 1, 1, [None, self._list[self._front], None]
339            case _:
340                if self._front <= self._rear:
341                    self._capacity, self._front, self._rear, self._list = \
342                        self._count+2, 1, self._count, [None] + self._list[self._front:self._rear+1] + [None]
343                else:
344                    self._capacity, self._front, self._rear, self._list = \
345                        self._count+2, 1, self._count, [None] + self._list[self._front:] + self._list[:self._rear+1] + [None]

Compact the CA.

def empty(self) -> None:
347    def empty(self) -> None:
348        """Empty the CA, keep current capacity."""
349        self._list, self._front, self._rear = [None]*self._capacity, 0, self._capacity-1

Empty the CA, keep current capacity.

def fractionFilled(self) -> float:
351    def fractionFilled(self) -> float:
352        """Returns fractional capacity of the CA."""
353        return self._count/self._capacity

Returns fractional capacity of the CA.

def resize(self, new_capacity: int = 2) -> None:
355    def resize(self, new_capacity: int=2) -> None:
356        """Compact `CA` and resize to `new_capacity` if necessary."""
357        self.compact()
358        if new_capacity > self._capacity:
359            self._capacity, self._list = \
360                new_capacity, self._list + [None]*(new_capacity-self._capacity)
361            if self._count == 0:
362                self._front = 0
363                self._rear = self._capacity - 1

Compact CA and resize to new_capacity if necessary.