From c8c3fc45f174ed7b2e4ef8d76e1b614165eb8c63 Mon Sep 17 00:00:00 2001 From: Diogo Henrique Fragoso de Oliveira Date: Thu, 25 Sep 2025 11:57:17 -0300 Subject: [PATCH] Implement Node and NodeManager with full hierarchy management - Node class with id and parent, type hints, __repr__, __eq__, and __hash__. - NodeManager class supporting single removal and recursive cascade removal. - Robust input validation and immutability of the input node list. - Optimized DFS for remove_cascade ensuring O(n) complexity for hierarchical operations. - Code follows PEP8, type hints, and senior-level best practices. - All pytest tests pass. --- nodes/manager.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++-- nodes/node.py | 50 ++++++++++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 6 deletions(-) diff --git a/nodes/manager.py b/nodes/manager.py index b3c1178..96a923d 100644 --- a/nodes/manager.py +++ b/nodes/manager.py @@ -1,5 +1,70 @@ -# Create here the NodeManager Class - +from typing import List +from collections import defaultdict +from nodes.node import Node class NodeManager: - pass + """ + Manages a collection of Node objects, allowing single or cascade removal + while respecting hierarchical dependencies. + """ + + nodes: List[Node] + + def __init__(self, nodes: List[Node]) -> None: + """ + Initialize NodeManager with a list of Node instances. + + Raises: + ValueError: If input is not a list of Node objects. + """ + if not isinstance(nodes, list) or not all(isinstance(n, Node) for n in nodes): + raise ValueError("NodeManager must be initialized with a list of Node objects.") + self.nodes = list(nodes) # Create a shallow copy to avoid mutating input + + def __len__(self) -> int: + """Return the number of nodes managed.""" + return len(self.nodes) + + def __getitem__(self, index: int) -> Node: + """Allow indexed access to nodes (manager[index]).""" + return self.nodes[index] + + def remove(self, node: Node) -> None: + """ + Remove the specified node only. + + Raises: + ValueError: If the node does not exist in the manager. + """ + try: + self.nodes.remove(node) + except ValueError: + raise ValueError("Node not found in manager.") + + def remove_cascade(self, node: Node) -> None: + """ + Remove the specified node and all its descendants recursively. + + Raises: + ValueError: If the node does not exist in the manager. + """ + if node not in self.nodes: + raise ValueError("Node not found in manager.") + + # Build parent_id -> list of children mapping for fast lookup + parent_map = defaultdict(list) + for n in self.nodes: + parent_map[n.parent].append(n) + + # Depth-First Search (DFS) to collect all descendant nodes + to_remove_ids = set() + stack = [node.id] + while stack: + current_id = stack.pop() + to_remove_ids.add(current_id) + for child in parent_map.get(current_id, []): + if child.id not in to_remove_ids: + stack.append(child.id) + + # Filter out all nodes that should be removed + self.nodes = [n for n in self.nodes if n.id not in to_remove_ids] diff --git a/nodes/node.py b/nodes/node.py index 644ab4e..3f27ab5 100644 --- a/nodes/node.py +++ b/nodes/node.py @@ -1,5 +1,49 @@ -# Create here the Node Class - +from typing import Any class Node: - pass + """ + Represents a node in a hierarchical document/tree structure. + Each node has a unique identifier (id) and a parent node (parent), both integers. + """ + + __slots__ = ('id', 'parent') # Memory optimization and faster attribute access + + id: int + parent: int + + def __init__(self, id: int, parent: int) -> None: + """ + Initialize a Node instance with id and parent. + + Raises: + ValueError: If id or parent are not integers, or if parent is invalid. + """ + if not isinstance(id, int) or not isinstance(parent, int): + raise ValueError("Both id and parent must be integers.") + if parent == id: + raise ValueError("A node cannot be its own parent.") + if parent > id: + raise ValueError("Parent id cannot be greater than node id.") + self.id = id + self.parent = parent + + def __repr__(self) -> str: + """ + Return the string representation of the Node that allows recreation using eval(). + Example: Node(2, 1) + """ + return f"Node({self.id}, {self.parent})" + + def __eq__(self, other: Any) -> bool: + """ + Compare Node equality based on id and parent. + """ + if not isinstance(other, Node): + return NotImplemented + return self.id == other.id and self.parent == other.parent + + def __hash__(self) -> int: + """ + Provide a hash function to allow using Node in sets or as dict keys. + """ + return hash((self.id, self.parent))