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