Search code examples
pythondata-structurestree

Construct a genera- tree structure recursively in python?


I built this Node class:

 
class Node:
    """
    Node of Tree object:
    """

    def __init__(self, data):
        self.data = data
        self.parent = None
        self.children = list()

    def __repr__(self) -> str:
        return f"Node: {self.data}"

    def set_data(self, data):
        self.data = data

    def get_data(self):
        return self.data

    def set_children(self, *children):
        for child in children:
            child.parent = self
            assert isinstance(child, Node)
            self.children.append(child)

    def get_children(self):
        return self.children

    def set_parent(self, parent):
        assert isinstance(parent, Node)
        parent.set_children(self)

    def get_parent(self):
        return self.parent

    def get_root(self):
        if self.parent == None:
            return self
        p = self.get_parent()
        while bool(p.get_parent()):
            print(bool(p.get_parent()))
            p = p.get_parent()
        return p

    def is_root(self):
        if self.parent is None:
            return True
        return False

    def is_leaf(self):
        if self.children:
            return False
        return True

    def get_level(self):
        if self.is_root():
            return 0
        return 1 + self.parent.get_level()

    def print_tree(self) -> None:
        if self.parent:
            spaces = "|   " * self.get_level()
        prefix = spaces + "|-- " if self.parent else ""
        print(prefix + str(self.data))
        if self.children:
            for child in self.children:
                child.print_tree()

The problem I am facing is, how can I create nodes and assign parents or child to them using loops? I've tryed this:

root = a = Node(0)
for i in range(1, 10):
    new_node = Node(i)
    a.set_children(new_node)
    a = new_node

root.print_tree()

which works, printing:

0
|   |-- 1
|   |   |-- 2
|   |   |   |-- 3
|   |   |   |   |-- 4
|   |   |   |   |   |-- 5
|   |   |   |   |   |   |-- 6
|   |   |   |   |   |   |   |-- 7
|   |   |   |   |   |   |   |   |-- 8
|   |   |   |   |   |   |   |   |   |-- 9

BUT, I need a way to make nodes subscriptable in such a way that if I need to assign more children to a previous node, like the node 4. I could go back to it and assign them.

Maybe building a separete class that would assign indexes to the nodes or something like it idk.

The data that I want to transform into a tree comes in a csv file and is like this:

AUG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;AUG02;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;FNS_1700 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_1701 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_3352 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_89069 (TS);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_80304 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_660 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_80079 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_80412 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_14118 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_3008 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_3007 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_81391 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_17629 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_659 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_665 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_81106 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;FNS_80996 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_80080 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;FNS_4139 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_17634 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_80081 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_2738 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_86546 (RA);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_4704 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;FNS_663 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;FNS_656 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_17637 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_3133 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_3000 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;FNS_80082 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;AUG01;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Solution

  • You can use the following function:

    def from_csv(csv):
        parent = root = Node(None) # dummy node
        curdepth = 0
        for line in csv.splitlines():
            depth = len(line) - len(line.lstrip(";"))
            while curdepth > depth:
                parent = parent.parent
                curdepth -= 1
            node = Node(line.strip(";"))
            node.set_parent(parent)
            parent = node
            curdepth += 1
        return root
    

    Here is a demo run:

    csv = """AUG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;AUG02;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;FNS_1700 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_1701 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_3352 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_89069 (TS);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_80304 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_660 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_80079 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_80412 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_14118 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_3008 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_3007 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_81391 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_17629 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_659 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_665 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_81106 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;;FNS_80996 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_80080 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;;FNS_4139 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_17634 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_80081 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_2738 (CD);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_86546 (RA);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_4704 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;FNS_663 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;;;FNS_656 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_17637 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_3133 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_3000 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;;;FNS_80082 (FU);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ;AUG01;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    """
    
    root = from_csv(csv)
    root.print_tree()
    

    This outputs:

    None
    |   |-- AUG
    |   |   |-- AUG02
    |   |   |   |-- FNS_1700 (CD)
    |   |   |   |   |-- FNS_1701 (CD)
    |   |   |   |   |   |-- FNS_3352 (FU)
    |   |   |   |   |   |-- FNS_89069 (TS)
    |   |   |   |   |   |   |-- FNS_80304 (FU)
    |   |   |   |   |   |   |-- FNS_660 (FU)
    |   |   |   |   |   |   |   |-- FNS_80079 (FU)
    |   |   |   |   |   |   |   |-- FNS_80412 (FU)
    |   |   |   |   |   |   |-- FNS_14118 (FU)
    |   |   |   |   |   |   |-- FNS_3008 (FU)
    |   |   |   |   |   |   |-- FNS_3007 (FU)
    |   |   |   |   |   |   |   |-- FNS_81391 (FU)
    |   |   |   |   |   |   |-- FNS_17629 (FU)
    |   |   |   |   |   |-- FNS_659 (FU)
    |   |   |   |   |   |   |-- FNS_665 (FU)
    |   |   |   |   |   |   |   |-- FNS_81106 (FU)
    |   |   |   |   |   |   |   |   |-- FNS_80996 (FU)
    |   |   |   |   |   |   |   |-- FNS_80080 (FU)
    |   |   |   |   |   |   |   |-- FNS_4139 (FU)
    |   |   |   |   |   |-- FNS_17634 (FU)
    |   |   |   |   |   |-- FNS_80081 (FU)
    |   |   |   |   |-- FNS_2738 (CD)
    |   |   |   |   |-- FNS_86546 (RA)
    |   |   |   |   |   |-- FNS_4704 (FU)
    |   |   |   |   |   |-- FNS_663 (FU)
    |   |   |   |   |   |   |-- FNS_656 (FU)
    |   |   |   |   |-- FNS_17637 (FU)
    |   |   |   |   |-- FNS_3133 (FU)
    |   |   |   |   |-- FNS_3000 (FU)
    |   |   |   |   |-- FNS_80082 (FU)
    |   |   |-- AUG01
    

    This includes a root with value None, because in theory it is possible that the CSV format defines multiple nodes at the top level, so it would represent a forest. The dummy root will then take those top level nodes as its children. If you expect only one top level node in the CSV input, then do:

    root = from_csv(csv).get_children()[0]
    root.parent = None
    root.print_tree()