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