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