grscheller.datastructures.split_ends
Stack type Data Structures
SplitEnd
LIFO stacks which can safely share immutable data between themselves.
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### Stack type Data Structures 17 18##### SplitEnd 19 20LIFO stacks which can safely share immutable data between themselves. 21 22--- 23 24""" 25 26from __future__ import annotations 27 28from typing import Callable, cast, Generic, Iterator, Optional, overload, TypeVar 29from grscheller.fp.iterables import FM, concat, exhaust, merge 30from grscheller.fp.nada import Nada, nada 31from .nodes import SL_Node as Node 32 33__all__ = ['SplitEnd'] 34 35D = TypeVar('D') 36S = TypeVar('S') 37T = TypeVar('T') 38 39class SplitEnd(Generic[D, S]): 40 """ 41 #### SplitEnd 42 43 Class implementing a stack type data structures called a *split end*. 44 45 * each *split end* is a very simple stateful LIFO stack 46 * contains a count of nodes & reference to first node of a linked list 47 * different *split ends* can safely share the same *tail* 48 * each *split end* sees itself as a singularly linked list 49 * bush-like datastructures can be formed using multiple *split ends* 50 * len() returns the number of elements on the stack 51 * in a boolean context, return `True` if SplitEnd is not empty 52 53 """ 54 __slots__ = '_head', '_count', '_sentinel' 55 56 @overload 57 def __init__(self, *ds: D, s: S) -> None: 58 ... 59 @overload 60 def __init__(self, *ds: D, s: Nada) -> None: 61 ... 62 @overload 63 def __init__(self, *ds: D) -> None: 64 ... 65 def __init__(self, *ds: D, s: S|Nada=nada) -> None: 66 self._head: Optional[Node[D]] = None 67 self._count: int = 0 68 self._sentinel = s 69 for d in ds: 70 node: Node[D] = Node(d, self._head) 71 self._head = node 72 self._count += 1 73 74 def __iter__(self) -> Iterator[D]: 75 node = self._head 76 while node: 77 yield node._data 78 node = node._next 79 80 def reverse(self) -> SplitEnd[D, S]: 81 """ 82 ##### Return a Reversed SplitEnd 83 84 Return shallow reversed copy of a SplitEnd. 85 86 * Returns a new Stack object with shallow copied new data 87 * creates all new nodes 88 * O(1) space & time complexity 89 90 """ 91 return SplitEnd(*self, s=self._sentinel) 92 93 def __reversed__(self) -> Iterator[D]: 94 return iter(self.reverse()) 95 96 def __repr__(self) -> str: 97 if self._sentinel is nada: 98 return 'SplitEnd(' + ', '.join(map(repr, reversed(self))) + ')' 99 elif self: 100 return ('SplitEnd(' 101 + ', '.join(map(repr, reversed(self))) 102 + ', s=' + repr(self._sentinel) + ')') 103 else: 104 return ('SplitEnd(' 105 + 's=' + repr(self._sentinel) + ')') 106 107 108 def __str__(self) -> str: 109 """Display the data in the Stack, left to right.""" 110 if self._sentinel is nada: 111 return ('>< ' 112 + ' -> '.join(map(str, self)) 113 + ' ||') 114 else: 115 return ('>< ' 116 + ' -> '.join(map(str, self)) 117 + ' |' + repr(self._sentinel) + '|') 118 119 def __bool__(self) -> bool: 120 return self._count > 0 121 122 def __len__(self) -> int: 123 return self._count 124 125 def __eq__(self, other: object) -> bool: 126 if not isinstance(other, type(self)): 127 return False 128 129 if self._count != other._count: 130 return False 131 if self._sentinel is not other._sentinel: 132 if self._sentinel != other._sentinel: 133 return False 134 135 left = self._head 136 right = other._head 137 nn = self._count 138 while nn > 0: 139 if left is right: 140 return True 141 if left is None or right is None: 142 return True 143 if left._data != right._data: 144 return False 145 left = left._next 146 right = right._next 147 nn -= 1 148 return True 149 150 def copy(self) -> SplitEnd[D, S]: 151 """ 152 ##### Shallow Copy 153 154 Return a swallow copy of the SplitEnd in O(1) space & time complexity. 155 156 """ 157 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 158 stack._head, stack._count = self._head, self._count 159 return stack 160 161 def push(self, *ds: D) -> None: 162 """ 163 ##### Push Data 164 165 Push data onto top of the SplitEnd. 166 167 * ignore "non-existent" Nothing() values pushed on the SplitEnd 168 169 """ 170 for d in ds: 171 if d is not nada: 172 node = Node(d, self._head) 173 self._head, self._count = node, self._count+1 174 175 @overload 176 def pop(self, default: D) -> D|S: 177 ... 178 @overload 179 def pop(self) -> D|S: 180 ... 181 def pop(self, default: D|Nada=nada) -> D|S|Nada: 182 """ 183 ##### Pop Data 184 185 Pop data off of the top of the SplitEnd. 186 187 * if empty, return a default value 188 * if empty and a default value not given, return the sentinel value 189 190 """ 191 if self._head is None: 192 if default is nada: 193 return self._sentinel 194 else: 195 return default 196 else: 197 data = self._head._data 198 self._head, self._count = self._head._next, self._count-1 199 return data 200 201 @overload 202 def peak(self, default: D) -> D: 203 ... 204 @overload 205 def peak(self) -> D|S: 206 ... 207 def peak(self, default: D|Nada=nada) -> D|S|Nada: 208 """ 209 ##### Peak at top of SplitEnd 210 211 Returns the data at the top of the SplitEnd. 212 213 * does not consume the data 214 * if empty, data does not exist, so in that case return default 215 * if empty and no default given, return nothing: Nothing 216 217 """ 218 if self._head is None: 219 return default 220 return self._head._data 221 222 @overload 223 def head(self, default: D|S) -> D|S: 224 ... 225 @overload 226 def head(self) -> D|S: 227 ... 228 def head(self, default: D|S|Nada=nada) -> D|S|Nada: 229 """ 230 ##### Head of SplitEnd 231 232 Returns the data at the top of the SplitEnd. 233 234 * does not consume the data 235 * for an empty SplitEnd, head does not exist, so return default 236 * otherwise return the sentinel value 237 * the sentinel value cannot be overridden by nada 238 * of course self._sentinel can always be set to nada 239 240 """ 241 if self._head is None: 242 if default is nada: 243 return self._sentinel 244 else: 245 return default 246 return self._head._data 247 248 @overload 249 def tail(self, default: S) -> SplitEnd[D, S]|S: 250 ... 251 @overload 252 def tail(self) -> SplitEnd[D, S]|S: 253 ... 254 def tail(self, default: S|Nada=nada) -> SplitEnd[D, S]|S|Nada: 255 """ 256 ##### Tail of SplitEnd 257 258 Returns the tail of the SplitEnd if it exists, otherwise returns the 259 sentinel value, or a default value of the same type as the sentinel 260 value. 261 262 * optional default needs to be of the same type as the sentinel value 263 * example: 264 * sentinel: tuple(int) = (0,) 265 * default: tuple(int) = (42,) 266 * decided not to let default: SplitEnd[D, S] as a return option 267 * made end code more confusing to reason about 268 * not worth cognitive overload when used in client code 269 * tended to hide the occurrence of an unusual event occurring 270 * the sentinel value cannot be overridden by nada 271 * of course self._sentinel can always be set to nada 272 273 """ 274 if self._head: 275 se: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 276 se._head = self._head._next 277 se._count = self._count - 1 278 return se 279 else: 280 return default 281 282 @overload 283 def cons(self, d: D) -> SplitEnd[D, S]: 284 ... 285 @overload 286 def cons(self, d: Nada) -> Nada: 287 ... 288 def cons(self, d: D|Nada) -> SplitEnd[D, S]|Nada: 289 """ 290 ##### Cons SplitEnd with a Head 291 292 Return a new SplitEnd with data as head and self as tail. 293 294 Constructing a SplitEnd using a non-existent value as head results in 295 a non-existent SplitEnd. In that case, return sentinel: _S. 296 297 """ 298 if d is nada: 299 return nada 300 else: 301 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 302 stack._head = Node(cast(D, d), self._head) 303 stack._count = self._count + 1 304 return stack 305 306 def fold(self, f:Callable[[D, D], D]) -> Optional[D]: 307 """ 308 ##### Reduce with `f` 309 310 * returns a value of the of type _T if self is not empty 311 * returns None if self is empty 312 * folds in natural LIFO Order 313 * TODO: consolidate fold & fold1 314 315 """ 316 node: Optional[Node[D]] = self._head 317 if not node: 318 return None 319 acc: D = node._data 320 while node: 321 if (node := node._next) is None: 322 break 323 acc = f(acc, node._data) 324 return acc 325 326 def fold1(self, f:Callable[[T, D], T], s: T) -> T: 327 """Reduce with f. 328 329 * returns a value of type ~T 330 * type ~T can be same type as ~D 331 * folds in natural LIFO Order 332 * TODO: consolidate fold & fold1 333 334 """ 335 node: Optional[Node[D]] = self._head 336 if not node: 337 return s 338 acc: T = s 339 while node: 340 acc = f(acc, node._data) 341 node = node._next 342 return acc 343 344 def flatMap(self, f: Callable[[D], SplitEnd[T, S]], type: FM=FM.CONCAT) -> SplitEnd[T, S]: 345 """ 346 ##### Bind function to SplitEnd 347 348 Bind function `f` to the SplitEnd. 349 350 * type = CONCAT: sequentially concatenate iterables one after the other 351 * type = MERGE: merge iterables together until one is exhausted 352 * type = Exhaust: merge iterables together until all are exhausted 353 354 """ 355 match type: 356 case FM.CONCAT: 357 return SplitEnd(*concat(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 358 case FM.MERGE: 359 return SplitEnd(*merge(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 360 case FM.EXHAUST: 361 return SplitEnd(*exhaust(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 362 case '*': 363 raise ValueError('Unknown FM type') 364 365 def map(self, f: Callable[[D], T]) -> SplitEnd[T, S]: 366 """ 367 ##### Map `f` over the SplitEnd 368 369 Maps a function (or callable object) over the values on the SplitEnd Stack. 370 371 * TODO: Redo in "natural" order? 372 * Returns a new Stack object with shallow copied new data 373 * O(n) complexity 374 375 """ 376 return self.flatMap(lambda a: SplitEnd(f(a)))
40class SplitEnd(Generic[D, S]): 41 """ 42 #### SplitEnd 43 44 Class implementing a stack type data structures called a *split end*. 45 46 * each *split end* is a very simple stateful LIFO stack 47 * contains a count of nodes & reference to first node of a linked list 48 * different *split ends* can safely share the same *tail* 49 * each *split end* sees itself as a singularly linked list 50 * bush-like datastructures can be formed using multiple *split ends* 51 * len() returns the number of elements on the stack 52 * in a boolean context, return `True` if SplitEnd is not empty 53 54 """ 55 __slots__ = '_head', '_count', '_sentinel' 56 57 @overload 58 def __init__(self, *ds: D, s: S) -> None: 59 ... 60 @overload 61 def __init__(self, *ds: D, s: Nada) -> None: 62 ... 63 @overload 64 def __init__(self, *ds: D) -> None: 65 ... 66 def __init__(self, *ds: D, s: S|Nada=nada) -> None: 67 self._head: Optional[Node[D]] = None 68 self._count: int = 0 69 self._sentinel = s 70 for d in ds: 71 node: Node[D] = Node(d, self._head) 72 self._head = node 73 self._count += 1 74 75 def __iter__(self) -> Iterator[D]: 76 node = self._head 77 while node: 78 yield node._data 79 node = node._next 80 81 def reverse(self) -> SplitEnd[D, S]: 82 """ 83 ##### Return a Reversed SplitEnd 84 85 Return shallow reversed copy of a SplitEnd. 86 87 * Returns a new Stack object with shallow copied new data 88 * creates all new nodes 89 * O(1) space & time complexity 90 91 """ 92 return SplitEnd(*self, s=self._sentinel) 93 94 def __reversed__(self) -> Iterator[D]: 95 return iter(self.reverse()) 96 97 def __repr__(self) -> str: 98 if self._sentinel is nada: 99 return 'SplitEnd(' + ', '.join(map(repr, reversed(self))) + ')' 100 elif self: 101 return ('SplitEnd(' 102 + ', '.join(map(repr, reversed(self))) 103 + ', s=' + repr(self._sentinel) + ')') 104 else: 105 return ('SplitEnd(' 106 + 's=' + repr(self._sentinel) + ')') 107 108 109 def __str__(self) -> str: 110 """Display the data in the Stack, left to right.""" 111 if self._sentinel is nada: 112 return ('>< ' 113 + ' -> '.join(map(str, self)) 114 + ' ||') 115 else: 116 return ('>< ' 117 + ' -> '.join(map(str, self)) 118 + ' |' + repr(self._sentinel) + '|') 119 120 def __bool__(self) -> bool: 121 return self._count > 0 122 123 def __len__(self) -> int: 124 return self._count 125 126 def __eq__(self, other: object) -> bool: 127 if not isinstance(other, type(self)): 128 return False 129 130 if self._count != other._count: 131 return False 132 if self._sentinel is not other._sentinel: 133 if self._sentinel != other._sentinel: 134 return False 135 136 left = self._head 137 right = other._head 138 nn = self._count 139 while nn > 0: 140 if left is right: 141 return True 142 if left is None or right is None: 143 return True 144 if left._data != right._data: 145 return False 146 left = left._next 147 right = right._next 148 nn -= 1 149 return True 150 151 def copy(self) -> SplitEnd[D, S]: 152 """ 153 ##### Shallow Copy 154 155 Return a swallow copy of the SplitEnd in O(1) space & time complexity. 156 157 """ 158 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 159 stack._head, stack._count = self._head, self._count 160 return stack 161 162 def push(self, *ds: D) -> None: 163 """ 164 ##### Push Data 165 166 Push data onto top of the SplitEnd. 167 168 * ignore "non-existent" Nothing() values pushed on the SplitEnd 169 170 """ 171 for d in ds: 172 if d is not nada: 173 node = Node(d, self._head) 174 self._head, self._count = node, self._count+1 175 176 @overload 177 def pop(self, default: D) -> D|S: 178 ... 179 @overload 180 def pop(self) -> D|S: 181 ... 182 def pop(self, default: D|Nada=nada) -> D|S|Nada: 183 """ 184 ##### Pop Data 185 186 Pop data off of the top of the SplitEnd. 187 188 * if empty, return a default value 189 * if empty and a default value not given, return the sentinel value 190 191 """ 192 if self._head is None: 193 if default is nada: 194 return self._sentinel 195 else: 196 return default 197 else: 198 data = self._head._data 199 self._head, self._count = self._head._next, self._count-1 200 return data 201 202 @overload 203 def peak(self, default: D) -> D: 204 ... 205 @overload 206 def peak(self) -> D|S: 207 ... 208 def peak(self, default: D|Nada=nada) -> D|S|Nada: 209 """ 210 ##### Peak at top of SplitEnd 211 212 Returns the data at the top of the SplitEnd. 213 214 * does not consume the data 215 * if empty, data does not exist, so in that case return default 216 * if empty and no default given, return nothing: Nothing 217 218 """ 219 if self._head is None: 220 return default 221 return self._head._data 222 223 @overload 224 def head(self, default: D|S) -> D|S: 225 ... 226 @overload 227 def head(self) -> D|S: 228 ... 229 def head(self, default: D|S|Nada=nada) -> D|S|Nada: 230 """ 231 ##### Head of SplitEnd 232 233 Returns the data at the top of the SplitEnd. 234 235 * does not consume the data 236 * for an empty SplitEnd, head does not exist, so return default 237 * otherwise return the sentinel value 238 * the sentinel value cannot be overridden by nada 239 * of course self._sentinel can always be set to nada 240 241 """ 242 if self._head is None: 243 if default is nada: 244 return self._sentinel 245 else: 246 return default 247 return self._head._data 248 249 @overload 250 def tail(self, default: S) -> SplitEnd[D, S]|S: 251 ... 252 @overload 253 def tail(self) -> SplitEnd[D, S]|S: 254 ... 255 def tail(self, default: S|Nada=nada) -> SplitEnd[D, S]|S|Nada: 256 """ 257 ##### Tail of SplitEnd 258 259 Returns the tail of the SplitEnd if it exists, otherwise returns the 260 sentinel value, or a default value of the same type as the sentinel 261 value. 262 263 * optional default needs to be of the same type as the sentinel value 264 * example: 265 * sentinel: tuple(int) = (0,) 266 * default: tuple(int) = (42,) 267 * decided not to let default: SplitEnd[D, S] as a return option 268 * made end code more confusing to reason about 269 * not worth cognitive overload when used in client code 270 * tended to hide the occurrence of an unusual event occurring 271 * the sentinel value cannot be overridden by nada 272 * of course self._sentinel can always be set to nada 273 274 """ 275 if self._head: 276 se: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 277 se._head = self._head._next 278 se._count = self._count - 1 279 return se 280 else: 281 return default 282 283 @overload 284 def cons(self, d: D) -> SplitEnd[D, S]: 285 ... 286 @overload 287 def cons(self, d: Nada) -> Nada: 288 ... 289 def cons(self, d: D|Nada) -> SplitEnd[D, S]|Nada: 290 """ 291 ##### Cons SplitEnd with a Head 292 293 Return a new SplitEnd with data as head and self as tail. 294 295 Constructing a SplitEnd using a non-existent value as head results in 296 a non-existent SplitEnd. In that case, return sentinel: _S. 297 298 """ 299 if d is nada: 300 return nada 301 else: 302 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 303 stack._head = Node(cast(D, d), self._head) 304 stack._count = self._count + 1 305 return stack 306 307 def fold(self, f:Callable[[D, D], D]) -> Optional[D]: 308 """ 309 ##### Reduce with `f` 310 311 * returns a value of the of type _T if self is not empty 312 * returns None if self is empty 313 * folds in natural LIFO Order 314 * TODO: consolidate fold & fold1 315 316 """ 317 node: Optional[Node[D]] = self._head 318 if not node: 319 return None 320 acc: D = node._data 321 while node: 322 if (node := node._next) is None: 323 break 324 acc = f(acc, node._data) 325 return acc 326 327 def fold1(self, f:Callable[[T, D], T], s: T) -> T: 328 """Reduce with f. 329 330 * returns a value of type ~T 331 * type ~T can be same type as ~D 332 * folds in natural LIFO Order 333 * TODO: consolidate fold & fold1 334 335 """ 336 node: Optional[Node[D]] = self._head 337 if not node: 338 return s 339 acc: T = s 340 while node: 341 acc = f(acc, node._data) 342 node = node._next 343 return acc 344 345 def flatMap(self, f: Callable[[D], SplitEnd[T, S]], type: FM=FM.CONCAT) -> SplitEnd[T, S]: 346 """ 347 ##### Bind function to SplitEnd 348 349 Bind function `f` to the SplitEnd. 350 351 * type = CONCAT: sequentially concatenate iterables one after the other 352 * type = MERGE: merge iterables together until one is exhausted 353 * type = Exhaust: merge iterables together until all are exhausted 354 355 """ 356 match type: 357 case FM.CONCAT: 358 return SplitEnd(*concat(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 359 case FM.MERGE: 360 return SplitEnd(*merge(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 361 case FM.EXHAUST: 362 return SplitEnd(*exhaust(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 363 case '*': 364 raise ValueError('Unknown FM type') 365 366 def map(self, f: Callable[[D], T]) -> SplitEnd[T, S]: 367 """ 368 ##### Map `f` over the SplitEnd 369 370 Maps a function (or callable object) over the values on the SplitEnd Stack. 371 372 * TODO: Redo in "natural" order? 373 * Returns a new Stack object with shallow copied new data 374 * O(n) complexity 375 376 """ 377 return self.flatMap(lambda a: SplitEnd(f(a)))
SplitEnd
Class implementing a stack type data structures called a split end.
- each split end is a very simple stateful LIFO stack
- contains a count of nodes & reference to first node of a linked list
- different split ends can safely share the same tail
- each split end sees itself as a singularly linked list
- bush-like datastructures can be formed using multiple split ends
- len() returns the number of elements on the stack
- in a boolean context, return
True
if SplitEnd is not empty
81 def reverse(self) -> SplitEnd[D, S]: 82 """ 83 ##### Return a Reversed SplitEnd 84 85 Return shallow reversed copy of a SplitEnd. 86 87 * Returns a new Stack object with shallow copied new data 88 * creates all new nodes 89 * O(1) space & time complexity 90 91 """ 92 return SplitEnd(*self, s=self._sentinel)
Return a Reversed SplitEnd
Return shallow reversed copy of a SplitEnd.
- Returns a new Stack object with shallow copied new data
- creates all new nodes
- O(1) space & time complexity
151 def copy(self) -> SplitEnd[D, S]: 152 """ 153 ##### Shallow Copy 154 155 Return a swallow copy of the SplitEnd in O(1) space & time complexity. 156 157 """ 158 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 159 stack._head, stack._count = self._head, self._count 160 return stack
Shallow Copy
Return a swallow copy of the SplitEnd in O(1) space & time complexity.
162 def push(self, *ds: D) -> None: 163 """ 164 ##### Push Data 165 166 Push data onto top of the SplitEnd. 167 168 * ignore "non-existent" Nothing() values pushed on the SplitEnd 169 170 """ 171 for d in ds: 172 if d is not nada: 173 node = Node(d, self._head) 174 self._head, self._count = node, self._count+1
Push Data
Push data onto top of the SplitEnd.
- ignore "non-existent" Nothing() values pushed on the SplitEnd
182 def pop(self, default: D|Nada=nada) -> D|S|Nada: 183 """ 184 ##### Pop Data 185 186 Pop data off of the top of the SplitEnd. 187 188 * if empty, return a default value 189 * if empty and a default value not given, return the sentinel value 190 191 """ 192 if self._head is None: 193 if default is nada: 194 return self._sentinel 195 else: 196 return default 197 else: 198 data = self._head._data 199 self._head, self._count = self._head._next, self._count-1 200 return data
Pop Data
Pop data off of the top of the SplitEnd.
- if empty, return a default value
- if empty and a default value not given, return the sentinel value
208 def peak(self, default: D|Nada=nada) -> D|S|Nada: 209 """ 210 ##### Peak at top of SplitEnd 211 212 Returns the data at the top of the SplitEnd. 213 214 * does not consume the data 215 * if empty, data does not exist, so in that case return default 216 * if empty and no default given, return nothing: Nothing 217 218 """ 219 if self._head is None: 220 return default 221 return self._head._data
Peak at top of SplitEnd
Returns the data at the top of the SplitEnd.
- does not consume the data
- if empty, data does not exist, so in that case return default
- if empty and no default given, return nothing: Nothing
229 def head(self, default: D|S|Nada=nada) -> D|S|Nada: 230 """ 231 ##### Head of SplitEnd 232 233 Returns the data at the top of the SplitEnd. 234 235 * does not consume the data 236 * for an empty SplitEnd, head does not exist, so return default 237 * otherwise return the sentinel value 238 * the sentinel value cannot be overridden by nada 239 * of course self._sentinel can always be set to nada 240 241 """ 242 if self._head is None: 243 if default is nada: 244 return self._sentinel 245 else: 246 return default 247 return self._head._data
Head of SplitEnd
Returns the data at the top of the SplitEnd.
- does not consume the data
- for an empty SplitEnd, head does not exist, so return default
- otherwise return the sentinel value
- the sentinel value cannot be overridden by nada
- of course self._sentinel can always be set to nada
255 def tail(self, default: S|Nada=nada) -> SplitEnd[D, S]|S|Nada: 256 """ 257 ##### Tail of SplitEnd 258 259 Returns the tail of the SplitEnd if it exists, otherwise returns the 260 sentinel value, or a default value of the same type as the sentinel 261 value. 262 263 * optional default needs to be of the same type as the sentinel value 264 * example: 265 * sentinel: tuple(int) = (0,) 266 * default: tuple(int) = (42,) 267 * decided not to let default: SplitEnd[D, S] as a return option 268 * made end code more confusing to reason about 269 * not worth cognitive overload when used in client code 270 * tended to hide the occurrence of an unusual event occurring 271 * the sentinel value cannot be overridden by nada 272 * of course self._sentinel can always be set to nada 273 274 """ 275 if self._head: 276 se: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 277 se._head = self._head._next 278 se._count = self._count - 1 279 return se 280 else: 281 return default
Tail of SplitEnd
Returns the tail of the SplitEnd if it exists, otherwise returns the sentinel value, or a default value of the same type as the sentinel value.
- optional default needs to be of the same type as the sentinel value
- example:
- sentinel: tuple(int) = (0,)
- default: tuple(int) = (42,)
- decided not to let default: SplitEnd[D, S] as a return option
- made end code more confusing to reason about
- not worth cognitive overload when used in client code
- tended to hide the occurrence of an unusual event occurring
- made end code more confusing to reason about
- example:
- the sentinel value cannot be overridden by nada
- of course self._sentinel can always be set to nada
289 def cons(self, d: D|Nada) -> SplitEnd[D, S]|Nada: 290 """ 291 ##### Cons SplitEnd with a Head 292 293 Return a new SplitEnd with data as head and self as tail. 294 295 Constructing a SplitEnd using a non-existent value as head results in 296 a non-existent SplitEnd. In that case, return sentinel: _S. 297 298 """ 299 if d is nada: 300 return nada 301 else: 302 stack: SplitEnd[D, S] = SplitEnd(s=self._sentinel) 303 stack._head = Node(cast(D, d), self._head) 304 stack._count = self._count + 1 305 return stack
Cons SplitEnd with a Head
Return a new SplitEnd with data as head and self as tail.
Constructing a SplitEnd using a non-existent value as head results in a non-existent SplitEnd. In that case, return sentinel: _S.
307 def fold(self, f:Callable[[D, D], D]) -> Optional[D]: 308 """ 309 ##### Reduce with `f` 310 311 * returns a value of the of type _T if self is not empty 312 * returns None if self is empty 313 * folds in natural LIFO Order 314 * TODO: consolidate fold & fold1 315 316 """ 317 node: Optional[Node[D]] = self._head 318 if not node: 319 return None 320 acc: D = node._data 321 while node: 322 if (node := node._next) is None: 323 break 324 acc = f(acc, node._data) 325 return acc
Reduce with f
- returns a value of the of type _T if self is not empty
- returns None if self is empty
- folds in natural LIFO Order
- TODO: consolidate fold & fold1
327 def fold1(self, f:Callable[[T, D], T], s: T) -> T: 328 """Reduce with f. 329 330 * returns a value of type ~T 331 * type ~T can be same type as ~D 332 * folds in natural LIFO Order 333 * TODO: consolidate fold & fold1 334 335 """ 336 node: Optional[Node[D]] = self._head 337 if not node: 338 return s 339 acc: T = s 340 while node: 341 acc = f(acc, node._data) 342 node = node._next 343 return acc
Reduce with f.
- returns a value of type ~T
- type ~T can be same type as ~D
- folds in natural LIFO Order
- TODO: consolidate fold & fold1
345 def flatMap(self, f: Callable[[D], SplitEnd[T, S]], type: FM=FM.CONCAT) -> SplitEnd[T, S]: 346 """ 347 ##### Bind function to SplitEnd 348 349 Bind function `f` to the SplitEnd. 350 351 * type = CONCAT: sequentially concatenate iterables one after the other 352 * type = MERGE: merge iterables together until one is exhausted 353 * type = Exhaust: merge iterables together until all are exhausted 354 355 """ 356 match type: 357 case FM.CONCAT: 358 return SplitEnd(*concat(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 359 case FM.MERGE: 360 return SplitEnd(*merge(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 361 case FM.EXHAUST: 362 return SplitEnd(*exhaust(*map(lambda x: iter(x), map(f, self))), s=self._sentinel) 363 case '*': 364 raise ValueError('Unknown FM type')
Bind function to SplitEnd
Bind function f
to the SplitEnd.
- type = CONCAT: sequentially concatenate iterables one after the other
- type = MERGE: merge iterables together until one is exhausted
- type = Exhaust: merge iterables together until all are exhausted
366 def map(self, f: Callable[[D], T]) -> SplitEnd[T, S]: 367 """ 368 ##### Map `f` over the SplitEnd 369 370 Maps a function (or callable object) over the values on the SplitEnd Stack. 371 372 * TODO: Redo in "natural" order? 373 * Returns a new Stack object with shallow copied new data 374 * O(n) complexity 375 376 """ 377 return self.flatMap(lambda a: SplitEnd(f(a)))
Map f
over the SplitEnd
Maps a function (or callable object) over the values on the SplitEnd Stack.
- TODO: Redo in "natural" order?
- Returns a new Stack object with shallow copied new data
- O(n) complexity