grscheller.datastructures.nodes
Nodes for Graphs
Node classes used with graph-like data structures. API designed to be used by other data structures which contain these data structures.
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### Nodes for Graphs 17 18Node classes used with graph-like data structures. API designed to be used by 19other data structures which contain these data structures. 20 21""" 22from __future__ import annotations 23from typing import Callable, cast, Iterator, Optional 24from grscheller.fp.err_handling import MB 25 26__all__ = ['SL_Node', 'DL_Node', 'Tree_Node'] 27 28class SL_Node[D](): 29 """ 30 Data node for rearward Pointing (tip-to-root) singularly linked graphs. 31 32 * for mutable and immutable linear data structures 33 * designed so multiple instances can safely share the same data 34 * this type of node always contain data and optionally a previous Node 35 * nodes point towards a unique "root node" with no predecessor 36 * in a Boolean context return false only if only at a root 37 * multiple root nodes can exist 38 * empty data structures can be "re-rooted" 39 * two nodes compare as equal if 40 * both their previous Nodes are the same 41 * their data compares as equal 42 * more than one node can point to the same node forming bush like graphs 43 44 """ 45 __slots__ = '_data', '_prev' 46 47 def __init__(self, data: D, prev: MB[SL_Node[D]]) -> None: 48 self._data = data 49 self._prev = prev 50 51 def __iter__(self) -> Iterator[D]: 52 node = self 53 while node: 54 yield node._data 55 node = node._prev.get() 56 yield node._data 57 58 def __bool__(self) -> bool: 59 return self._prev != MB() 60 61 def data_eq(self, other: SL_Node[D]) -> bool: 62 if self._data is other._data: 63 return True 64 elif self._data == other._data: 65 return True 66 else: 67 return False 68 69 def __eq__(self, other: object) -> bool: 70 if not isinstance(other, type(self)): 71 return False 72 73 if self._prev is not other._prev: 74 return False 75 else: 76 return self.data_eq(other) 77 78 def get_data(self) -> D: 79 return self._data 80 81 def fold[T](self, f: Callable[[T, D], T], init: Optional[T] = None) -> T: 82 """Reduce data across linked nodes 83 84 * with a function and an optional starting value 85 * reduces in natural LIFO order 86 * from self to the root 87 88 """ 89 if init is None: 90 acc: T = cast(T, self._data) 91 node = self._prev.get() 92 else: 93 acc = init 94 node = self 95 96 while node: 97 acc = f(acc, node._data) 98 node = node._prev.get() 99 acc = f(acc, node._data) 100 return acc 101 102 def pop2(self) -> tuple[D, MB[SL_Node[D]]]: 103 """Return the *head* and, if it exists, the top node of the *tail*.""" 104 return self._data, self._prev 105 106 def push_data(self, data: D) -> SL_Node[D]: 107 """Push data onto the stack and return a new node containing the data""" 108 return SL_Node(data, MB(self)) 109 110class DL_Node[D](): 111 """ 112 #### Doubly Linked Node 113 114 Doubly linked nodes for graph-like data structures. 115 116 * this type of node always contain data, even if that data is None 117 * in a Boolean context return true if both left and right nodes exist 118 * not __iter__ method provided, too application specific 119 * doubly link lists possible 120 * circular graphs are possible 121 * simple recursive binary trees possible 122 123 """ 124 __slots__ = '_left', '_data', '_right' 125 126 def __init__(self, left: MB[DL_Node[D]], data: D, right: MB[DL_Node[D]]): 127 self._left = left 128 self._data = data 129 self._right = right 130 131 def __bool__(self) -> bool: 132 if self._left == MB() or self._right == MB(): 133 return False 134 return True 135 136 def __eq__(self, other: object) -> bool: 137 if not isinstance(other, type(self)): 138 return False 139 140 if self._left is not other._left: 141 return False 142 if self._right is not other._right: 143 return False 144 if self._data is other._data: 145 return True 146 elif self._data == other._data: 147 return True 148 149 return False 150 151 def has_left(self) -> bool: 152 return self._left != MB() 153 154 def has_right(self) -> bool: 155 return self._right != MB() 156 157class Tree_Node[D, M](): 158 """Binary Tree Node with metadata. 159 160 Nodes useful for binary trees 161 162 * this type of node always contain data, even if that data is None 163 * in a Boolean context return true if not at the top of the tree 164 * potential uses of metadata can be for re-balancing or repeat counts 165 """ 166 __slots__ = '_data', '_left', '_right', '_up' 167 168 def __init__(self, data: D, 169 up: MB[Tree_Node[D,M]], 170 left: MB[Tree_Node[D,M]], 171 right: MB[Tree_Node[D,M]], 172 meta: tuple[M, ...] = ()): 173 self._data = data 174 self._up = up 175 self._left = left 176 self._right = right 177 178 def __bool__(self) -> bool: 179 if self._up == MB(): 180 return False 181 else: 182 return True 183 184 def is_top(self) -> bool: 185 return self._up == MB()
class
SL_Node(typing.Generic[D]):
29class SL_Node[D](): 30 """ 31 Data node for rearward Pointing (tip-to-root) singularly linked graphs. 32 33 * for mutable and immutable linear data structures 34 * designed so multiple instances can safely share the same data 35 * this type of node always contain data and optionally a previous Node 36 * nodes point towards a unique "root node" with no predecessor 37 * in a Boolean context return false only if only at a root 38 * multiple root nodes can exist 39 * empty data structures can be "re-rooted" 40 * two nodes compare as equal if 41 * both their previous Nodes are the same 42 * their data compares as equal 43 * more than one node can point to the same node forming bush like graphs 44 45 """ 46 __slots__ = '_data', '_prev' 47 48 def __init__(self, data: D, prev: MB[SL_Node[D]]) -> None: 49 self._data = data 50 self._prev = prev 51 52 def __iter__(self) -> Iterator[D]: 53 node = self 54 while node: 55 yield node._data 56 node = node._prev.get() 57 yield node._data 58 59 def __bool__(self) -> bool: 60 return self._prev != MB() 61 62 def data_eq(self, other: SL_Node[D]) -> bool: 63 if self._data is other._data: 64 return True 65 elif self._data == other._data: 66 return True 67 else: 68 return False 69 70 def __eq__(self, other: object) -> bool: 71 if not isinstance(other, type(self)): 72 return False 73 74 if self._prev is not other._prev: 75 return False 76 else: 77 return self.data_eq(other) 78 79 def get_data(self) -> D: 80 return self._data 81 82 def fold[T](self, f: Callable[[T, D], T], init: Optional[T] = None) -> T: 83 """Reduce data across linked nodes 84 85 * with a function and an optional starting value 86 * reduces in natural LIFO order 87 * from self to the root 88 89 """ 90 if init is None: 91 acc: T = cast(T, self._data) 92 node = self._prev.get() 93 else: 94 acc = init 95 node = self 96 97 while node: 98 acc = f(acc, node._data) 99 node = node._prev.get() 100 acc = f(acc, node._data) 101 return acc 102 103 def pop2(self) -> tuple[D, MB[SL_Node[D]]]: 104 """Return the *head* and, if it exists, the top node of the *tail*.""" 105 return self._data, self._prev 106 107 def push_data(self, data: D) -> SL_Node[D]: 108 """Push data onto the stack and return a new node containing the data""" 109 return SL_Node(data, MB(self))
Data node for rearward Pointing (tip-to-root) singularly linked graphs.
- for mutable and immutable linear data structures
- designed so multiple instances can safely share the same data
- this type of node always contain data and optionally a previous Node
- nodes point towards a unique "root node" with no predecessor
- in a Boolean context return false only if only at a root
- multiple root nodes can exist
- empty data structures can be "re-rooted"
- two nodes compare as equal if
- both their previous Nodes are the same
- their data compares as equal
- more than one node can point to the same node forming bush like graphs
def
fold(self, f: 'Callable[[T, D], T]', init: 'Optional[T]' = None) -> 'T':
82 def fold[T](self, f: Callable[[T, D], T], init: Optional[T] = None) -> T: 83 """Reduce data across linked nodes 84 85 * with a function and an optional starting value 86 * reduces in natural LIFO order 87 * from self to the root 88 89 """ 90 if init is None: 91 acc: T = cast(T, self._data) 92 node = self._prev.get() 93 else: 94 acc = init 95 node = self 96 97 while node: 98 acc = f(acc, node._data) 99 node = node._prev.get() 100 acc = f(acc, node._data) 101 return acc
Reduce data across linked nodes
- with a function and an optional starting value
- reduces in natural LIFO order
- from self to the root
class
DL_Node(typing.Generic[D]):
111class DL_Node[D](): 112 """ 113 #### Doubly Linked Node 114 115 Doubly linked nodes for graph-like data structures. 116 117 * this type of node always contain data, even if that data is None 118 * in a Boolean context return true if both left and right nodes exist 119 * not __iter__ method provided, too application specific 120 * doubly link lists possible 121 * circular graphs are possible 122 * simple recursive binary trees possible 123 124 """ 125 __slots__ = '_left', '_data', '_right' 126 127 def __init__(self, left: MB[DL_Node[D]], data: D, right: MB[DL_Node[D]]): 128 self._left = left 129 self._data = data 130 self._right = right 131 132 def __bool__(self) -> bool: 133 if self._left == MB() or self._right == MB(): 134 return False 135 return True 136 137 def __eq__(self, other: object) -> bool: 138 if not isinstance(other, type(self)): 139 return False 140 141 if self._left is not other._left: 142 return False 143 if self._right is not other._right: 144 return False 145 if self._data is other._data: 146 return True 147 elif self._data == other._data: 148 return True 149 150 return False 151 152 def has_left(self) -> bool: 153 return self._left != MB() 154 155 def has_right(self) -> bool: 156 return self._right != MB()
Doubly Linked Node
Doubly linked nodes for graph-like data structures.
- this type of node always contain data, even if that data is None
- in a Boolean context return true if both left and right nodes exist
- not __iter__ method provided, too application specific
- doubly link lists possible
- circular graphs are possible
- simple recursive binary trees possible
class
Tree_Node(typing.Generic[D, M]):
158class Tree_Node[D, M](): 159 """Binary Tree Node with metadata. 160 161 Nodes useful for binary trees 162 163 * this type of node always contain data, even if that data is None 164 * in a Boolean context return true if not at the top of the tree 165 * potential uses of metadata can be for re-balancing or repeat counts 166 """ 167 __slots__ = '_data', '_left', '_right', '_up' 168 169 def __init__(self, data: D, 170 up: MB[Tree_Node[D,M]], 171 left: MB[Tree_Node[D,M]], 172 right: MB[Tree_Node[D,M]], 173 meta: tuple[M, ...] = ()): 174 self._data = data 175 self._up = up 176 self._left = left 177 self._right = right 178 179 def __bool__(self) -> bool: 180 if self._up == MB(): 181 return False 182 else: 183 return True 184 185 def is_top(self) -> bool: 186 return self._up == MB()
Binary Tree Node with metadata.
Nodes useful for binary trees
- this type of node always contain data, even if that data is None
- in a Boolean context return true if not at the top of the tree
- potential uses of metadata can be for re-balancing or repeat counts