Inside a recursive class function, how do i use a class member as default argument?
I am currently using following snippet that works (python 3). height is recursive class function. Default argument that i want to use for node is self._root. In order to achieve this, i do something like following but thats a very round about way where i have to define another function height_tree. Is there a more direct approach?
# following code works #
class BTree:
def __init__(self, node=None):
self._root = node
def height_tree(self):
return self.height(self._root)
def height(self, node):
if node is not None:
height = max(self.height(node.get_left()), self.height(node.get_right())) + 1
else:
height = 0
return height
I want to do something like following but it obviously doesn't works !
def height(self, node=self._root)
# code from above follows
I however cannot use alternate trick of assigning root to node when node is None since that is one of the end condition in recursion
def height(self, node)
if node is None:
node = self._root
# this will conflict with logic in code block above#
Are there any recommended ways to handle such a situation?
You can use a sentinel object:
sentinel = object()
def height(self, node=sentinel)
if node is sentinel:
node = self._root
...
Since such object would have a unique memory address, it would never be identical to any other object (including None).
You can always create arbitrary sentinels:
SENTINEL = object()
class BTree:
def __init__(self, node=None):
self._root = node
def height_tree(self):
return self.height(self._root)
def height(self, node=SENTINEL):
if node is SENTINEL:
node = self._root
elif node is not None:
height = max(self.height(node.get_left()), self.height(node.get_right())) + 1
else:
height = 0
return height
Related
I am trying to implement a recursive function within a class declaration in Python. However, the function doesn't seem to accept parameters. If I declare a recursive function outside the Class, it works.
[A while loop][1] will also do the trick. (See "Traversing values).
I've banged my head on the keyboard enough to permit an SO post, IMHO.
class Node:
def __init__(self, value):
self.data = value
self.next = None
def traverse(self, node):
print(node.data)
if (node.next == None):
return
else:
node.traverse(node.next)
>>> a = Node('a')
>>> b = Node('b')
>>> c = Node('c')
>>> a.next = b
>>> b.next = c
>>> Node.traverse(a)
Traceback (most recent call last):
File "<pyshell#62>", line 1, in <module>
Node.traverse(a)
TypeError: traverse() missing 1 required positional argument: 'node'
[1]: https://medium.com/#kojinoshiba/data-structures-in-python-series-1-linked-lists-d9f848537b4d
A more typical implementation of this would be.
Node Code
class Node:
def __init__(self, value):
self.data = value
self.next = None
def traverse(self): # instance method (no 2nd argument node)
if self: # check if node
print(self.data) # output data
if self.next: # check for next node
self.next.traverse() # recursive call to next node if any
Test
a = Node('a')
b = Node('b')
c = Node('c')
a.next = b
b.next = c
a.traverse() # Note: Node.traverse(a) also works
# This is because
# When you call an instance method (e.g. traverse) from an
# instance object (e.g. a), Python automatically passes
# that instance object as the first argument (in addition
# to any other arguments) to the function call
# Thus: a.traverse() becomes Node.traverse(a)
Output
a
b
c
You need to make traverse a class method. Currently it says you're missing the node argument because in the line Node.traverse(a) you provided self=a and not node=a.
class Node:
def __init__(self, value):
self.data = value
self.next = None
#classmethod # This is the only addition needed :)
def traverse(self, node):
print(node.data)
if node.next is None:
return
else:
node.traverse(node.next)
In Python Cookbook, page 117, you will find this recipe which is a better solution for this problem:
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
Try to use this.
Let's say I have a simple linked list class:
class LL:
def __init__(self):
self.next = None
self.previous = None
def next(self):
return self.next
def previous(self):
return self.previous
In this case, I want to invoke previous or next, based on what is passed into a function in another class, like so:
class foo:
def __init__(self):
self.node = LL()
def move(direction):
self.node = self.node.direction
S.t. when it makes a call, it would call self.node.next() or self.node.previous().
Where move("next") would make a call to self.node.next().
This doesn't work. Nor does
self.node = self.node.direction()
How would I go about accomplishing something like this?
I'm not sure how to even formally describe this- assigning a class attribute by calling an alternate class' method via a parameter?
For your case, it would be best to keep it simple with an if statement.
def move(direction):
if direction == 'next':
self.node = self.node.next()
elif direction == 'previous':
self.node = self.node.previous()
else:
#handle the invalid input however you think is best
However, you should rename either the field or the method for both next and previous. I would recommend changing next(self) to getNext(self). Alternately, you could change self.next to self._next to indicate that the field is not intended to be accessed directly.
As #user2357112 demonstrated, your LL class itself has shadowing issues. You'll probably want to modify it to perhaps one of the following:
class LL:
def __init__(self):
self._next = None
self._previous = None
# assign a different name
def next(self):
... do something like move the node to next
... maybe change self._next on the way...
return self._next
# Or make use of property decorator
#property
def previous(self):
... do something like move the node to previous
return self._previous
#previous.setter
def previous(self, value):
... do something to change self._previous
Somehow I still don't think that's what you're after, but to answer the spirit of your question, you can use a dict like a switch statement in your foo() class like so:
class foo:
def __init__(self):
self.node = LL()
def move(direction):
_moves = {
'next': self.node.next,
'previous': self.node.previous,
'nowhere': self.node.nowhere
}
# if you're invoking a method
_moves.get(direction)()
# if you're just referencing an attribute
_moves.get(direction)
Conceptually I think it's more important to think about what you're trying to achieve with LL and foo before proceeding though.
I tried to write a code that protects the pointer of a linked list. The setter should point only to an instance which belongs to the same class. Usually isinstance() or type() work after the class is defined. But the setter is a method inside that class, hence the class is not fully defined yet.
I have no doubt that type() gives out error. But I wonder why isinstance(instance, class) yields False if calling from another class.
#define the Node class
class Node:
"""Class Node contain only data and a next pointer variables."""
def __init__(self, Data = None):
self.data = Data
self.__next = None
def getNext(self):
"""The getter"""
return self.__next
def setNext(self, NextNode):
"""The setter"""
#if possible check if NewNode is an instance of Node before pointing to it.
#I have tried "isinstance(), type(), etc. but failed.
if isinstance(NextNode, Node):
self.__next = NextNode
else:
print('The Next pointer should point to "Node" only.')
Then check if isinstance is working
ANode = Node((1,2,3))
BNode = Node((5,6))
ANode.setNext(BNode)
print(BNode)
print(ANode.getNext())
Both prints yield the same address
<__main__.Node object at 0x112162828>
<__main__.Node object at 0x112162828>
So everything looks fine. But When I call from the LinkedList class, printed below, the isinstance yields False, as seen from my warning.
class LinkedList:
"""This class is the Linked List of Node."""
def __init__(self, FirstNode = None):
"""Initialize by creating an empty List. __First hold the pointer that point to the first node."""
if FirstNode is None:
self.__first = Node(None)
self.__last = self.__first
elif type(FirstNode) is Node:
self.__first = FirstNode
self.__last = self.__first
else:
print('To create a linked-list enter nothing or a Node.')
raise TypeError
def getFirst(self):
return self.__first
def append(self, NewLastNode):
"""Add LastNode to the end of the list."""
if not isinstance(NewLastNode,Node):
raise TypeError
OldLast = self.__last
OldLast.setNext(NewLastNode)
self.__last = NewLastNode
NewLastNode.setNext(None)
def removeFirstNode(self):
"""Remove the first node (when the buffer is full)."""
OldFirst = self.__first
NewFirst = OldFirst.getNext()
if NewFirst == None:
# just clear data
OldFirst.data = None
else:
self.__first = NewFirst
del OldFirst
Then I create an instance of the LinkedList class
LL = LinkedList(Node((1,2)))
NewNode = Node((2.0, 3.0, -10))
Surely isinstance works fine here
isinstance(NewNode,Node)
yields True, but
LL.append(NewNode)
which will call Node.setNext() and there the isinstance() yields False as the else in Node.setNext() prints out
The Next pointer should point to "Node" only.
The piece of code that's giving you the error is this:
NewLastNode.setNext(None)
because you're trying to set the next element to an object that it's not a Node instance, hence the error.
I think you could simply remove this statement, as your self.__last is now correctly pointing to your NewLastNode. So your code will become:
def append(self, NewLastNode):
"""Add LastNode to the end of the list."""
if not isinstance(NewLastNode,Node):
raise TypeError
OldLast = self.__last
OldLast.setNext(NewLastNode)
self.__last = NewLastNode
I'm writing an implementation of doubly linked lists. In order to traverse the list, I'm using something like:
class Node:
""" A node in our linked list """
def __init__(self, value: Any, next: Union['Node', None] =None,
previous: Union['Node', None] =None) -> None:
self.value = value
self.next = next
self.previous = previous
...
def __next__(self, direction: int =1) -> Union['Node', None]:
if direction == 1:
return self.get_next()
else:
return self.get_previous()
...
where get_next and get_previous are just getters of self.next and self.previous.
However, PyCharm yells at me for trying to call next as
next(some_node, direction=-1). What's the proper way to do this?
Besides __iter__ there is also __reversed__. Both are required to return iterators. The __next__ method should be implemented on iterators (not on node-classes). Note that all magic methods (when called by a function like next instead of directly invoked) need to implement the expected arguments not more - not less.
For example a doubly linked list could just implement __iter__ and __reversed__ and rely on next and previous attribute of the Node:
class Node(object):
def __init__(self, val, nxt, prv):
self.val = val
self.nxt = nxt
self.prv = prv
class DoublyLinkedList(object):
def __init__(self, base=None, last=None):
self.base = base
self.last = last
def prepend(self, val):
new = Node(val, self.base, None)
if self.base is None:
self.base = new
self.last = new
else:
self.base.prv = new
self.base = new
def append(self, val):
new = Node(val, None, self.last)
if self.last is None:
self.base = new
self.last = new
else:
self.last.nxt = new
self.last = new
def __iter__(self):
current = self.base
while current is not None:
yield current
current = current.nxt
def __reversed__(self):
current = self.last
while current is not None:
yield current
current = current.prv
For example:
dl = DoublyLinkedList()
dl.prepend(10)
dl.prepend(20)
dl.prepend(30)
for i in dl:
print(i.val)
gives:
30
20
10
similar for reversed:
for i in reversed(dl):
print(i.val)
# prints:
10
20
30
__next__ is part of the iterator protocol and should be used as described in said protocol, doing otherwise only make problems with the rest python.
In your case just rename the function to simple next and use as some_node.next(-1), though I would change the direction argument to a boolean, as that is how you use it, and its name too. Like this for example
class None:
...
def next(self, forward:bool=True) -> Union['Node', None]:
if forward:
return self.get_next()
else:
return self.get_previous()
and use as some_node.next(), some_node.next(False) or even some_node.next(0) (using 0 instead of False for the same effect)
The extra argument to next is a default value, and __next__ doesn't take any extra arguments. Python doesn't have any sort of two-way iterators. If your interface is not exactly the same as for i in obj:, then you should write your own.
I've got a binary search tree full of objects. I'm traversing the tree using a callback function that adds a property of all the objects to a global variable. I've got this working, but I'd like to find a way to accomplish this without using a global.
Here's the relevant code:
TOTAL_AGE = 0.0
class Node(object):
def __init__(self, data):
self.left = None
self.right = None
self.data = data
class Tree(object):
def __init__(self):
self.root = None
self.size = 0
def traverse(self, callback):
self._traverse(callback, self.root)
def _traverse(self, callback, node):
if node is None:
return
self._traverse(callback, node.left)
callback(node.data)
self._traverse(callback, node.right)
def add_ages(tree):
tree.traverse(callback)
def callback(student):
global TOTAL_AGE
TOTAL_AGE += student.age
def main():
tree = bst.Tree()
add_ages(tree)
print TOTAL_AGE
This is admittedly for an assignment, which requires that I use the current traverse function and not a different implementation. That's mainly my issue though because I don't see a way to do this without using a global or modifying traverse().
Thanks in advance for any help.
You could pass a method of a class instance as callback so that you can keep track of the state in the instance:
class Count(object):
def __init__(self):
self.total_age = 0
def callback(self, student):
self.total_age += student.age
And then instantiate Count and pass its callback method to the Tree:
count = Count()
tree.traverse(count.callback)