From 94992ce3e948d30ca89440cad6c7e3eebbd52ea0 Mon Sep 17 00:00:00 2001 From: ftg Date: Sat, 30 Mar 2024 09:18:23 +0100 Subject: [PATCH] Initial commit --- nodes.py | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 nodes.py diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..f632d35 --- /dev/null +++ b/nodes.py @@ -0,0 +1,207 @@ +from __future__ import annotations # for Class forward reference +import random + +from typing import ClassVar + + +class Node: + """ + Should be subclassed only + """ + # Class Var for statistics + total_nodes_number: ClassVar[int] = 0 + deepest_level: ClassVar[int] = 0 + largest_sibling_number: ClassVar[int] = 0 + all_nodes: ClassVar[list[Node]] = [] + + def __init_subclass__(cls): + """ each subclass must define its own ClassVar """ + # TODO to be renamed for clarity + super().__init_subclass__() + cls.total_nodes_number: ClassVar[int] = 0 + cls.deepest_level: ClassVar[int] = 0 + cls.largest_sibling_number: ClassVar[int] = 0 + cls.all_nodes: ClassVar[list[Node]] = [] + return cls + + def __init__(self, name: str, parent: Node | None = None) -> None: + self.name = name + self.parent = parent # is set with add_child + self.children: list[Node] = [] + type(self).total_nodes_number += 1 + + @property + def siblings(self) -> list[Node]: + if self.has_siblings(): + return self.parent.children + + @property + def parents(self) -> list[Node]: + parents = [] + p = self + while p.has_parent(): + p = p.parent + parents.append(p) + return parents + + @property + def level(self) -> int: + level = 0 + p = self + while p.has_parent(): + level += 1 + p = p.parent + return level + + @property + def path(self) -> str: + """ + returns a representation of the ancestor lineage of self + """ + path = '' + for p in reversed(self.parents): + path += p.name+'.' + path += self.name + return path + + def is_sibling(self, other: str) -> bool: + if other in [s.name for s in self.siblings]: + return True + else: + return False + + def is_child(self, other: str) -> bool: + if other in [s.name for s in self.children]: + return True + else: + return False + + def pretty_print(self) -> None: + dashes = ' '*self.level+'|'+'--'*self.level+' ' + print(f'{dashes}{self.name}') + for c in self.children: + c.pretty_print() + + def add_child(self, child: Node) -> None: + child.parent = self + if child.name not in [c.name for c in self.children]: + self.children.append(child) + type(self).all_nodes.append(child) + if child.level > type(self).deepest_level: + type(self).deepest_level = child.level + if len(self.children) > type(self).largest_sibling_number: + type(self).largest_sibling_number = len(self.children) + else: + raise Exception('Child with same name already exists') + + def has_parent(self) -> bool: + if self.parent is not None: + return True + return False + + def has_children(self) -> bool: + if self.children is not None: + return True + return False + + def has_siblings(self) -> bool: + if self.has_parent() and self.parent.has_children() \ + and len(self.parent.children) > 0: + return True + return False + + def get_child(self, name: str) -> Node | None: + for c in self.children: + if c.name == name: + return c + return None + + def get_sibling(self, name: str) -> Node | None: + for c in self.siblings: + if c.name == name: + return c + return None + + def get_children(self, name: str) -> list[Node]: + # refactoring, recursion is not good + results = [] + if self.name == name: + results.append(self) + for c in self.children: + results += c.get_children(name) + return results + + @staticmethod + def check_lineage(nodes: list[Node]) -> bool: + """ + check if the list of nodes is a straight lineage: + node 1 (ancestor) -> node 2 -> node 3 -> ... -> node n (grand + children) + + """ + for i in range(1, len(nodes)): + if nodes[i].parent != nodes[i-1]: + return False + + return True + + @classmethod + def reset_stats(cls) -> None: + """ + reset all the ClassVar members + """ + cls.total_nodes_number = 0 + cls.deepest_level = 0 + cls.largest_sibling_number = 0 + cls.all_nodes = [] + + @classmethod + def create_random_nodes(cls, type_: str = 'cmd', depth: int = 0) -> Node: + """ + Creates random tree of nodes for testing purpose + """ + def create_node(level, i): + id_ = cls.total_nodes_number + 1 + return cls(f'{type_}_'+str(id_)) + + def create_node_list(level: int, width: int = 5): + return [create_node(level, i) + for i in range(random.randint(1, width))] + + def create_arg_tree(arg: cls): + if arg.level < depth: + for a in create_node_list(arg.level): + arg.add_child(a) + create_arg_tree(a) + return arg + + arg = cls('parser') + + return create_arg_tree(arg) + + def __lt__(self, other): + return self.level < other.level + + def __gt__(self, other): + return self.level > other.level + + def __le__(self, other): + return self.level <= other.level + + def __ge__(self, other): + return self.level >= other.level + + def __str__(self) -> str: + return self.name + + +if __name__ == "__main__": + + while True: + master_node = Node.create_random_nodes(depth=3, type_='cmd') + node_list = [master_node, master_node.children[0], + master_node.children[0].children[0]] + random.shuffle(node_list) + print([n.name for n in node_list]) + print(Node.check_lineage(node_list)) + input('pause')