Source code for stupidb.associative.indextree

"""Module implementing an abstraction for navigation of array-backed trees."""

from typing import MutableSequence, Sequence, TypeVar

from .bitset import BitSet

T = TypeVar("T", covariant=True)


[docs]def reprtree(nodes: Sequence[T], *, fanout: int, indent: str = 4 * " ") -> str: """Return a string representation of `nodes`. Parameters ---------- nodes A sequence of nodes in a tree. fanout The number of children per node. indent The prefix number of spaces to print at each level. """ # track the current level of the tree and the index of the current node level_index_stack = [(0, 0)] # store the nodes that we've seen seen = BitSet() node_repr_pieces: MutableSequence[str] = [] while level_index_stack: level, node_index = level_index_stack.pop() if node_index not in seen: node = nodes[node_index] node_repr_pieces.append(f"{level * indent}|-- {node}") node_indices = ( fanout * node_index + i + 1 for i in reversed(range(fanout)) ) level_index_stack.extend( (level + 1, index) for index in node_indices if index < len(nodes) ) seen.add(node_index) return "\n".join(node_repr_pieces)
[docs]def first_node(level: int, *, fanout: int) -> int: """Return the first node at `level`.""" return (fanout ** level - 1) // (fanout - 1)
[docs]def last_node(level: int, *, fanout: int) -> int: """Return the last node at `level`.""" return (fanout ** (level + 1) - 1) // (fanout - 1)
[docs]class IndexTree: """Abstraction for navigating around array-backed trees.""" __slots__ = "height", "fanout", "nodes" def __init__(self, *, height: int, fanout: int) -> None: """Construct an :class:`~stupidb.associative.indextree.IndexTree`.""" self.height = height self.fanout = fanout self.nodes = range((fanout ** height - 1) // (fanout - 1)) @property def leaves(self) -> range: """Return the indices of the leaves of the tree.""" height = self.height - 1 first = self.first_node(height) last = self.last_node(height) return self.nodes[first:last] def __repr__(self) -> str: return reprtree(self.nodes, fanout=self.fanout) def __len__(self) -> int: """Return the number of nodes in the tree.""" return len(self.nodes)
[docs] def first_node(self, level: int) -> int: """Return the first node at `level`.""" return first_node(level, fanout=self.fanout)
[docs] def last_node(self, level: int) -> int: """Return the last node at `level`.""" return last_node(level, fanout=self.fanout)
[docs] def parent(self, node: int) -> int: """Return the index of the parent node of `node`.""" if not node: parent_node_index = 0 else: parent_node_index = (node - 1) // self.fanout # parent should never be negative assert ( parent_node_index >= 0 ), f"parent_node_index < 0: parent_node_index == {parent_node_index}" return parent_node_index