Clarification on local and global variable scope in Python - python

I'm a bit confused regarding local and global variables referenced outside a function scope. For one problem, I had this code and it worked:
def leafSimilar(self, root1: TreeNode, root2: TreeNode) -> bool:
l1 = []
l2 = []
def traverseLeaf(root: TreeNode, sequence: list):
if not root: return
elif not root.left and not root.right: sequence.append(root.val)
else:
traverseLeaf(root.left, sequence)
traverseLeaf(root.right, sequence)
traverseLeaf(root1, l1)
traverseLeaf(root2, l2)
return l1 == l2
You can see I reference the list object outside the function scope. However, if I try this:
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
total = 0
def rightOrderTraversal(node: TreeNode):
if not node: return
elif not node.left and not node.right:
total += node.val
node.val = total
else:
rightOrderTraversal(node.right)
total += node.val
node.val = total
rightOrderTraversal(node.left)
rightOrderTraversal(root)
return root
It does not work, and the error says I reference total as a local variable when calling rightOrderTraversal. What kind of variables does Python create local references for? From these examples, it seems like Python references lists outside the function scope but not ints.
I also tried to make total global as a way to solve the issue, but that didn't work.

If you want to use a variable that is visible to any methods/sub functions within an object instance, you use the "self" to reference to the object instance, eg:
class Solution:
def my_method(self, total):
self.total = total
def my_function(total):
self.total += total
my_function(total)
s = Solution()
s.my_method(1)
print(s.total)

Related

Calculating no. of nodes in a Binary Tree in O(1)?

I am taking Data Structures and Algorithm course from Jovian. Currently on Binary Tress, but I am stuck on one question where we need to calculate no. of nodes in O(1) time.
Firstly here's how the final class TreeMap looks like:
class TreeMap:
def __init__(self):
self.root = None
def __setitem__(self, key, value):
node = find(self.root, key)
if not node:
self.root = insert(self.root, key, value)
self.root = balance_tree(self.root)
else:
update(self.root, key, value)
def __getitem__(self, key):
node = find(self.root, key)
return node.value if node else None
def __iter__(self):
return (x for x in list_all(self.root))
def __len__(self):
return size(self.root)
def display(self):
return display_keys(self.root):
Currently, it's calculating no. of nodes with the recursion method. I think we just need a counter and increment every time a node is created and we also have a hint to modify the BSTNode class. So this is how I did it:
class BSTNode:
counter = 0
def __init__(self, key, value=None):
self.key = key
self.value = value
self.left = None
self.right = None
self.parent = None
BSTNode.counter += 1
But I don't know how do I implement or use the counter in __len__ function in class TreeMap. Any help would be much appreciated.
Here is the link to Jovian Lesson: https://jovian.ai/learn/data-structures-and-algorithms-in-python/lesson/lesson-2-binary-search-trees-traversals-and-balancing

Python 3 - Using recursion in a linked list

Python 3 - I am new to coding and am finding recursion difficult. I'm making a linked list class with recursive methods for adding and removing items from the list. Right now, I am unable to remove an item if it happens to be the first item in my list. I wrote some alternative code which could remove the first item from the list if I included another parameter (previous) and another base case, but then I could only remove the first item and spent way too long trying to figure out why so I scrapped that entirely. I would appreciate a hint!
Also, I am already aware that I have getters and am not using them properly.
class Node:
"""
Represents a node in a linked list
"""
def __init__(self, data):
self._data = data
self._next = None
def get_data(self):
"""getter method for data in Node class"""
return self._data
def get_next(self):
"""getter method for next in Node class"""
return self._next
class LinkedList:
"""
A linked list implementation of the List ADT
"""
def __init__(self):
self._head = None
def get_head(self):
"""getter function for head of list"""
return self._head
def add(self, val):
""" Adds a node containing val to the linked list - helper function"""
self._head = self.recursive_add(self._head, val)
def recursive_add(self, node1, val):
""" Adds a node containing val to the linked list """
if node1 is None:
return Node(val)
else:
node1._next = self.recursive_add(node1._next, val)
return node1
def remove(self, val):
"""removed the node containing val from the linked list - helper function"""
self.recursive_remove(self._head, val)
def recursive_remove(self, node1, val):
"""
Removes the node containing val from the linked list
"""
if node1 is None:
return node1
elif node1._data == val:
return node1._next
else:
node1._next = self.recursive_remove(node1._next, val)
return node1
def main():
my_list = LinkedList()
my_list.add(13)
my_list.add(9)
my_list.add(5)
my_list.remove(9)
if __name__ == '__main__':
main()
def remove(self, val):
"""removed the node containing val from the linked list - helper function"""
if self._head and self._head._data == val:
self._head = self._head._next
return
self.recursive_remove(self._head, val)
if its at the start, the head needs to be changed.
In remove you call recursive_remove, but ignore its return value. You should use it as the (potentially different) _head reference, must like is done in the recursive method itself, where you have:
node1._next = self.recursive_remove(node1._next, val)
# ^ │
# └───────────────────────────────────┘
Note how node1._next is passed as argument, and the method's return value is the (potentially different) reference that node1._next should end up with. The same pattern should be applied in the initial call in remove:
def remove(self, val):
self._head = self.recursive_remove(self._head, val)
# ^ │
# └──────────────────────────────────┘
NB: the same pattern is used in add, where you do it correctly.

Understanding variable share between recursion

I was doing this leetcode question:(https://leetcode.com/problems/binary-tree-inorder-traversal/) in which I came up with this solution:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return None
result = []
if root.left is None and root.right is None:
result.append(root.val)
return result
return self.traverse(root,result)
def traverse(self,node,result):
if node is None:
return result
result = self.traverse(node.left,result)
result.append(node.val)
result = self.traverse(node.right,result)
return result
However I found out I actually don't need to store the results of recursion call in the variable and I can simply do this:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if root is None:
return None
result = []
if root.left is None and root.right is None:
result.append(root.val)
return result
return self.traverse(root,result)
def traverse(self,node,result):
if node is None:
return result
self.traverse(node.left,result)
result.append(node.val)
self.traverse(node.right,result)
return result
My understanding was that in each recursion call, we are passing a reference to the result variable, not copying the result variable, so what is happening is that when the recursion call gets to the left most node, it appends the value and returns to its parent node, and since we had pass by reference, the result variable in parent node already has the leftmost node added to it, so it just adds the parent node to it and keep continuing on the recursion.
Is my understanding correct or is there something else going on?
Thanks
Yes, your understanding is right.
Note: you are sharing the same code in both boxes.

__next__ and next with arguments

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.

Using a callback function to find the sum of values in a bst (without a global)

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)

Categories