diff --git a/binary_search_tree/binary_search_tree.py b/binary_search_tree/binary_search_tree.py index d80d9f6282..e569d6694f 100644 --- a/binary_search_tree/binary_search_tree.py +++ b/binary_search_tree/binary_search_tree.py @@ -17,20 +17,106 @@ def __init__(self, value): # Insert the given value into the tree def insert(self, value): - pass + # check if value is int + value_type = type(value) + if value_type != int and value_type != float: + print(f"Error: Insert type is {value_type}") + print("type must be 'int'") + return + # check if the value is Less than the value of the current node's value + if value < self.value: + # if there's no left child already there + if not self.left: + # add the new node to the left + left_node = BSTNode(value) + # create a BSTNode and encapsulate the value in it and then set it to the Left node + self.left = left_node + # otherwise recursively call insert on left node + else: + self.left.insert(value) + # otherwise the value is Greater than or Equal to the value of the current node + elif value >= self.value: + # if there's no right child already there + if not self.right: + # add the new node to the right + right_node = BSTNode(value) + # create a BSTNode and encapsulate the value in it and then set it to the Right node + self.right = right_node + # otherwise recursively call insert on right node + else: + self.right.insert(value) + # Return True if the tree contains the value # False if it does not def contains(self, target): - pass + # if the value of the current node matches the target + if target == self.value: + # return True + return True + # check if the target is Less than the value of the current node's value + elif target < self.value: + # if there's no left child already there + if not self.left: + # return False + return False + # otherwise + else: + # return a call of 'contains' on the Left child passing in the target value + return self.left.contains(target) + # otherwise the target is Greater than to the value of the current node + elif target > self.value: + # if there's no Right child already there + if not self.right: + # return False + return False + # otherwise + else: + # return a call of 'contains' on the Right child passing in the target value + return self.right.contains(target) + else: + print(f"Could not search tree for {target}") + # Return the maximum value found in the tree def get_max(self): - pass + # check for an empty Tree + if not self.value: + # return None + print("empty tree") + return None + + # ** EASY - Recursive ** + # check if there is no node to the Right + if not self.right: + # if True return value + return self.value + # otherwise return a call to get_max on the Right child + else: + return self.right.get_max() + + # ** ITERATIVE approach ** + # initialize the max value //self's value + # get a ref to the current node + # Loop while there is still a Node + # if the current value is greater than the max value, update the max value + # move onto the next right node + + # return max value # Call the function `fn` on the value of each node def for_each(self, fn): - pass + # call the function passing in the current node's value + fn(self.value) + # if there is a node to the Left + if self.left: + # call the function on the Left value + self.left.for_each(fn) + # if there is a node to the Right + if self.right: + # call the function on the Right value + self.right.for_each(fn) + # Part 2 ----------------------- @@ -63,7 +149,7 @@ def post_order_dft(self): """ This code is necessary for testing the `print` methods """ -bst = BSTNode(1) +bst = BSTNode(4) bst.insert(8) bst.insert(5) @@ -71,15 +157,20 @@ def post_order_dft(self): bst.insert(6) bst.insert(3) bst.insert(4) -bst.insert(2) - -bst.bft_print() -bst.dft_print() - -print("elegant methods") -print("pre order") -bst.pre_order_dft() -print("in order") -bst.in_order_dft() -print("post order") -bst.post_order_dft() +bst.insert("2") + +print(bst.contains(6)) +print(bst.contains(56)) + +print(bst.get_max()) + +# bst.bft_print() +# bst.dft_print() + +# print("elegant methods") +# print("pre order") +# bst.pre_order_dft() +# print("in order") +# bst.in_order_dft() +# print("post order") +# bst.post_order_dft() diff --git a/doubly_linked_list/doubly_linked_list.py b/doubly_linked_list/doubly_linked_list.py index 6f91b43a9b..bfdf3cde12 100644 --- a/doubly_linked_list/doubly_linked_list.py +++ b/doubly_linked_list/doubly_linked_list.py @@ -7,6 +7,30 @@ def __init__(self, value, prev=None, next=None): self.prev = prev self.value = value self.next = next + + def get_value(self): + return self.value + + def next_node(self): + if self.next: + return self.next + else: + return None + + def prev_node(self): + if self.prev: + return self.prev + else: + return None + + def delete(self): + if self.prev: + self.prev.next = self.next + if self.next: + self.next.prev = self.prev + self.value = None + self.next = None + self.prev = None """ Our doubly-linked list class. It holds references to @@ -27,7 +51,16 @@ def __len__(self): the old head node's previous pointer accordingly. """ def add_to_head(self, value): - pass + new_node = ListNode(value) + self.length += 1 + if not self.head and not self.tail: + self.head = new_node + self.tail = new_node + else: + new_node.next = self.head + # set the current head's prev node to the new node + self.head.prev = new_node + self.head = new_node """ Removes the List's current head node, making the @@ -35,7 +68,19 @@ def add_to_head(self, value): Returns the value of the removed Node. """ def remove_from_head(self): - pass + if not self.head: + return "No head in List" + else: + self.length -= 1 + # get the old head into a ValueError + removed_head = self.head.get_value() + # get the next head + new_head = self.head.next_node() + # remove the current head + self.head.delete() + # set the current head's next as the new head + self.head = new_head + return removed_head """ Wraps the given value in a ListNode and inserts it @@ -43,7 +88,15 @@ def remove_from_head(self): the old tail node's next pointer accordingly. """ def add_to_tail(self, value): - pass + new_node = ListNode(value) + self.length += 1 + if not self.head and not self.tail: + self.head = new_node + self.tail = new_node + else: + new_node.prev = self.tail + self.tail.next = new_node + self.tail = new_node """ Removes the List's current tail node, making the @@ -51,32 +104,86 @@ def add_to_tail(self, value): Returns the value of the removed Node. """ def remove_from_tail(self): - pass - + if not self.tail: + return "No tail in List" + else: + self.length -= 1 + removed_tail = self.tail.get_value() + new_tail = self.tail.prev_node() + self.tail.delete() + self.tail = new_tail + return removed_tail """ Removes the input node from its current spot in the List and inserts it as the new head node of the List. """ def move_to_front(self, node): - pass + new_head_value = node.value + if node == self.head: + return + elif node == self.tail: + self.remove_from_tail() + self.add_to_head(new_head_value) + else: + self.delete(node) + self.add_to_head(new_head_value) """ Removes the input node from its current spot in the List and inserts it as the new tail node of the List. """ def move_to_end(self, node): - pass + new_tail_value = node.value + if node == self.tail: + return + elif node == self.head: + self.remove_from_head() + self.add_to_tail(new_tail_value) + else: + self.delete(node) + self.add_to_tail(new_tail_value) """ Deletes the input node from the List, preserving the order of the other elements of the List. """ def delete(self, node): - pass + if self.length == 0: + return + elif self.length == 1: + self.length -= 1 + node.delete() + self.head = None + self.tail = None + elif node == self.head: + self.remove_from_head() + elif node == self.tail: + self.remove_from_tail() + else: + self.length -= 1 + node.delete() + + def get_all_nodes(self): + if not self.head and not self.tail: + print("No nodes in List") + else: + current_node = self.head + while current_node: + print(f"{current_node.value.value}") + current_node = current_node.next_node() + print(self.tail.get_value().value) """ Finds and returns the maximum value of all the nodes in the List. """ def get_max(self): - pass \ No newline at end of file + if self.length == 0: + return None + max_value = self.head.value + current_node = self.head + while current_node: + if current_node.get_value() > max_value: + max_value = current_node.get_value() + current_node = current_node.next_node() + return max_value diff --git a/queue/queue.py b/queue/queue.py index 0d2599ded7..45e89a32dd 100644 --- a/queue/queue.py +++ b/queue/queue.py @@ -13,16 +13,25 @@ Stretch: What if you could only use instances of your Stack class to implement the Queue? What would that look like? How many Stacks would you need? Try it! """ + +from singly_linked_list import Node, LinkedList + class Queue: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = LinkedList() def __len__(self): - pass + return self.size def enqueue(self, value): - pass + self.storage.add_to_tail(value) + self.size += 1 def dequeue(self): - pass + if self.size == 0: + return + else: + dequeued_node = self.storage.remove_head() + self.size -= 1 + return dequeued_node diff --git a/queue/singly_linked_list.py b/queue/singly_linked_list.py new file mode 100644 index 0000000000..24e3891c76 --- /dev/null +++ b/queue/singly_linked_list.py @@ -0,0 +1,117 @@ +# linear data structure made up of nodes and refs to the next node + +# lets make some node class +class Node: + def __init__(self, value, next_node = None): + self.value = value + self.next_node = next_node + + def get_value(self): + """ + Method to get the value of a node + """ + return self.value + + def get_next(self): + """ + Method to get the next node + """ + return self.next_node + + def set_next(self, new_next): + """ + Method to update the node's "next_node" + """ + self.next_node = new_next + + +# now lets think of how we can make nodes interact in a way that consolidates their pieces together + +# lets make a LinkedList class +# think of the idea of having a head and a tail like a snake +# where the snake can grow based upon having more links in it + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_tail(self, value): + # wrap the value in a new Node + new_node = Node(value) + + # check if the linked list is empty + if self.head == None and self.tail == None: + # set the head and tail to the new node + self.head = new_node + self.tail = new_node + # otherwise the list must have a head and tail + else: + # update the last node's "next_node" to the new node + self.tail.set_next(new_node) # (Last node in chain).next_node = new_node + # update the "self.tail" to point to the new node that we just added + self.tail = new_node + + def remove_tail(self): + """ + Remove the last node in the chain and return its value + """ + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + # check if there's only one node + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + # otherwise + else: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # we need to set the "self.tail" to the second to last Node + # we can only do this by traversing the whole list from beginning to end + + # starting from the head + current_node = self.head + # keep iterating until the node after "current_node" is the tail + while current_node.get_next() != self.tail: + # keep looping + current_node = current_node.get_next() + + # set the "self.tail" to the current_node + self.tail = current_node + + # set the new tail's "next_node" to None + self.tail.next_node = None + + # return Value + return value + + def remove_head(self): + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.head.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + else: + # store the head's old value + value = self.head.get_value() + # set self.head to the old head's next + self.head = self.head.get_next() + # retun the value + return value + diff --git a/singly_linked_list/singly_linked_list.py b/singly_linked_list/singly_linked_list.py index e69de29bb2..47ca714c64 100644 --- a/singly_linked_list/singly_linked_list.py +++ b/singly_linked_list/singly_linked_list.py @@ -0,0 +1,117 @@ +# linear data structure made up of nodes and refs to the next node + +# lets make some node class +class Node: + def __init__(self, value, next_node = None): + self.value = value + self.next_node = next_node + + def get_value(self): + """ + Method to get the value of a node + """ + return self.value + + def get_next(self): + """ + Method to get the next node + """ + return self.next_node + + def set_next(self, new_next): + """ + Method to update the node's "next_node" + """ + self.next_node = new_next + + +# now lets think of how we can make nodes interact in a way that consolidates their pieces together + +# lets make a LinkedList class +# think of the idea of having a head and a tail like a snake +# where the snake can grow based upon having more links in it + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_tail(self, value): + # wrap the value in a new Node + new_node = Node(value) + + # check if the linked list is empty + if self.head == None and self.tail == None: + # set the head and tail to the new node + self.head = new_node + self.tail = new_node + # otherwise the list must have a head and tail + else: + # update the last node's "next_node" to the new node + self.tail.set_next(new_node) # (Last node in chain).next_node = new_node + # update the "self.tail" to point to the new node that we just added + self.tail = new_node + + def remove_tail(self): + """ + Remove the last node in the chain and return its value + """ + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + # check if there's only one node + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + # otherwise + else: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # we need to set the "self.tail" to the second to last Node + # we can only do this by traversing the whole list from beginning to end + + # starting from the head + current_node = self.head + # keep iterating until the node after "current_node" is the tail + while current_node.get_next() != self.tail: + # keep looping + current_node = current_node.get_next() + + # set the "self.tail" to the current_node + self.tail = current_node + + # set the new tail's "next_node" to None + self.tail.next_node = None + + # return Value + return value + + def remove_head(self): + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.head.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + else: + # store the head's old value + value = self.head.get_value() + # set self.head to the old head's next + self.head = self.head.get_next() + # retun the value + return value + diff --git a/stack/singly_linked_list.py b/stack/singly_linked_list.py new file mode 100644 index 0000000000..24e3891c76 --- /dev/null +++ b/stack/singly_linked_list.py @@ -0,0 +1,117 @@ +# linear data structure made up of nodes and refs to the next node + +# lets make some node class +class Node: + def __init__(self, value, next_node = None): + self.value = value + self.next_node = next_node + + def get_value(self): + """ + Method to get the value of a node + """ + return self.value + + def get_next(self): + """ + Method to get the next node + """ + return self.next_node + + def set_next(self, new_next): + """ + Method to update the node's "next_node" + """ + self.next_node = new_next + + +# now lets think of how we can make nodes interact in a way that consolidates their pieces together + +# lets make a LinkedList class +# think of the idea of having a head and a tail like a snake +# where the snake can grow based upon having more links in it + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_tail(self, value): + # wrap the value in a new Node + new_node = Node(value) + + # check if the linked list is empty + if self.head == None and self.tail == None: + # set the head and tail to the new node + self.head = new_node + self.tail = new_node + # otherwise the list must have a head and tail + else: + # update the last node's "next_node" to the new node + self.tail.set_next(new_node) # (Last node in chain).next_node = new_node + # update the "self.tail" to point to the new node that we just added + self.tail = new_node + + def remove_tail(self): + """ + Remove the last node in the chain and return its value + """ + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + # check if there's only one node + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + # otherwise + else: + # store the value of the node that we are going to remove + value = self.tail.get_value() + # we need to set the "self.tail" to the second to last Node + # we can only do this by traversing the whole list from beginning to end + + # starting from the head + current_node = self.head + # keep iterating until the node after "current_node" is the tail + while current_node.get_next() != self.tail: + # keep looping + current_node = current_node.get_next() + + # set the "self.tail" to the current_node + self.tail = current_node + + # set the new tail's "next_node" to None + self.tail.next_node = None + + # return Value + return value + + def remove_head(self): + # check for empty list + if self.head == None and self.tail == None: + # if true return None + return None + if self.head == self.tail: + # store the value of the node that we are going to remove + value = self.head.get_value() + # remove the node + # setting the head and the tail to None + self.head = None + self.tail = None + # return the stored value of the Node + return value + else: + # store the head's old value + value = self.head.get_value() + # set self.head to the old head's next + self.head = self.head.get_next() + # retun the value + return value + diff --git a/stack/stack.py b/stack/stack.py index 6e6d660ac7..ebc3d5aa11 100644 --- a/stack/stack.py +++ b/stack/stack.py @@ -1,7 +1,6 @@ """ A stack is a data structure whose primary purpose is to store and return elements in Last In First Out order. - 1. Implement the Stack class using an array as the underlying storage structure. Make sure the Stack tests pass. 2. Re-implement the Stack class, this time using the linked list implementation @@ -10,16 +9,34 @@ 3. What is the difference between using an array vs. a linked list when implementing a Stack? """ + +from singly_linked_list import Node, LinkedList + class Stack: def __init__(self): self.size = 0 - # self.storage = ? - - def __len__(self): - pass + self.storage = LinkedList() def push(self, value): - pass + self.storage.add_to_tail(value) + self.size += 1 def pop(self): - pass + if self.size == 0: + return + else: + removed_node = self.storage.remove_tail() + self.size -= 1 + return removed_node + + def __len__(self): + return self.size + +# stack = Stack() + +# stack.push(4) +# stack.push(45) +# stack.push(5) +# print(stack.__len__()) +# stack.pop() +# print(stack.__len__())