grscheller.circular_array.ca

Indexable circular array data structure module.

  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### Indexable circular array data structure module.
 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 compare identity before equality (like Python built-ins do)
 40      * as Python tuples, lists, and dicts do
 41    * raises `IndexError` for out-of-bounds indexing
 42    * raises `ValueError` for popping from or folding an empty CA
 43    """
 44    __slots__ = '_data', '_count', '_capacity', '_front', '_rear'
 45
 46    def __init__(self, *ds: D) -> None:
 47        self._data: list[D|None] = [None] + list(ds) + [None]
 48        self._capacity = capacity = len(self._data)
 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._data += [None]*self._capacity
 60            self._capacity *= 2
 61        else:
 62            self._data = self._data[:self._front] + [None]*self._capacity + self._data[self._front:]
 63            self._front, self._capacity = self._front + self._capacity, 2*self._capacity
 64
 65    def _compact_storage_capacity(self) -> None:
 66        """Compact the CA."""
 67        match self._count:
 68            case 0:
 69                self._capacity, self._front, self._rear, self._data = 2, 0, 1, [None, None]
 70            case 1:
 71                self._capacity, self._front, self._rear, self._data = 3, 1, 1, [None, self._data[self._front], None]
 72            case _:
 73                if self._front <= self._rear:
 74                    self._capacity, self._front, self._rear, self._data = \
 75                        self._count+2, 1, self._count, [None] + self._data[self._front:self._rear+1] + [None]
 76                else:
 77                    self._capacity, self._front, self._rear, self._data = \
 78                        self._count+2, 1, self._count, [None] + self._data[self._front:] + self._data[:self._rear+1] + [None]
 79
 80    def __iter__(self) -> Iterator[D]:
 81        if self._count > 0:
 82            capacity, rear, position, current_state = \
 83                self._capacity, self._rear, self._front, self._data.copy()
 84
 85            while position != rear:
 86                yield cast(D, current_state[position])
 87                position = (position + 1) % capacity
 88            yield cast(D, current_state[position])
 89
 90    def __reversed__(self) -> Iterator[D]:
 91        if self._count > 0:
 92            capacity, front, position, current_state = \
 93                self._capacity, self._front, self._rear, self._data.copy()
 94
 95            while position != front:
 96                yield cast(D, current_state[position])
 97                position = (position - 1) % capacity
 98            yield cast(D, current_state[position])
 99
100    def __repr__(self) -> str:
101        return 'CA(' + ', '.join(map(repr, self)) + ')'
102
103    def __str__(self) -> str:
104        return '(|' + ', '.join(map(str, self)) + '|)'
105
106    def __bool__(self) -> bool:
107        return self._count > 0
108
109    def __len__(self) -> int:
110        return self._count
111
112    def __getitem__(self, index: int) -> D:
113        cnt = self._count
114        if 0 <= index < cnt:
115            return cast(D, self._data[(self._front + index) % self._capacity])
116        elif -cnt <= index < 0:
117            return cast(D, self._data[(self._front + cnt + index) % self._capacity])
118        else:
119            if cnt > 0:
120                msg1 = 'Out of bounds: '
121                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
122                msg3 = 'while getting value from a CA.'
123                raise IndexError(msg1 + msg2 + msg3)
124            else:
125                msg0 = 'Trying to get a value from an empty CA.'
126                raise IndexError(msg0)
127
128    def __setitem__(self, index: int, value: D) -> None:
129        cnt = self._count
130        if 0 <= index < cnt:
131            self._data[(self._front + index) % self._capacity] = value
132        elif -cnt <= index < 0:
133            self._data[(self._front + cnt + index) % self._capacity] = value
134        else:
135            if cnt > 0:
136                msg1 = 'Out of bounds: '
137                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
138                msg3 = 'while setting value from a CA.'
139                raise IndexError(msg1 + msg2 + msg3)
140            else:
141                msg0 = 'Trying to set a value from an empty CA.'
142                raise IndexError(msg0)
143
144    def __eq__(self, other: object) -> bool:
145        if self is other:
146            return True
147        if not isinstance(other, type(self)):
148            return False
149
150        frontL, frontR, \
151        countL, countR, \
152        capacityL, capacityR = \
153            self._front, other._front, \
154            self._count, other._count, \
155            self._capacity, other._capacity
156
157        if countL != countR:
158            return False
159
160        for nn in range(countL):
161            if self._data[(frontL+nn)%capacityL] is other._data[(frontR+nn)%capacityR]:
162                continue
163            if self._data[(frontL+nn)%capacityL] != other._data[(frontR+nn)%capacityR]:
164                return False
165        return True
166
167    def pushL(self, *ds: D) -> None:
168        """Push data from the left onto the CA."""
169        for d in ds:
170            if self._count == self._capacity:
171                self._double_storage_capacity()
172            self._front = (self._front - 1) % self._capacity
173            self._data[self._front], self._count = d, self._count + 1
174
175    def pushR(self, *ds: D) -> None:
176        """Push data from the right onto the CA."""
177        for d in ds:
178            if self._count == self._capacity:
179                self._double_storage_capacity()
180            self._rear = (self._rear + 1) % self._capacity
181            self._data[self._rear], self._count = d, self._count + 1
182
183    def popL(self) -> D|Never:
184        """Pop one value off the left side of the CA.
185
186        * raises `ValueError` when called on an empty CA
187        """
188        if self._count > 1:
189            d, self._data[self._front], self._front, self._count = \
190                self._data[self._front], None, (self._front+1) % self._capacity, self._count - 1
191        elif self._count < 1:
192            msg = 'Method popL called on an empty CA'
193            raise ValueError(msg)
194        else:
195            d, self._data[self._front], self._count, self._front, self._rear = \
196                self._data[self._front], None, 0, 0, self._capacity - 1
197        return cast(D, d)
198
199    def popR(self) -> D|Never:
200        """Pop one value off the right side of the CA.
201
202        * raises `ValueError` when called on an empty CA
203        """
204        if self._count > 0:
205            d, self._data[self._rear], self._rear, self._count = \
206                self._data[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
207        elif self._count < 1:
208            msg = 'Method popR called on an empty CA'
209            raise ValueError(msg)
210        else:
211            d, self._data[self._front], self._count, self._front, self._rear = \
212                self._data[self._front], None, 0, 0, self._capacity - 1
213        return cast(D, d)
214
215    def popLD(self, default: D) -> D:
216        """Pop one value from left, provide a mandatory default value.
217
218        * safe version of popL
219        * returns a default value in the event the `CA` is empty
220        """
221        try:
222            return self.popL()
223        except ValueError:
224            return default
225
226    def popRD(self, default: D) -> D:
227        """Pop one value from right, provide a mandatory default value.
228
229        * safe version of popR
230        * returns a default value in the event the `CA` is empty
231        """
232        try:
233            return self.popR()
234        except ValueError:
235            return default
236
237    def popLT(self, max: int) -> tuple[D, ...]:
238        """Pop multiple values from left side of CA.
239
240        * returns the results in a tuple of type `tuple[~D, ...]`
241        * returns an empty tuple if `CA` is empty
242        * pop no more that `max` values
243        * will pop less if `CA` becomes empty
244        """
245        ds: list[D] = []
246
247        while max > 0:
248            try:
249                ds.append(self.popL())
250            except ValueError:
251                break
252            else:
253                max -= 1
254
255        return tuple(ds)
256
257    def popRT(self, max: int) -> tuple[D, ...]:
258        """Pop multiple values from right side of CA.
259
260        * returns the results in a tuple of type `tuple[~D, ...]`
261        * returns an empty tuple if `CA` is empty
262        * pop no more that `max` values
263        * will pop less if `CA` becomes empty
264        """
265        ds: list[D] = []
266        while max > 0:
267            try:
268                ds.append(self.popR())
269            except ValueError:
270                break
271            else:
272                max -= 1
273
274        return tuple(ds)
275
276    def map(self, f: Callable[[D], U]) -> CA[U]:
277        """Apply function f over contents, returns new CA instance.
278
279        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
280        * returns a new instance of type `CA[~U]``
281        """
282        return CA(*map(f, self))
283
284    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
285        """Left fold CA via function and optional initial value.
286
287        * parameter `f` function of type `f[~L, ~D] -> ~L`
288          * the first argument to `f` is for the accumulated value.
289        * parameter `initial` is an optional initial value
290        * returns the reduced value of type `~L`
291          * note that `~L` and `~D` can be the same type
292          * if an initial value is not given then by necessity `~L = ~D` 
293        * raises `ValueError` when called on an empty `CA` and `initial` not given
294        """
295        if self._count == 0:
296            if initial is None:
297                msg = 'Method foldL called on an empty CA without an initial value.'
298                raise ValueError(msg)
299            else:
300                return initial
301        else:
302            if initial is None:
303                acc = cast(L, self[0])  # in this case D = L
304                for idx in range(1, self._count):
305                    acc = f(acc, self[idx])
306                return acc
307            else:
308                acc = initial
309                for d in self:
310                    acc = f(acc, d)
311                return acc
312
313    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
314        """Right fold CA via function and optional initial value.
315
316        * parameter `f` function of type `f[~D, ~R] -> ~R`
317          * the second argument to f is for the accumulated value
318        * parameter `initial` is an optional initial value
319        * returns the reduced value of type `~R`
320          * note that `~R` and `~D` can be the same type
321          * if an initial value is not given then by necessity `~R = ~D`
322        * raises `ValueError` when called on an empty `CA` and `initial` not given
323        """
324        if self._count == 0:
325            if initial is None:
326                msg = 'Method foldR called on an empty CA without an initial value.'
327                raise ValueError(msg)
328            else:
329                return initial
330        else:
331            if initial is None:
332                acc = cast(R, self[-1])  # in this case D = R
333                for idx in range(self._count-2, -1, -1):
334                    acc = f(self[idx], acc)
335                return acc
336            else:
337                acc = initial
338                for d in reversed(self):
339                    acc = f(d, acc)
340                return acc
341
342    def capacity(self) -> int:
343        """Returns current capacity of the CA."""
344        return self._capacity
345
346    def empty(self) -> None:
347        """Empty the CA, keep current capacity."""
348        self._data, 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, min_capacity: int=2) -> None:
355        """Compact `CA` and resize to `min_capacity` if necessary.
356
357        * to just compact the `CA`, do not provide a min_capacity
358        """
359        self._compact_storage_capacity()
360        if min_capacity > self._capacity:
361            self._capacity, self._data = \
362                min_capacity, self._data + [None]*(min_capacity-self._capacity)
363            if self._count == 0:
364                self._front, self._rear = 0, 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 compare identity before equality (like Python built-ins do)
 41      * as Python tuples, lists, and dicts do
 42    * raises `IndexError` for out-of-bounds indexing
 43    * raises `ValueError` for popping from or folding an empty CA
 44    """
 45    __slots__ = '_data', '_count', '_capacity', '_front', '_rear'
 46
 47    def __init__(self, *ds: D) -> None:
 48        self._data: list[D|None] = [None] + list(ds) + [None]
 49        self._capacity = capacity = len(self._data)
 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._data += [None]*self._capacity
 61            self._capacity *= 2
 62        else:
 63            self._data = self._data[:self._front] + [None]*self._capacity + self._data[self._front:]
 64            self._front, self._capacity = self._front + self._capacity, 2*self._capacity
 65
 66    def _compact_storage_capacity(self) -> None:
 67        """Compact the CA."""
 68        match self._count:
 69            case 0:
 70                self._capacity, self._front, self._rear, self._data = 2, 0, 1, [None, None]
 71            case 1:
 72                self._capacity, self._front, self._rear, self._data = 3, 1, 1, [None, self._data[self._front], None]
 73            case _:
 74                if self._front <= self._rear:
 75                    self._capacity, self._front, self._rear, self._data = \
 76                        self._count+2, 1, self._count, [None] + self._data[self._front:self._rear+1] + [None]
 77                else:
 78                    self._capacity, self._front, self._rear, self._data = \
 79                        self._count+2, 1, self._count, [None] + self._data[self._front:] + self._data[:self._rear+1] + [None]
 80
 81    def __iter__(self) -> Iterator[D]:
 82        if self._count > 0:
 83            capacity, rear, position, current_state = \
 84                self._capacity, self._rear, self._front, self._data.copy()
 85
 86            while position != rear:
 87                yield cast(D, current_state[position])
 88                position = (position + 1) % capacity
 89            yield cast(D, current_state[position])
 90
 91    def __reversed__(self) -> Iterator[D]:
 92        if self._count > 0:
 93            capacity, front, position, current_state = \
 94                self._capacity, self._front, self._rear, self._data.copy()
 95
 96            while position != front:
 97                yield cast(D, current_state[position])
 98                position = (position - 1) % capacity
 99            yield cast(D, current_state[position])
100
101    def __repr__(self) -> str:
102        return 'CA(' + ', '.join(map(repr, self)) + ')'
103
104    def __str__(self) -> str:
105        return '(|' + ', '.join(map(str, self)) + '|)'
106
107    def __bool__(self) -> bool:
108        return self._count > 0
109
110    def __len__(self) -> int:
111        return self._count
112
113    def __getitem__(self, index: int) -> D:
114        cnt = self._count
115        if 0 <= index < cnt:
116            return cast(D, self._data[(self._front + index) % self._capacity])
117        elif -cnt <= index < 0:
118            return cast(D, self._data[(self._front + cnt + index) % self._capacity])
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 getting value from a CA.'
124                raise IndexError(msg1 + msg2 + msg3)
125            else:
126                msg0 = 'Trying to get a value from an empty CA.'
127                raise IndexError(msg0)
128
129    def __setitem__(self, index: int, value: D) -> None:
130        cnt = self._count
131        if 0 <= index < cnt:
132            self._data[(self._front + index) % self._capacity] = value
133        elif -cnt <= index < 0:
134            self._data[(self._front + cnt + index) % self._capacity] = value
135        else:
136            if cnt > 0:
137                msg1 = 'Out of bounds: '
138                msg2 = f'index = {index} not between {-cnt} and {cnt-1} '
139                msg3 = 'while setting value from a CA.'
140                raise IndexError(msg1 + msg2 + msg3)
141            else:
142                msg0 = 'Trying to set a value from an empty CA.'
143                raise IndexError(msg0)
144
145    def __eq__(self, other: object) -> bool:
146        if self is other:
147            return True
148        if not isinstance(other, type(self)):
149            return False
150
151        frontL, frontR, \
152        countL, countR, \
153        capacityL, capacityR = \
154            self._front, other._front, \
155            self._count, other._count, \
156            self._capacity, other._capacity
157
158        if countL != countR:
159            return False
160
161        for nn in range(countL):
162            if self._data[(frontL+nn)%capacityL] is other._data[(frontR+nn)%capacityR]:
163                continue
164            if self._data[(frontL+nn)%capacityL] != other._data[(frontR+nn)%capacityR]:
165                return False
166        return True
167
168    def pushL(self, *ds: D) -> None:
169        """Push data from the left onto the CA."""
170        for d in ds:
171            if self._count == self._capacity:
172                self._double_storage_capacity()
173            self._front = (self._front - 1) % self._capacity
174            self._data[self._front], self._count = d, self._count + 1
175
176    def pushR(self, *ds: D) -> None:
177        """Push data from the right onto the CA."""
178        for d in ds:
179            if self._count == self._capacity:
180                self._double_storage_capacity()
181            self._rear = (self._rear + 1) % self._capacity
182            self._data[self._rear], self._count = d, self._count + 1
183
184    def popL(self) -> D|Never:
185        """Pop one value off the left side of the CA.
186
187        * raises `ValueError` when called on an empty CA
188        """
189        if self._count > 1:
190            d, self._data[self._front], self._front, self._count = \
191                self._data[self._front], None, (self._front+1) % self._capacity, self._count - 1
192        elif self._count < 1:
193            msg = 'Method popL called on an empty CA'
194            raise ValueError(msg)
195        else:
196            d, self._data[self._front], self._count, self._front, self._rear = \
197                self._data[self._front], None, 0, 0, self._capacity - 1
198        return cast(D, d)
199
200    def popR(self) -> D|Never:
201        """Pop one value off the right side of the CA.
202
203        * raises `ValueError` when called on an empty CA
204        """
205        if self._count > 0:
206            d, self._data[self._rear], self._rear, self._count = \
207                self._data[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
208        elif self._count < 1:
209            msg = 'Method popR called on an empty CA'
210            raise ValueError(msg)
211        else:
212            d, self._data[self._front], self._count, self._front, self._rear = \
213                self._data[self._front], None, 0, 0, self._capacity - 1
214        return cast(D, d)
215
216    def popLD(self, default: D) -> D:
217        """Pop one value from left, provide a mandatory default value.
218
219        * safe version of popL
220        * returns a default value in the event the `CA` is empty
221        """
222        try:
223            return self.popL()
224        except ValueError:
225            return default
226
227    def popRD(self, default: D) -> D:
228        """Pop one value from right, provide a mandatory default value.
229
230        * safe version of popR
231        * returns a default value in the event the `CA` is empty
232        """
233        try:
234            return self.popR()
235        except ValueError:
236            return default
237
238    def popLT(self, max: int) -> tuple[D, ...]:
239        """Pop multiple values from left side of CA.
240
241        * returns the results in a tuple of type `tuple[~D, ...]`
242        * returns an empty tuple if `CA` is empty
243        * pop no more that `max` values
244        * will pop less if `CA` becomes empty
245        """
246        ds: list[D] = []
247
248        while max > 0:
249            try:
250                ds.append(self.popL())
251            except ValueError:
252                break
253            else:
254                max -= 1
255
256        return tuple(ds)
257
258    def popRT(self, max: int) -> tuple[D, ...]:
259        """Pop multiple values from right side of CA.
260
261        * returns the results in a tuple of type `tuple[~D, ...]`
262        * returns an empty tuple if `CA` is empty
263        * pop no more that `max` values
264        * will pop less if `CA` becomes empty
265        """
266        ds: list[D] = []
267        while max > 0:
268            try:
269                ds.append(self.popR())
270            except ValueError:
271                break
272            else:
273                max -= 1
274
275        return tuple(ds)
276
277    def map(self, f: Callable[[D], U]) -> CA[U]:
278        """Apply function f over contents, returns new CA instance.
279
280        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
281        * returns a new instance of type `CA[~U]``
282        """
283        return CA(*map(f, self))
284
285    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
286        """Left fold CA via function and optional initial value.
287
288        * parameter `f` function of type `f[~L, ~D] -> ~L`
289          * the first argument to `f` is for the accumulated value.
290        * parameter `initial` is an optional initial value
291        * returns the reduced value of type `~L`
292          * note that `~L` and `~D` can be the same type
293          * if an initial value is not given then by necessity `~L = ~D` 
294        * raises `ValueError` when called on an empty `CA` and `initial` not given
295        """
296        if self._count == 0:
297            if initial is None:
298                msg = 'Method foldL called on an empty CA without an initial value.'
299                raise ValueError(msg)
300            else:
301                return initial
302        else:
303            if initial is None:
304                acc = cast(L, self[0])  # in this case D = L
305                for idx in range(1, self._count):
306                    acc = f(acc, self[idx])
307                return acc
308            else:
309                acc = initial
310                for d in self:
311                    acc = f(acc, d)
312                return acc
313
314    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
315        """Right fold CA via function and optional initial value.
316
317        * parameter `f` function of type `f[~D, ~R] -> ~R`
318          * the second argument to f is for the accumulated value
319        * parameter `initial` is an optional initial value
320        * returns the reduced value of type `~R`
321          * note that `~R` and `~D` can be the same type
322          * if an initial value is not given then by necessity `~R = ~D`
323        * raises `ValueError` when called on an empty `CA` and `initial` not given
324        """
325        if self._count == 0:
326            if initial is None:
327                msg = 'Method foldR called on an empty CA without an initial value.'
328                raise ValueError(msg)
329            else:
330                return initial
331        else:
332            if initial is None:
333                acc = cast(R, self[-1])  # in this case D = R
334                for idx in range(self._count-2, -1, -1):
335                    acc = f(self[idx], acc)
336                return acc
337            else:
338                acc = initial
339                for d in reversed(self):
340                    acc = f(d, acc)
341                return acc
342
343    def capacity(self) -> int:
344        """Returns current capacity of the CA."""
345        return self._capacity
346
347    def empty(self) -> None:
348        """Empty the CA, keep current capacity."""
349        self._data, 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, min_capacity: int=2) -> None:
356        """Compact `CA` and resize to `min_capacity` if necessary.
357
358        * to just compact the `CA`, do not provide a min_capacity
359        """
360        self._compact_storage_capacity()
361        if min_capacity > self._capacity:
362            self._capacity, self._data = \
363                min_capacity, self._data + [None]*(min_capacity-self._capacity)
364            if self._count == 0:
365                self._front, self._rear = 0, 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 compare identity before equality (like Python built-ins do)
    • as Python tuples, lists, and dicts do
  • 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._data: list[D|None] = [None] + list(ds) + [None]
49        self._capacity = capacity = len(self._data)
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:
168    def pushL(self, *ds: D) -> None:
169        """Push data from the left onto the CA."""
170        for d in ds:
171            if self._count == self._capacity:
172                self._double_storage_capacity()
173            self._front = (self._front - 1) % self._capacity
174            self._data[self._front], self._count = d, self._count + 1

Push data from the left onto the CA.

def pushR(self, *ds: ~D) -> None:
176    def pushR(self, *ds: D) -> None:
177        """Push data from the right onto the CA."""
178        for d in ds:
179            if self._count == self._capacity:
180                self._double_storage_capacity()
181            self._rear = (self._rear + 1) % self._capacity
182            self._data[self._rear], self._count = d, self._count + 1

Push data from the right onto the CA.

def popL(self) -> Union[~D, Never]:
184    def popL(self) -> D|Never:
185        """Pop one value off the left side of the CA.
186
187        * raises `ValueError` when called on an empty CA
188        """
189        if self._count > 1:
190            d, self._data[self._front], self._front, self._count = \
191                self._data[self._front], None, (self._front+1) % self._capacity, self._count - 1
192        elif self._count < 1:
193            msg = 'Method popL called on an empty CA'
194            raise ValueError(msg)
195        else:
196            d, self._data[self._front], self._count, self._front, self._rear = \
197                self._data[self._front], None, 0, 0, self._capacity - 1
198        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]:
200    def popR(self) -> D|Never:
201        """Pop one value off the right side of the CA.
202
203        * raises `ValueError` when called on an empty CA
204        """
205        if self._count > 0:
206            d, self._data[self._rear], self._rear, self._count = \
207                self._data[self._rear], None, (self._rear - 1) % self._capacity, self._count - 1
208        elif self._count < 1:
209            msg = 'Method popR called on an empty CA'
210            raise ValueError(msg)
211        else:
212            d, self._data[self._front], self._count, self._front, self._rear = \
213                self._data[self._front], None, 0, 0, self._capacity - 1
214        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:
216    def popLD(self, default: D) -> D:
217        """Pop one value from left, provide a mandatory default value.
218
219        * safe version of popL
220        * returns a default value in the event the `CA` is empty
221        """
222        try:
223            return self.popL()
224        except ValueError:
225            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:
227    def popRD(self, default: D) -> D:
228        """Pop one value from right, provide a mandatory default value.
229
230        * safe version of popR
231        * returns a default value in the event the `CA` is empty
232        """
233        try:
234            return self.popR()
235        except ValueError:
236            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) -> tuple[~D, ...]:
238    def popLT(self, max: int) -> tuple[D, ...]:
239        """Pop multiple values from left side of CA.
240
241        * returns the results in a tuple of type `tuple[~D, ...]`
242        * returns an empty tuple if `CA` is empty
243        * pop no more that `max` values
244        * will pop less if `CA` becomes empty
245        """
246        ds: list[D] = []
247
248        while max > 0:
249            try:
250                ds.append(self.popL())
251            except ValueError:
252                break
253            else:
254                max -= 1
255
256        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) -> tuple[~D, ...]:
258    def popRT(self, max: int) -> tuple[D, ...]:
259        """Pop multiple values from right side of CA.
260
261        * returns the results in a tuple of type `tuple[~D, ...]`
262        * returns an empty tuple if `CA` is empty
263        * pop no more that `max` values
264        * will pop less if `CA` becomes empty
265        """
266        ds: list[D] = []
267        while max > 0:
268            try:
269                ds.append(self.popR())
270            except ValueError:
271                break
272            else:
273                max -= 1
274
275        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]:
277    def map(self, f: Callable[[D], U]) -> CA[U]:
278        """Apply function f over contents, returns new CA instance.
279
280        * parameter `f` function of type `f[~D, ~U] -> CA[~U]`
281        * returns a new instance of type `CA[~U]``
282        """
283        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:
285    def foldL(self, f: Callable[[L, D], L], initial: Optional[L]=None) -> L:
286        """Left fold CA via function and optional initial value.
287
288        * parameter `f` function of type `f[~L, ~D] -> ~L`
289          * the first argument to `f` is for the accumulated value.
290        * parameter `initial` is an optional initial value
291        * returns the reduced value of type `~L`
292          * note that `~L` and `~D` can be the same type
293          * if an initial value is not given then by necessity `~L = ~D` 
294        * raises `ValueError` when called on an empty `CA` and `initial` not given
295        """
296        if self._count == 0:
297            if initial is None:
298                msg = 'Method foldL called on an empty CA without an initial value.'
299                raise ValueError(msg)
300            else:
301                return initial
302        else:
303            if initial is None:
304                acc = cast(L, self[0])  # in this case D = L
305                for idx in range(1, self._count):
306                    acc = f(acc, self[idx])
307                return acc
308            else:
309                acc = initial
310                for d in self:
311                    acc = f(acc, d)
312                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:
314    def foldR(self, f: Callable[[D, R], R], initial: Optional[R]=None) -> R:
315        """Right fold CA via function and optional initial value.
316
317        * parameter `f` function of type `f[~D, ~R] -> ~R`
318          * the second argument to f is for the accumulated value
319        * parameter `initial` is an optional initial value
320        * returns the reduced value of type `~R`
321          * note that `~R` and `~D` can be the same type
322          * if an initial value is not given then by necessity `~R = ~D`
323        * raises `ValueError` when called on an empty `CA` and `initial` not given
324        """
325        if self._count == 0:
326            if initial is None:
327                msg = 'Method foldR called on an empty CA without an initial value.'
328                raise ValueError(msg)
329            else:
330                return initial
331        else:
332            if initial is None:
333                acc = cast(R, self[-1])  # in this case D = R
334                for idx in range(self._count-2, -1, -1):
335                    acc = f(self[idx], acc)
336                return acc
337            else:
338                acc = initial
339                for d in reversed(self):
340                    acc = f(d, acc)
341                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:
343    def capacity(self) -> int:
344        """Returns current capacity of the CA."""
345        return self._capacity

Returns current capacity of the CA.

def empty(self) -> None:
347    def empty(self) -> None:
348        """Empty the CA, keep current capacity."""
349        self._data, 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, min_capacity: int = 2) -> None:
355    def resize(self, min_capacity: int=2) -> None:
356        """Compact `CA` and resize to `min_capacity` if necessary.
357
358        * to just compact the `CA`, do not provide a min_capacity
359        """
360        self._compact_storage_capacity()
361        if min_capacity > self._capacity:
362            self._capacity, self._data = \
363                min_capacity, self._data + [None]*(min_capacity-self._capacity)
364            if self._count == 0:
365                self._front, self._rear = 0, self._capacity - 1

Compact CA and resize to min_capacity if necessary.

  • to just compact the CA, do not provide a min_capacity