grscheller.datastructures.splitends.se
SplitEnd stack related data structures
With use I am finding this data structure needs some sort of supporting infrastructure. Hence I split the original splitend module out to be its own subpackage.
SplitEnd Stack type and SE factory function
- class SplitEnd: Singularly linked stack with shareable data nodes
- function SE: create SplitEnd from a variable number of arguments
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"""### SplitEnd stack related data structures 16 17With use I am finding this data structure needs some sort of supporting 18infrastructure. Hence I split the original splitend module out to be its own 19subpackage. 20 21#### SplitEnd Stack type and SE factory function 22 23* class SplitEnd: Singularly linked stack with shareable data nodes 24* function SE: create SplitEnd from a variable number of arguments 25 26""" 27from __future__ import annotations 28 29from collections.abc import Callable, Iterable, Iterator 30from typing import cast, Never 31from ..nodes import SL_Node 32from grscheller.fp.err_handling import MB 33 34__all__ = [ 'SplitEnd', 'SE' ] 35 36class SplitEnd[D](): 37 """Class SplitEnd 38 39 LIFO stacks which can safely share immutable data between themselves. 40 41 * each SplitEnd is a very simple stateful (mutable) LIFO stack 42 * top of the stack is the "top" 43 * data can be pushed and popped to the stack 44 * different mutable split ends can safely share the same "tail" 45 * each SplitEnd sees itself as a singularly linked list 46 * bush-like datastructures can be formed using multiple SplitEnds 47 * len() returns the number of elements on the SplitEnd stack 48 * in boolean context, return true if split end is not empty 49 50 """ 51 __slots__ = '_count', '_tip' 52 53 def __init__(self, *dss: Iterable[D]) -> None: 54 if length:=len(dss) < 2: 55 self._tip: MB[SL_Node[D]] = MB() 56 self._count: int = 0 57 if length == 1: 58 self.pushI(*dss) 59 else: 60 msg1 = 'SplitEnd: expected at most 1 ' 61 msg2 = f'iterable argument, got {length}.' 62 raise TypeError(msg1+msg2) 63 64 def __iter__(self) -> Iterator[D]: 65 if self._tip == MB(): 66 empty: tuple[D, ...] = () 67 return iter(empty) 68 return iter(self._tip.get()) 69 70 def __reversed__(self) -> Iterator[D]: 71 return reversed(list(self)) 72 73 def __bool__(self) -> bool: 74 # Returns true if not a root node 75 return bool(self._tip) 76 77 def __len__(self) -> int: 78 return self._count 79 80 def __repr__(self) -> str: 81 return 'SE(' + ', '.join(map(repr, reversed(self))) + ')' 82 83 def __str__(self) -> str: 84 return ('>< ' + ' -> '.join(map(str, self)) + ' ||') 85 86 def __eq__(self, other: object, /) -> bool: 87 if not isinstance(other, type(self)): 88 return False 89 90 if self._count != other._count: 91 return False 92 if self._count == 0: 93 return True 94 95 left = self._tip.get() 96 right = other._tip.get() 97 for _ in range(self._count): 98 if left is right: 99 return True 100 if not left.data_eq(right): 101 return False 102 if left: 103 left = left._prev.get() 104 right = right._prev.get() 105 106 return True 107 108 def pushI(self, ds: Iterable[D], /) -> None: 109 """Push data onto the top of the SplitEnd.""" 110 for d in ds: 111 node = SL_Node(d, self._tip) 112 self._tip, self._count = MB(node), self._count+1 113 114 def push(self, *ds: D) -> None: 115 """Push data onto the top of the SplitEnd.""" 116 for d in ds: 117 node = SL_Node(d, self._tip) 118 self._tip, self._count = MB(node), self._count+1 119 120 def pop(self, default: D|None = None, /) -> D|Never: 121 """Pop data off of the top of the SplitEnd. 122 123 * raises ValueError if 124 * popping from an empty SplitEnd 125 * and no default value was given 126 127 """ 128 if self._count == 0: 129 if default is None: 130 raise ValueError('SE: Popping from an empty SplitEnd') 131 else: 132 return default 133 134 data, self._tip, self._count = self._tip.get().pop2() + (self._count-1,) 135 return data 136 137 def peak(self, default: D|None = None, /) -> D: 138 """Return the data at the top of the SplitEnd. 139 140 * does not consume the data 141 * raises ValueError if peaking at an empty SplitEnd 142 143 """ 144 if self._count == 0: 145 if default is None: 146 raise ValueError('SE: Popping from an empty SplitEnd') 147 else: 148 return default 149 150 return self._tip.get().get_data() 151 152 def copy(self) -> SplitEnd[D]: 153 """Return a copy of the SplitEnd. 154 155 * O(1) space & time complexity. 156 * returns a new instance 157 158 """ 159 se: SplitEnd[D] = SE() 160 se._tip, se._count = self._tip, self._count 161 return se 162 163 def fold[T](self, f:Callable[[T, D], T], init: T|None = None, /) -> T|Never: 164 """Reduce with a function. 165 166 * folds in natural LIFO Order 167 168 """ 169 if self._tip != MB(): 170 return self._tip.get().fold(f, init) 171 elif init is not None: 172 return init 173 else: 174 msg = 'SE: Folding empty SplitEnd but no initial value supplied' 175 raise ValueError(msg) 176 177def SE[D](*ds: D) -> SplitEnd[D]: 178 return SplitEnd(ds)
class
SplitEnd(typing.Generic[D]):
37class SplitEnd[D](): 38 """Class SplitEnd 39 40 LIFO stacks which can safely share immutable data between themselves. 41 42 * each SplitEnd is a very simple stateful (mutable) LIFO stack 43 * top of the stack is the "top" 44 * data can be pushed and popped to the stack 45 * different mutable split ends can safely share the same "tail" 46 * each SplitEnd sees itself as a singularly linked list 47 * bush-like datastructures can be formed using multiple SplitEnds 48 * len() returns the number of elements on the SplitEnd stack 49 * in boolean context, return true if split end is not empty 50 51 """ 52 __slots__ = '_count', '_tip' 53 54 def __init__(self, *dss: Iterable[D]) -> None: 55 if length:=len(dss) < 2: 56 self._tip: MB[SL_Node[D]] = MB() 57 self._count: int = 0 58 if length == 1: 59 self.pushI(*dss) 60 else: 61 msg1 = 'SplitEnd: expected at most 1 ' 62 msg2 = f'iterable argument, got {length}.' 63 raise TypeError(msg1+msg2) 64 65 def __iter__(self) -> Iterator[D]: 66 if self._tip == MB(): 67 empty: tuple[D, ...] = () 68 return iter(empty) 69 return iter(self._tip.get()) 70 71 def __reversed__(self) -> Iterator[D]: 72 return reversed(list(self)) 73 74 def __bool__(self) -> bool: 75 # Returns true if not a root node 76 return bool(self._tip) 77 78 def __len__(self) -> int: 79 return self._count 80 81 def __repr__(self) -> str: 82 return 'SE(' + ', '.join(map(repr, reversed(self))) + ')' 83 84 def __str__(self) -> str: 85 return ('>< ' + ' -> '.join(map(str, self)) + ' ||') 86 87 def __eq__(self, other: object, /) -> bool: 88 if not isinstance(other, type(self)): 89 return False 90 91 if self._count != other._count: 92 return False 93 if self._count == 0: 94 return True 95 96 left = self._tip.get() 97 right = other._tip.get() 98 for _ in range(self._count): 99 if left is right: 100 return True 101 if not left.data_eq(right): 102 return False 103 if left: 104 left = left._prev.get() 105 right = right._prev.get() 106 107 return True 108 109 def pushI(self, ds: Iterable[D], /) -> None: 110 """Push data onto the top of the SplitEnd.""" 111 for d in ds: 112 node = SL_Node(d, self._tip) 113 self._tip, self._count = MB(node), self._count+1 114 115 def push(self, *ds: D) -> None: 116 """Push data onto the top of the SplitEnd.""" 117 for d in ds: 118 node = SL_Node(d, self._tip) 119 self._tip, self._count = MB(node), self._count+1 120 121 def pop(self, default: D|None = None, /) -> D|Never: 122 """Pop data off of the top of the SplitEnd. 123 124 * raises ValueError if 125 * popping from an empty SplitEnd 126 * and no default value was given 127 128 """ 129 if self._count == 0: 130 if default is None: 131 raise ValueError('SE: Popping from an empty SplitEnd') 132 else: 133 return default 134 135 data, self._tip, self._count = self._tip.get().pop2() + (self._count-1,) 136 return data 137 138 def peak(self, default: D|None = None, /) -> D: 139 """Return the data at the top of the SplitEnd. 140 141 * does not consume the data 142 * raises ValueError if peaking at an empty SplitEnd 143 144 """ 145 if self._count == 0: 146 if default is None: 147 raise ValueError('SE: Popping from an empty SplitEnd') 148 else: 149 return default 150 151 return self._tip.get().get_data() 152 153 def copy(self) -> SplitEnd[D]: 154 """Return a copy of the SplitEnd. 155 156 * O(1) space & time complexity. 157 * returns a new instance 158 159 """ 160 se: SplitEnd[D] = SE() 161 se._tip, se._count = self._tip, self._count 162 return se 163 164 def fold[T](self, f:Callable[[T, D], T], init: T|None = None, /) -> T|Never: 165 """Reduce with a function. 166 167 * folds in natural LIFO Order 168 169 """ 170 if self._tip != MB(): 171 return self._tip.get().fold(f, init) 172 elif init is not None: 173 return init 174 else: 175 msg = 'SE: Folding empty SplitEnd but no initial value supplied' 176 raise ValueError(msg)
Class SplitEnd
LIFO stacks which can safely share immutable data between themselves.
- each SplitEnd is a very simple stateful (mutable) LIFO stack
- top of the stack is the "top"
- data can be pushed and popped to the stack
- different mutable split ends can safely share the same "tail"
- each SplitEnd sees itself as a singularly linked list
- bush-like datastructures can be formed using multiple SplitEnds
- len() returns the number of elements on the SplitEnd stack
- in boolean context, return true if split end is not empty
SplitEnd(*dss: 'Iterable[D]')
54 def __init__(self, *dss: Iterable[D]) -> None: 55 if length:=len(dss) < 2: 56 self._tip: MB[SL_Node[D]] = MB() 57 self._count: int = 0 58 if length == 1: 59 self.pushI(*dss) 60 else: 61 msg1 = 'SplitEnd: expected at most 1 ' 62 msg2 = f'iterable argument, got {length}.' 63 raise TypeError(msg1+msg2)
def
pushI(self, ds: 'Iterable[D]', /) -> None:
109 def pushI(self, ds: Iterable[D], /) -> None: 110 """Push data onto the top of the SplitEnd.""" 111 for d in ds: 112 node = SL_Node(d, self._tip) 113 self._tip, self._count = MB(node), self._count+1
Push data onto the top of the SplitEnd.
def
push(self, *ds: 'D') -> None:
115 def push(self, *ds: D) -> None: 116 """Push data onto the top of the SplitEnd.""" 117 for d in ds: 118 node = SL_Node(d, self._tip) 119 self._tip, self._count = MB(node), self._count+1
Push data onto the top of the SplitEnd.
def
pop(self, default: 'D | None' = None, /) -> 'D | Never':
121 def pop(self, default: D|None = None, /) -> D|Never: 122 """Pop data off of the top of the SplitEnd. 123 124 * raises ValueError if 125 * popping from an empty SplitEnd 126 * and no default value was given 127 128 """ 129 if self._count == 0: 130 if default is None: 131 raise ValueError('SE: Popping from an empty SplitEnd') 132 else: 133 return default 134 135 data, self._tip, self._count = self._tip.get().pop2() + (self._count-1,) 136 return data
Pop data off of the top of the SplitEnd.
- raises ValueError if
- popping from an empty SplitEnd
- and no default value was given
def
peak(self, default: 'D | None' = None, /) -> 'D':
138 def peak(self, default: D|None = None, /) -> D: 139 """Return the data at the top of the SplitEnd. 140 141 * does not consume the data 142 * raises ValueError if peaking at an empty SplitEnd 143 144 """ 145 if self._count == 0: 146 if default is None: 147 raise ValueError('SE: Popping from an empty SplitEnd') 148 else: 149 return default 150 151 return self._tip.get().get_data()
Return the data at the top of the SplitEnd.
- does not consume the data
- raises ValueError if peaking at an empty SplitEnd
def
copy(self) -> 'SplitEnd[D]':
153 def copy(self) -> SplitEnd[D]: 154 """Return a copy of the SplitEnd. 155 156 * O(1) space & time complexity. 157 * returns a new instance 158 159 """ 160 se: SplitEnd[D] = SE() 161 se._tip, se._count = self._tip, self._count 162 return se
Return a copy of the SplitEnd.
- O(1) space & time complexity.
- returns a new instance
def
fold( self, f: 'Callable[[T, D], T]', init: 'T | None' = None, /) -> 'T | Never':
164 def fold[T](self, f:Callable[[T, D], T], init: T|None = None, /) -> T|Never: 165 """Reduce with a function. 166 167 * folds in natural LIFO Order 168 169 """ 170 if self._tip != MB(): 171 return self._tip.get().fold(f, init) 172 elif init is not None: 173 return init 174 else: 175 msg = 'SE: Folding empty SplitEnd but no initial value supplied' 176 raise ValueError(msg)
Reduce with a function.
- folds in natural LIFO Order
def
SE(*ds: 'D') -> 'SplitEnd[D]':