2

I want to create a data structure in Python, but since I'm very C oriented. I need a little bit of help.

In general, I want to create a Node class which will contain the data, a pointer to a sibling, pointers to the children and a pointer to the parent.

this is a way to think of the Node class:

                                 NODE
            /            /         ...  \                   \
       child_node1 - child_node2 - ... - child_node(N-1) - child_nodeN

What I'm struggling with so far is: I want to overload the '+' operator for the Node class so I can do this:

node1 = Node("data1")
node2 = Node("data2", 3)
node1 = node1 + node2

So basically make the 2 nodes, siblings.

Here's my code:

class Node:
def __init__(self, data = None, numberOfChildren = 0):
    '''
    Creates a new Node with *numberOfChildren* children. 
    By default it will be set to 0, meaning it will only create the root of the tree.
    No children whatsoever.
    '''
    self.__sibling_count = 0
    self.__parent = Node()
    self.__sibling = Node()
    self.__data = data

    self.__children = []
    if numberOfChildren != 0:
        '''
        The Node has children and we need to initialize them
        '''
        for i in range(numberOfChildren):
            self.__children[i] = Node()

def getParent(self):
    return self.__parent


def getData(self):
    return self.__data

def getChild(self, i):
    '''
    Returns the ith child of the current *Node*.
    '''
    return self.__children[i]


def __add__(self, other):
    '''
    Overloads the *+* function so that *Node* objects can be added.
    The 2 merged *Node* elements will now be siblings.
    ex. node1  = Node()
        node2 = Node()
        node1 = node1 + node2
    '''
    if self.__sibling_count == 0:
        self.__sibling = other
        self.__sibling_count += 1
    return self

But when I try to add 2 nodes like this:

node1 = Node()
node2 = Node()
node1 = node1 + node2

I get a RuntimeError: maximum recursion depth exceeded. Why is this happening?

Pavlos Panteliadis
  • 1,495
  • 1
  • 15
  • 25
  • BTW, `for i in range(numberOfChildren): self.__children[i] = Node()` won't work either. Not only does it incur the same recursion problem that you get with `parent`, you're also attempting to index an empty list. – PM 2Ring Sep 01 '16 at 12:51

2 Answers2

2

Operator overriding in Python is allowed, but using the + operator for something that is not concatenation or summing is frowned upon. A more pythonic implementation would be something like this untested fragment:

class Node(object):
    def __init__(self, parent=None):
        self.set_parent(parent)
        self.children = set()

    def set_parent(self, parent):
        if self.parent and self.parent is not parent:
            self.parent.children.remove(self)
        self.parent = parent

    def siblings(self):
        if self.parent is None:
            return []
        return [_ for _ in self.parent.children if _ is not self]

    def add_child(self, node):
        self.children.add(node)
        node.set_parent(self)

    def add_sibling(self, node):
        assert self.parent, "root node can't have siblings"
        self.parent.add_child(node)

... and so on. Off course you can override the + operator to perform add_sibling, but the gist of it is to rely heavily on the native collections.

If you want to create a note with 3 children, it would be:

root = Node()
nodes = [Node(parent=root) for _ in range(3)]
Paulo Scardine
  • 73,447
  • 11
  • 124
  • 153
  • +1 This was very helpful in moving from a binary tree to a tree with theoretically unlimited children. I ended up using a list for the children instead of a set because after implementing a depth-first search, I was getting strange results due to sets not maintaining order of added children. Not sure if this is expected or not. Thanks! – Englehas Aug 29 '18 at 18:36
  • @Englehas indeed, python `set` is an unordered collection. AFAIK there is no ordered set in the standard library but you may want to read https://stackoverflow.com/questions/1653970/does-python-have-an-ordered-set – Paulo Scardine Aug 29 '18 at 20:34
0

Python recursion is limited to prevent stack overflowing and infinite recursion. There for recursion without break conditions or counter will be stopped after some-many iterations.

Stop creating any more nodes after a number of levels, otherwise python will stop you. You are activating __init_ in the first Node, then in every one of it's children and so on. This never stops, and trigger this run-time error.

See that as an estimation to how far you can go:

>>> def f(): f()
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
  File "<stdin>", line 1, in f
  ... another 995 times the same
  File "<stdin>", line 1, in f
RecursionError: maximum recursion depth exceeded

You can change __init__ to this:

def __init__(self, levels = 5, numberOfChildren = 0, data = None):
    '''
    Creates a new Node with *numberOfChildren* children. 
    By default it will be set to 0, meaning it will only create the root of the tree.
    No children whatsoever.
    '''
    self.__sibling_count = 0
    # these will only produce **new** nodes. I commented them.
    # self.__parent = Node()
    # self.__sibling = Node()
    self.__data = data

    self.__children = []
    if numberOfChildren != 0 and levels > 0:
        '''
        The Node has children and we need to initialize them
        '''
        for i in range(numberOfChildren):
            self.__children[i] = Node(levels - 1, numberOfChildren)
Uriel
  • 15,579
  • 6
  • 25
  • 46
  • Ok. Shouldn't there be an alternative to this? I mean, if I were to do this implementation in C++ or Java I would have just declared the `parent` and `sibling` attributes as `Node` and that would be it. – Pavlos Panteliadis Sep 01 '16 at 12:35
  • When you initialize every child as `Node` you cause them to run **their** `__init__` function, and so on. The 1st child of the 1000th recursion will be created before the 2nd of the first. – Uriel Sep 01 '16 at 12:39
  • Add a variable named 'level' to the `__init__` function, and decrease level by one with every recursive creation of `Node`. – Uriel Sep 01 '16 at 12:40
  • can you post the full code of your solution? I can see where you going, but I can't figure out any other way to 'declare' that one variable will be of type `Node` only. if I type `self.__parent = None`, etc I get another error – Pavlos Panteliadis Sep 01 '16 at 12:45
  • Do not bother referencing to parent and sibling. To sibling is useless, since you iterate them all together. To parent is beyond complicated with python, since it has no pointers. – Uriel Sep 01 '16 at 12:46
  • 1
    One can argue that every variable in Python is like a C pointer (in fact, they are more like a C++ reference). – Paulo Scardine Sep 01 '16 at 12:48
  • And remember - you don't have to declare the type on creation. You can use the same variable to store integer, string, and then self created object all on the same program. – Uriel Sep 01 '16 at 12:48
  • Correct @PauloScardine, but using assigned memory pointers (like memory view) in python is not ideal. – Uriel Sep 01 '16 at 12:49
  • About the recursion limit, it is 999 by default, see [setrecursionlimit()](https://docs.python.org/2/library/sys.html#sys.setrecursionlimit). Every recursive algorithm can be converted to an iterative form, unfortunately I don't have time to post a full answer right now - but rest assured the Python version is way simpler than the C version. – Paulo Scardine Sep 01 '16 at 12:53