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