Source code for mode.utils.trees

"""Data structure: Trees."""
from contextlib import suppress
from typing import Any, Iterator, List, cast

from .graphs import DependencyGraph
from .objects import shortlabel
from .types.graphs import DependencyGraphT
from .types.trees import NodeT, _T
from .typing import Deque

__all__ = [
    'Node',
]


[docs]class Node(NodeT[_T]): """Tree node. Notes: Nodes have a link to - the ``.root`` node (or None if this is the top-most node) - the ``.parent`` node (if this is a child node). - a list of children A Node may have arbitrary ``.data`` associated with it, and arbitrary data may also be stored in ``.children``. Arguments: data (Any): Data to associate with node. Keyword Arguments: root (NodeT): Root node. parent (NodeT): Parent node. children (List[NodeT]): List of child nodes. """ _root: NodeT[_T] = None _parent: NodeT[_T] = None @classmethod def _new_node(cls, data: _T, **kwargs: Any) -> NodeT[_T]: return cls(data, **kwargs) # type: ignore def __init__(self, data: _T, *, root: NodeT = None, parent: NodeT = None, children: List[NodeT[_T]] = None) -> None: self.data = data self.root = root self.parent = parent self.children = children or []
[docs] def new(self, data: _T) -> NodeT: """Create new node from this node.""" node = self._new_node( data, root=self.root if self.root is not None else self, parent=self, ) self.children.append(node) return node
[docs] def reattach(self, parent: NodeT[_T]) -> NodeT[_T]: """Attach this node to `parent` node.""" self.root = parent.root if parent.root is not None else parent self.parent = parent parent.add(self) return self
[docs] def detach(self, parent: NodeT[_T]) -> NodeT[_T]: """Detach this node from `parent` node.""" self.parent.discard(self) self.parent = None self.root = None return self
[docs] def add(self, data: _T) -> None: """Add node as a child node.""" self.children.append(data)
[docs] def discard(self, data: _T) -> None: """Remove node so it's no longer a child of this node.""" # XXX slow with suppress(ValueError): self.children.remove(data)
[docs] def traverse(self) -> Iterator[NodeT[_T]]: """Iterate over the tree in BFS order.""" stack: Deque[NodeT[_T]] = Deque([self]) while stack: node = stack.popleft() yield node for child in node.children: if isinstance(child, NodeT): stack.append(child) else: yield child
[docs] def walk(self) -> Iterator[NodeT[_T]]: """Iterate over hierarchy backwards. This will yield parent nodes all the way up to the root. """ node: NodeT[_T] = self while node: yield node node = node.parent
[docs] def as_graph(self) -> DependencyGraphT: """Convert to :class:`~mode.utils.graphs.DependencyGraph`.""" graph = DependencyGraph() stack: Deque[NodeT] = Deque([self]) while stack: node = stack.popleft() for child in node.children: graph.add_arc(node.data) if isinstance(child, NodeT): stack.append(cast(Node, child)) graph.add_edge(node.data, child.data) else: graph.add_edge(node.data, child) return graph
def __repr__(self) -> str: return f'{type(self).__name__}: {self.path}' @property def depth(self) -> int: return self._find_depth() def _find_depth(self) -> int: return sum(1 for _ in enumerate(self.walk())) @property def path(self) -> str: return '/'.join(reversed([ shortlabel(node.data) for node in self.walk() ])) @property def parent(self) -> NodeT: return self._parent @parent.setter def parent(self, node: NodeT) -> None: if node is self: raise ValueError('Parent node cannot be itself.') self._parent = node @property def root(self) -> NodeT: return self._root @root.setter def root(self, node: NodeT) -> None: if node is self: raise ValueError('Root node cannot be itself.') self._root = node