diff --git a/binary_search_tree/binary_search_tree.py b/binary_search_tree/binary_search_tree.py index d80d9f6282..99a3d5bd41 100644 --- a/binary_search_tree/binary_search_tree.py +++ b/binary_search_tree/binary_search_tree.py @@ -17,37 +17,168 @@ def __init__(self, value): # Insert the given value into the tree def insert(self, value): - pass + # check if the new nodes value is less than the current nodes value + if value < self.value: + # if there is no left child already here + # add the new node to the left + # create a BSTNode and encapsulate the value in it then set it to the left + if self.left == None: + self.left = BSTNode(value) + + # otherwise call insert on the left node + else: + self.left.insert(value) + + # otherwise (the new nodes value is greaterthan or equal to the current node value) + elif value >= self.value: + # if there is no right child already here + if not self.right: + # add the new node to the right + # create a BSTNode and encapsulate the value in it then set it to the right + self.right = BSTNode(value) + else: + # otherwise call insert on the right node + 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 self.value == target: + # return True + return True + # check if the target is less than the current nodes value + if self.value > target: + # if there is no left child already here + if not self.left: + return False + # return False + else: + # otherwise + return self.left.contains(target) + # return a call of contains on the left child passing in the target value + + # otherwise (the target is greater than the current nodes value) + else: + + # if there is no right child already here + 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) + + # Return the maximum value found in the tree def get_max(self): - pass + + if not self.value: + return None + # check for an empty tree + # return None + + # ---------------------------------------------- + # recursive approach + # check if there is no node to the right + elif not self.right: + return self.value + # return the nodes value + # return a call to get max on the right child + return self.right.get_max() + # ----------------------------------------------- + + # iterative aproach + + # initialise the max value + + # get a ref to the current node + + # loop while there is still a current node + # if the current value is greater than the max value, update the max value + # move on to the next right node + + # return the 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 nodes value + fn(self.value) + + # if there is a node to the left + if self.left: + self.left.for_each(fn) + # call the function on the left value + + # if there is a node to the right + if self.right: + self.right.for_each(fn) + # call the function on the right node + + + + + # Part 2 ----------------------- # Print all the values in order from low to high # Hint: Use a recursive, depth first traversal def in_order_print(self): - pass + print(self.value) + if self.left: + self.left.in_order_print() + if self.right: + self.right.in_order_print() # Print the value of every node, starting with the given node, # in an iterative breadth first traversal def bft_print(self): - pass + # Create an empty queue + # add root value to queue + # while there are items in the queue do the following + # go to first item in the queue + # if there is a left value add it + # if there is a right value add it + # print the item in the queue that you are on and dequeue it. + + newQueue = [] + newQueue.append(self) + while len(newQueue): + current = newQueue.pop(0) + print(current.value) + if current.left: + newQueue.append(current.left) + if current.right: + newQueue.append(current.right) + # Print the value of every node, starting with the given node, # in an iterative depth first traversal def dft_print(self): - pass + # Create an empty stack + # add root value to stack + # while there are items in the stack do the following + # go to last item in the stack + # if there is a left value add it + # if there is a right value add it + # print the item in the stack that you are on and dequeue it. + + stack = [] + stack.append(self) + while len(stack): + current = stack.pop() + print(current.value) + if current.right: + stack.append(current.right) + if current.left: + stack.append(current.left) + # Stretch Goals ------------------------- # Note: Research may be required @@ -76,10 +207,10 @@ def post_order_dft(self): 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() +# 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..15c4a77b01 100644 --- a/doubly_linked_list/doubly_linked_list.py +++ b/doubly_linked_list/doubly_linked_list.py @@ -27,7 +27,23 @@ def __len__(self): the old head node's previous pointer accordingly. """ def add_to_head(self, value): - pass + # wrap the input value in a node + new_node = ListNode(value) + # increment the length + self.length += 1 + # check if the linked list is empty + if not self.head and not self.tail: + # if the list is initially empty, set both head and tail to the new node + self.head = new_node + self.tail = new_node + # we have a non-empty list, add the new node to the head + else: + # set the new node's `next` to refer to the current head + new_node.next = self.head + # set the current head's 'prev' to refer to the new_node (added to make it work with DLL) + self.head.prev = new_node + # set the list's head reference to the new node + self.head = new_node """ Removes the List's current head node, making the @@ -35,7 +51,9 @@ def add_to_head(self, value): Returns the value of the removed Node. """ def remove_from_head(self): - pass + value = self.head.value + self.delete(self.head) + return value """ Wraps the given value in a ListNode and inserts it @@ -43,7 +61,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, None, None) + self.length += 1 + if not self.tail and not self.head: + self.tail = new_node + self.head = 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 +77,88 @@ def add_to_tail(self, value): Returns the value of the removed Node. """ def remove_from_tail(self): - pass + value = self.tail.value + self.delete(self.tail) + return value """ 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 + if node is self.head: + return + value = node.value + if node is self.tail: + self.remove_from_tail() + else: + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + + self.length -= 1 + self.add_to_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 + if node is self.tail: + return + value = node.value + if node is self.head: + self.remove_from_head() + else: + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + + self.length -= 1 + self.add_to_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 not self.head and not self.tail: + return + if self.head is self.tail: + self.head = None + self.tail = None + elif self.head is node: + self.head = node.next + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + elif self.tail is node: + self.tail = node.prev + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + else: + if node.prev: + node.prev.next = node.next + if node.next: + node.next.prev = node.prev + self.length -= 1 """ 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 not self.head: + return None + max_val = self.head.value + current = self.head + while current: + if current.value > max_val: + max_val = current.value + current = current.next + return max_val \ No newline at end of file diff --git a/queue/queue.py b/queue/queue.py index 0d2599ded7..f347b794e6 100644 --- a/queue/queue.py +++ b/queue/queue.py @@ -1,3 +1,8 @@ +import sys + +sys.path.append("../singly_linked_list") +from singly_linked_list import LinkedList + """ A queue is a data structure whose primary purpose is to store and return elements in First In First Out order. @@ -13,16 +18,42 @@ 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! """ -class Queue: +class QueueArray: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = [] def __len__(self): - pass + return len(self.storage) def enqueue(self, value): - pass + self.storage.insert(0, value) def dequeue(self): - pass + if len(self.storage) == 0: + return None + else: + self.storage.pop() + + + + + class Queue: + def __init__(self): + self.size = 0 + self.storage = LinkedList() + + def __len__(self): + return self.storage.size() + + def enqueue(self, value): + self.storage.add_to_tail(value) + self.size +=1 + + def dequeue(self): + if self.storage.size == 0: + return None + else: + popped = self.storage.remove_head() + self.size -=1 + return popped diff --git a/singly_linked_list/singly_linked_list.py b/singly_linked_list/singly_linked_list.py index e69de29bb2..bb79b48a80 100644 --- a/singly_linked_list/singly_linked_list.py +++ b/singly_linked_list/singly_linked_list.py @@ -0,0 +1,150 @@ +# 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): + # value that the node is holding + self.value = value + # ref to the next node in the chain + 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 node's "next_node" + """ + return self.next_node + + def set_next(self, new_next): + """ + Method to update the node's "next_node" to the new_next + """ + self.next_node = new_next + + def set_prev(self, new_prev): + """ + Method to update the node's "next_node" to the new_next + """ + self.prev_node = new_prev + + + + +# 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 is None and self.tail is None: + # set the head and tail to the new node + self.head = new_node + self.tail = new_node + # otherwise the list must have at least one item in there + 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 is None and self.tail is None: + # return None + return None + # check if there is 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 + # set head and the tail to None + self.head = None + self.tail = None + # return the stored value + 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() + + # at the end of the iteration set "self.tail" to the current_node + self.tail = current_node + # set the new tail's "next_node" to None + self.tail.set_next(None) + # return Value + return value + + def add_to_head(self, value): + # wrap the input value in a node + new_node = Node(value) + # check if the linked list is empty + if not self.head and not self.tail: + # if the list is initially empty, set both head and tail to the new node + self.head = new_node + self.tail = new_node + # we have a non-empty list, add the new node to the head + else: + # set the new node's `next` to refer to the current head + new_node.set_next(self.head) + # set the list's head reference to the new node + self.head = new_node + + def remove_head(self): + # check for empty list + if self.head is None and self.tail is None: + # 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 + # set head and the tail to None + self.head = None + self.tail = None + # return the stored value + return value + else: + # store the old head's value + value = self.head.get_value() + # set self.head to old head's next + self.head = self.head.get_next() + # return the value + return value + + + +# n1 = Node(1) +# n2 = Node(2) +# n3 = Node(3) +# n4 = Node(4) + +# n1.set_next(n2) # n1.next_node = n2 +# n1.get_value() # => 2 \ No newline at end of file diff --git a/stack/stack.py b/stack/stack.py index 6e6d660ac7..105b9a8448 100644 --- a/stack/stack.py +++ b/stack/stack.py @@ -1,3 +1,8 @@ +import sys + +sys.path.append("../singly_linked_list") +from singly_linked_list import LinkedList + """ A stack is a data structure whose primary purpose is to store and return elements in Last In First Out order. @@ -9,17 +14,53 @@ Make sure the Stack tests pass. 3. What is the difference between using an array vs. a linked list when implementing a Stack? + + In an array, each element is independent and can be accessed using it's index value. + In the case of a linked list, each node/element points to the next, previous, or maybe both nodes. """ + + class Stack: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = [] + + + def __len__(self): + return len(self.storage) + + def push(self, value): + self.storage.append(value) + self.size += 1 + + def pop(self): + if self.size == 0: + return None + else: + popped = self.storage.pop() + self.size -= 1 + return popped + + +# /Users/patrickdevincentis/Desktop/Lambda/DataStructures/Data-Structures/singly_linked_list/singly_linked_list.py + +class ArrayStack: + def __init__(self): + self.size = 0 + self.storage = LinkedList() + def __len__(self): - pass + return self.size def push(self, value): - pass + self.storage.add_to_tail(value) def pop(self): - pass + if self.size == 0: + return None + else: + popped = self.storage.remove_tail() + self.size -= 1 + return popped +