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
SE(*ds: 'D')
49    def __init__(self, *ds: D) -> None:
50        self._tip: MB[SL_Node[D]] = MB()
51        self._count: int = 0
52        self.push(*ds)
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