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 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 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
- as Python tuples, lists, and dicts do
- raises
IndexError
for out-of-bounds indexing - raises
ValueError
for popping from or folding an empty CA
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)
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)
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 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:
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 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:
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 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:
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