implementing Search Tree in Python - python

I know there are already multiple question on this topics, but none of them has the solution to my problem.
I'm trying to build a Search Tree that has two options:
build the tree
get from the user a tree and search in it (e.g. as list, dictionary, ...)
My problem is with the second option, as it appears to be an AttributeError .
When I run my code with no given tree it works fine, but when I try it with a list an error message appears:
self.root.add(i)
AttributeError: 'NoneType' object has no attribute 'add'
My code:
import unittest
class Testfunction(unittest.TestCase):
def test(self):
init = SearchTree(['x', 'b', 'eee'])
init.add('left')
init.add('right')
init.tolist()
self.assertEqual(init.__contains__('left'),True )
self.assertEqual(init.add('xx'), None )
class Node:
def __init__(self, val):
self.value = val
self.left = None
self.right = None
def insert(self, item):
if self.value == item:
return False
elif self.value > item:
if self.left:
return self.left.insert(item)
else:
self.right = Node(item)
return True
def find(self, item):
if self.value == item:
return True
elif self.value > item:
if self.left:
return self.left.find(item)
else:
return False
else:
if self.right:
return self.right.find(item)
else:
return False
def tolist(self):
if self:
if self.left:
self.left.tolist()
if self.right:
self.right.tolist()
class SearchTree:
def __init__(self, items=None):
# if items . then should be inserted
self.items = items
self.root = None
if items:
for i in self.items:
self.root.add(i)
def __contains__(self, item):
if self.root:
return self.root.find(item)
else:
return False
def add(self, item):
if self.root:
return self.root.insert(item)
else:
self.root = Node(item)
def tolist(self):
self.root.tolist()
test = Testfunction()
test.test()

When you check the items, modify the line to use your built add.
if items:
for i in self.items:
# Instead of self.root.add(i)
self.add(i)

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

BST Tree Error: TypeError: '<=' not supported between instances of 'int' and 'Node'

I am trying to implement a BST. My code in Python is as follows:
class Node:
def __init__(self, val):
self.val = val
self.leftChild = None
self.rightChild = None
def get(self):
return self.val
def getleftChild(self):
return self.leftChild
def getrightChild(self):
return self.rightChild
def set(self, val):
self.val = val
def getChildren(self):
children = []
if self.leftChild != None:
children.append(self.leftChild)
if self.rightChild != None:
children.append(self.rightChild)
return children
class BST:
def __init__(self):
self.root = None
def setRoot(self, val):
self.root = Node(val)
def insert(self, val):
if self.root == None:
self.setRoot(val)
else:
self.insertNode(self.root, val)
def insertNode(self, CurrentNode, val):
if val <= CurrentNode.get():
if CurrentNode.leftChild:
self.insertNode(CurrentNode.leftChild, val)
else:
CurrentNode.leftChild = Node(val)
elif val < CurrentNode.get():
if CurrentNode.rightChild:
self.insertNode(CurrentNode.rightChild, val)
else:
CurrentNode.rightChild = Node(val)
new_BST = BST()
root_node = Node(2)
new_BST.setRoot(root_node)
array = [4,5,2,1,6,3]
for element in array:
new_BST.insert(element)
I keep on getting an TypeError: '<=' not supported between instances of 'int' and 'Node'
on line 41 in the insertNode section and I am not sure why. I am calling .get() which is suppose to return an int, so I am not sure why the comparison will not work.
Type annotations make it much easier to find bugs like this:
from typing import List, Optional
class Node:
def __init__(self, val: int):
self.val = val
self.leftChild: Optional['Node'] = None
self.rightChild: Optional['Node'] = None
def get(self) -> int:
return self.val
def getleftChild(self) -> Optional['Node']:
return self.leftChild
def getrightChild(self) -> Optional['Node']:
return self.rightChild
def set(self, val: int) -> None:
self.val = val
def getChildren(self) -> List['Node']:
children: List['Node'] = []
if self.leftChild is not None:
children.append(self.leftChild)
if self.rightChild is not None:
children.append(self.rightChild)
return children
class BST:
def __init__(self):
self.root: Optional[Node] = None
def setRoot(self, val: int) -> None:
self.root = Node(val)
def insert(self, val: int) -> None:
if self.root is None:
self.setRoot(val)
else:
self.insertNode(self.root, val)
def insertNode(self, CurrentNode: Node, val: int) -> None:
if val <= CurrentNode.get():
if CurrentNode.leftChild:
self.insertNode(CurrentNode.leftChild, val)
else:
CurrentNode.leftChild = Node(val)
elif val < CurrentNode.get():
if CurrentNode.rightChild:
self.insertNode(CurrentNode.rightChild, val)
else:
CurrentNode.rightChild = Node(val)
new_BST = BST()
root_node = Node(2)
new_BST.setRoot(root_node)
array = [4, 5, 2, 1, 6, 3]
for element in array:
new_BST.insert(element)
When your code is annotated, you can use static type checkers like mypy, which shows an error here:
tree.py:57: error: Argument 1 to "setRoot" of "BST" has incompatible type "Node"; expected "int"
which indeed does seem to be the problem -- you're creating a Node whose val is a Node instead of an int.
You don't see the bug until later when insertNode tries to compare the two values, which might make it hard to debug at runtime. If you declare up front that setRoot takes an int argument, though, then mypy will let you know about the mistake (and exactly where it is) even before you actually run the code.
More info on mypy and type checking here: https://mypy.readthedocs.io/en/stable/

why is "append" method not working correctly?

The "append" method is not working correctly.
It's only going inside the 'if" statement of the "append' method and not entering into the while loop.
class Node:
def __init__(self,data=None):
self.data=data
self.next=None
class Linkedlist:
def __init__(self):
self.head=Node()
def append(self,data):
new_node=Node(data)
if self.head.data is None:
self.head=new_node
cur_node=self.head
while cur_node.next is not None:
cur_node=cur_node.next
cur_node=new_node
def insert_after_node(self,prev_node,data):
new_node=Node(data)
if prev_node is None:
print("node that you have entered does not exist")
new_node.next=prev_node.next
prev_node.next=new_node
def display(self):
current=self.head
while current.next is not None:
print(current.data)
current=current.next
List=Linkedlist()
List.append("A")
List.append("B")
List.append("C")
List.insert_after_node(List.head,"g")
List.display()
Expected output: AgBC
Actual Output: A
Your .append() method simply sets a local variable to cur_node to point to new_node. This doesn't change the linked list at all; the last node in the link that was previously assigned to that local variable is not changed.
You instead want to assign to the .next attribute of the last node:
cur_node.next = new_node
The while loop in the method is working fine otherwise.
You also should not use new_node twice, when the list is empty. Exit when you don't yet have a head node with data:
if self.head.data is None:
self.head=new_node
return
Personally, I'd set self.head = None instead of self.head = Node(), then use if self.head is None:.
Next, your display function forgets to print the last element. Rather than test for current.next, check if current is None; this is where setting self.head to None for an empty list would work a lot better:
def display(self):
current = self.head
while current is not None
print(current.data)
current = current.next
I had the exact same question as you, but my implementation was different and it seems to work just fine.
First I created a node class with all its different methods:
class Node:
def __init__(self, init_data):
self.data = init_data
self.next = None
def get_data(self):
return self.data
def get_next(self):
return self.next
def set_data(self, new_data):
self.data = new_data
def set_next(self, new_next):
self.next= new_next
The I created my UnorderedList class with its methods too, including append. insert, index and pop I am still working on...
class UnorderedList:
"""
An unordered list class built from a collection of nodes.
"""
def __init__(self):
self.head = None
def is_empty(self):
return self.head == None
def add(self, item):
temp = Node(item)
temp.set_next(self.head)
self.head = temp
def size(self):
current = self.head
count = 0
while current != None:
count += 1
current = current.get_next()
return count
def search(self, item):
current = self.head
found = False
while current != None and not found:
if current.get_data() == item:
found = True
else:
current = current.get_next()
return found
def remove(self, item):
current = self.head
previous = None
found = False
while not found:
if current.get_data() == item:
found = True
else:
previous = current
current = current.get_next()
if previous == None:
self.head = current.get_next()
else:
previous.set_next(current.get_next())
def print_list(self):
current = self.head
while current != None:
print(current.data)
current = current.get_next()
def append(self, item):
new_node = Node(item)
if self.head == None:
self.head = new_node
return
current = self.head
found_last = False
while not found_last:
if current.get_next() == None:
found_last = True
current.set_next(new_node)
else:
current = current.get_next()
def insert(self, item, pos):
pass
def index(self, item):
pass
def pop(self):
pass
I realize my version of append is more verbose, but I was using the traversal method I previously used as part of my size method to make it work, and it seems to work just fine.
I found it easier to create a Node class with its methods separately as it made it easier to visualize how to set the next node and get data from the current one. Hope this helps!
FYI the node class and a lot of the UnorderedList is straight out of Problem Solving with Algorithms and Data Structures in Python by David Ranum and Brad Miller, so I know they work fine!
class Node:
def __init__(self,data=None):
self.data=data
self.next=None
class LinkedList():
def __init__(self):
self.head=Node()
def append(self,data):
new_node=Node(data)
if self.head.data is None:
self.head=new_node
else:
cur_node=self.head
while cur_node.next is not None:
cur_node=cur_node.next
cur_node.next=new_node
def insert_after_node(self,prev_node,data):
new_node=Node(data)
if prev_node is None:
print("node that you have entered does not exist")
new_node.next=prev_node.next
prev_node.next=new_node
def display(self):
current=self.head
while current.next is not None:
print(current.data)
current=current.next
print(current.data)
List=LinkedList()
List.append("A")
List.append("B")
List.append("C")
List.insert_after_node(List.head,"g")
Fixed some bugs for you:
The class name LinkedList mismatch problem
In append, if self.head.data is None, it should set self.head and then return.
In the else part of append, let the last node point to the new node by cur_node.next=new_node
In display, the last node should also be printed.

Printing elements in a linked list queue

I have the following code in python. My question is how do you print each element in the linked queue? I know that I will have to make a __repr__ or __str__ function but I am unsure how to implement it. Thanks.
class LinkedQueue :
class _Node :
def __init__(self, element, next):
self._element = element
self._next = next
def get_elements():
return self._element
def set_elements(num):
self._element = num
def __init__(self) :
self._head = None
self._tail = None
self._size = 0
def __len__(self) :
return self._size
def is_empty(self) :
return self._size == 0
def first(self) :
if self.is_empty() :
raise Empty('Queue is empty')
return self._head._element
def dequeue(self) :
if self.is_empty():
raise Empty('Queue is empty')
answer = self._head._element
self._head = self._head._next
self._size -= 1
if self.is_empty() :
self._tail = None
return answer
def enqueue(self, e) :
newest = self._Node(e,None)
if self.is_empty() :
self._head = newest
else :
self._tail._next = newest
self._tail = newest
self._size += 1
class Empty(Exception) :
pass
It depends on what you want the repr to look like, but here's one way. We give the _Node class a simple __repr__ that just returns the repr of the element, and to build the repr for a LinkedQueue instance we walk the linked list, storing the repr of each Node into a list. We can then call .join on that list to make the repr for the LinkedQueue.
class Empty(Exception):
pass
class LinkedQueue:
class _Node:
def __init__(self, element, _next=None):
self._element = element
self._next = _next
def __repr__(self):
return repr(self._element)
def __init__(self):
self._head = None
self._tail = None
self._size = 0
def __len__(self):
return self._size
def __repr__(self):
lst = []
head = self._head
while head is not None:
lst.append(repr(head))
head = head._next
return 'Queue({})'.format(", ".join(lst))
def is_empty(self):
return self._size == 0
def first(self):
if self.is_empty():
raise Empty('Queue is empty')
return self._head._element
def dequeue(self):
if self.is_empty():
raise Empty('Queue is empty')
answer = self._head._element
self._head = self._head._next
self._size -= 1
if self.is_empty():
self._tail = None
return answer
def enqueue(self, e):
newest = self._Node(e)
if self.is_empty():
self._head = newest
else:
self._tail._next = newest
self._tail = newest
self._size += 1
# test
q = LinkedQueue()
for u in 'abcd':
q.enqueue(u)
print(len(q))
print(q)
while not q.is_empty():
print(q.first(), q.dequeue())
output
1
2
3
4
Queue('a', 'b', 'c', 'd')
a a
b b
c c
d d
I got rid of the getter & setter method in Node, since you don't use them, and we don't normally write getters & setters like that in Python. See the Descriptor HowTo Guide in the docs.
FWIW, I would make Node a separate class (or get rid of it entirely), rather than nesting it in LinkedQueue. I guess it doesn't hurt to nest it, but nested class definitions aren't often used in Python.
BTW, the collections.deque is a very efficient double-ended queue. For simple queues and stacks, it's generally faster than list. But I guess this LinkedQueue class is for an exercise in implementing linked lists in Python, so collections.deque isn't currently relevant for you. ;)

Creating a list of stacks in python

so I am trying to create a list of stack objects in Python. I have first created a class Stack that has simple methods that a Stack should have. I have then created another class called Stacks. I am trying to create a list of stacks. If a stack has more than 3 elements, it creates a new stack but I get an error when I try to display the elements. Could someone point out what I might be doing wrong here please?
class Stack:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def size(self):
return len(self.items)
def printStack(self):
for item in reversed(self.items):
print (item)
class Stacks:
def __init__(self):
self.stacks = []
self.noOfStacks = 0
self.itemsOnStack = 0
def dev(self):
self.stacks.append(Stack())
# if len(self.stacks) != 0:
# self.noOfStacks += 1
def push(self, item):
if self.itemsOnStack > 3:
self.dev()
else:
self.itemsOnStack += 1
self.stacks[self.noOfStacks].push(item)
def pop(self, stackNo):
return self.stacks(noOfStacks).pop()
def size(self):
return len(self.stacks)
def printtack(self, index):
print (len(self.stacks(index)))
self.stacks(index).printStack()
stacky = Stacks()
stacky.dev()
stacky.push(3)
stacky.printtack(0)
Indexing lists in Python works by [] not (). Try
def printtack(self, index):
self.stacks[index].printStack()
One thing to note as kshikama said indexing should be done using [] not (),the other problem is using the len() method in the stack class u most override the __len__() method or as u have given use the size() method
class Stack:
def __init__(self):
self.items = []
def isEmpty(self):
return self.items == []
def push(self, item):
self.items.append(item)
def pop(self):
return self.items.pop()
def size(self):
return len(self.items)
def printStack(self):
for item in reversed(self.items):
print (item)
class Stacks:
def __init__(self):
self.stacks = []
self.noOfStacks = 0
self.itemsOnStack = 0
def dev(self):
self.stacks.append(Stack())
#if len(self.stacks) != 0:
#self.noOfStacks += 1
def push(self, item):
if self.itemsOnStack > 3:
self.dev()
else:
self.itemsOnStack += 1
self.stacks[self.noOfStacks].push(item)
def pop(self, stackNo):
return self.stacks(noOfStacks).pop()
def size(self):
return len(self.stacks)
def printtack(self, index):
print (self.stacks[index].size())
self.stacks[index].printStack()
stacky = Stacks()
stacky.dev()
stacky.push(3)
stacky.printtack(0)
OUTPUT
1
3

Categories