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
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)
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)
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 typef[~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 typef[~L, ~D] -> ~L
- the first argument to
f
is for the accumulated value.
- the first argument to
- 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
- note that
- raises
ValueError
when called on an emptyCA
andinitial
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 typef[~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
- note that
- raises
ValueError
when called on an emptyCA
andinitial
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.