diff --git a/.gitignore b/.gitignore index c1c01224ed..023f082e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store node_modules *.pyc -.vscode/ \ No newline at end of file +.vscode/ + +env/* \ No newline at end of file diff --git a/binary_search_tree/binary_search_tree.py b/binary_search_tree/binary_search_tree.py index d80d9f6282..79d8fb5a45 100644 --- a/binary_search_tree/binary_search_tree.py +++ b/binary_search_tree/binary_search_tree.py @@ -1,7 +1,7 @@ """ -Binary search trees are a data structure that enforce an ordering over -the data they store. That ordering in turn makes it a lot more efficient -at searching for a particular piece of data in the tree. +Binary search trees are a data structure that enforce an ordering over +the data they store. That ordering in turn makes it a lot more efficient +at searching for a particular piece of data in the tree. This part of the project comprises two days: 1. Implement the methods `insert`, `contains`, `get_max`, and `for_each` @@ -9,6 +9,9 @@ 2. Implement the `in_order_print`, `bft_print`, and `dft_print` methods on the BSTNode class. """ +from queue import Queue + + class BSTNode: def __init__(self, value): self.value = value @@ -17,42 +20,137 @@ def __init__(self, value): # Insert the given value into the tree def insert(self, value): - pass + + if value < self.value: + if self.left is None: + self.left = BSTNode(value) + else: + self.left.insert(value) + + if value >= self.value: + if self.right is None: + self.right = BSTNode(value) + else: + self.right.insert(value) # Return True if the tree contains the value # False if it does not + def contains(self, target): - pass + + if self.value == target: + print(f"Made it with {target}") + return True + elif target < self.value and self.left: + self.left.contains(target) + elif target > self.value and self.right: + self.right.contains(target) + + return False + + # def contains(self, target): + # # check if the node is == target + # if self.value == target: + # # if true return true + # return True + # # otherwise check if target is < node value + # elif target < self.value: + # # if left is None, target doesn't exist in tree, return false + # if self.left == None: + # return False + # # if left value is = target return true + # elif self.left.value == target: + # return True + # # otherwise move down left, call contains on left node + # else: + # self.left.contains(target) + # # otherwise check if target is >= node value + # elif target > self.value: + # # if right is None, target doesn't exist in tree, return false + # if self.right == None: + # return False + # # if right value is = target return true + # elif self.right.value == target: + # return True + # # otherwise move down right, call contains on right node + # else: + # self.right.contains(target) # Return the maximum value found in the tree + def get_max(self): - pass + current_max = self.value + current_node = self + + while current_node is not None: + current_max = current_node.value + current_node = current_node.right + + return current_max # Call the function `fn` on the value of each node + def for_each(self, fn): - pass + fn(self.value) + + if self.left: + self.left.for_each(fn) + if self.right: + self.right.for_each(fn) # 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 + # Recursive: place print statement in between recursive calls that explore left and right subtrees + + if self.left: + self.left.in_order_print() + print(self.value) + 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 + q = Queue() + if self is None: + return + q.put(self) + while q.empty() is not True: + current = q.get() + print(current.value) + if current.left: + + q.put(current.left) + if current.right: + + q.put(current.right) # Print the value of every node, starting with the given node, # in an iterative depth first traversal + def dft_print(self): - pass + stack = [] + stack.append(self) + + while len(stack) is not 0: + 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 # Print Pre-order recursive DFT + def pre_order_dft(self): pass @@ -60,26 +158,19 @@ def pre_order_dft(self): def post_order_dft(self): pass + """ This code is necessary for testing the `print` methods """ -bst = BSTNode(1) - -bst.insert(8) -bst.insert(5) -bst.insert(7) -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() +tree = BSTNode(1) +tree.insert(8) +tree.insert(5) +tree.insert(7) +tree.insert(6) +tree.insert(3) +tree.insert(4) +tree.insert(2) + +# tree.in_order_print() +# tree.bft_print() +tree.dft_print() diff --git a/binary_search_tree/test_binary_search_tree.py b/binary_search_tree/test_binary_search_tree.py index 0a0cee5911..d2b5da51aa 100644 --- a/binary_search_tree/test_binary_search_tree.py +++ b/binary_search_tree/test_binary_search_tree.py @@ -4,6 +4,7 @@ import io from binary_search_tree import BSTNode + class BinarySearchTreeTests(unittest.TestCase): def setUp(self): self.bst = BSTNode(5) @@ -15,7 +16,7 @@ def test_insert(self): self.bst.insert(6) self.assertEqual(self.bst.left.right.value, 3) self.assertEqual(self.bst.right.left.value, 6) - + def test_handle_dupe_insert(self): self.bst2 = BSTNode(1) self.bst2.insert(1) @@ -38,7 +39,7 @@ def test_get_max(self): def test_for_each(self): arr = [] - cb = lambda x: arr.append(x) + def cb(x): return arr.append(x) v1 = random.randint(1, 101) v2 = random.randint(1, 101) @@ -106,5 +107,6 @@ def test_print_traversals(self): sys.stdout = stdout_ # Restore stdout + if __name__ == '__main__': unittest.main() diff --git a/doubly_linked_list/doubly_linked_list.py b/doubly_linked_list/doubly_linked_list.py index 6f91b43a9b..2754ff7193 100644 --- a/doubly_linked_list/doubly_linked_list.py +++ b/doubly_linked_list/doubly_linked_list.py @@ -2,16 +2,27 @@ Each ListNode holds a reference to its previous node as well as its next node in the List. """ + + class ListNode: - def __init__(self, value, prev=None, next=None): + def __init__(self, value, prev=None, next_node=None): self.prev = prev self.value = value - self.next = next - + self.next = next_node + + def delete(self): + if self.prev: + self.prev.next = self.next + if self.next: + self.next.prev = self.prev + + """ Our doubly-linked list class. It holds references to the list's head and tail nodes. """ + + class DoublyLinkedList: def __init__(self, node=None): self.head = node @@ -20,63 +31,132 @@ def __init__(self, node=None): def __len__(self): return self.length - + """ Wraps the given value in a ListNode and inserts it as the new head of the list. Don't forget to handle the old head node's previous pointer accordingly. """ + def add_to_head(self, value): - pass - + new_node = ListNode(value) + if self.head is None: + self.head = new_node + self. tail = new_node + else: + new_node.next = self.head + self.head.prev = new_node + self.head = new_node + + self.length += 1 + """ Removes the List's current head node, making the current head's next node the new head of the List. Returns the value of the removed Node. """ + def remove_from_head(self): - pass - + temp = self.head + self.delete(self.head) + return temp.value + """ Wraps the given value in a ListNode and inserts it as the new tail of the list. Don't forget to handle the old tail node's next pointer accordingly. """ + def add_to_tail(self, value): - pass - + new_node = ListNode(value) + + if self.head is None: + self.head = new_node + self.tail = new_node + elif self.head is self.tail: + self.head.next = new_node + new_node.prev = self.head + self.tail = new_node + else: + self.tail.next = new_node + new_node.prev = self.tail + self.tail = new_node + + self.length += 1 + """ Removes the List's current tail node, making the current tail's previous node the new tail of the List. Returns the value of the removed Node. """ + def remove_from_tail(self): - pass - + temp = self.tail + self.delete(self.tail) + + return temp.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 + self.delete(node) + self.add_to_head(node.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 + + self.delete(node) + self.add_to_tail(node.value) """ Deletes the input node from the List, preserving the order of the other elements of the List. """ + def delete(self, node): - pass + # Don't need to return value + + # DO need to update head, tail + if self.head is None: + return None + elif self.head == self.tail: + self.head = None + self.tail = None + elif node is self.head: + self.head = self.head.next + node.delete() + elif node is self.tail: + self.tail = node.prev + node.delete() + else: + node.delete() + + 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 + highest = 0 + + current_node = self.head + while current_node is not None: + if current_node.value > highest: + highest = current_node.value + + current_node = current_node.next + + return highest diff --git a/queue/queue.py b/queue/queue.py index 0d2599ded7..c589ceed6d 100644 --- a/queue/queue.py +++ b/queue/queue.py @@ -1,28 +1,38 @@ """ A queue is a data structure whose primary purpose is to store and -return elements in First In First Out order. +return elements in First In First Out order. 1. Implement the Queue class using an array as the underlying storage structure. Make sure the Queue tests pass. 2. Re-implement the Queue class, this time using the linked list implementation as the underlying storage structure. Make sure the Queue tests pass. -3. What is the difference between using an array vs. a linked list when +3. What is the difference between using an array vs. a linked list when implementing a Queue? - + 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_head(value) + self.size = self.size + 1 def dequeue(self): - pass + if self.size == 0: + return None + else: + popped_value = self.storage.remove_tail() + self.size -= 1 + return popped_value diff --git a/queue/singly_linked_list.py b/queue/singly_linked_list.py new file mode 100644 index 0000000000..6a7809da78 --- /dev/null +++ b/queue/singly_linked_list.py @@ -0,0 +1,98 @@ +# A class that represents the individual elements in our LL + +class Node: + def __init__(self, value=None, next_node=None): + self.value = value + self.next_node = next_node + + def get_value(self): + return self.value + + def get_next_node(self): + return self.next_node + + def set_next_node(self, new_next): + self.next_node = new_next + + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_head(self, value): + new_node = Node(value) + if self.head is None: + self.head = new_node + self.tail = new_node + else: + new_node.set_next_node(self.head) + # update head attribute + self.head = new_node + + def add_to_tail(self, value): + new_node = Node(value) + + if self.head is None: + self.head = new_node + self.tail = new_node + + else: + self.tail.set_next_node(new_node) + self.tail = new_node + + def remove_head(self): + # empty list + if self.head is None: + return None + + # list with one node + else: + ret_value = self.head.get_value() + + if self.head == self.tail: + self.head = None + self.tail = None + + # list with two nodes + + else: + self.head = self.head.get_next_node() + + return ret_value + + def remove_tail(self): + + if self.tail is None: + return None + + else: + ret_value = self.tail.get_value() + if self.head == self.tail: + self.head = None + self.tail = None + else: + current_node = self.head + while current_node is not None: + if current_node.get_next_node() == self.tail: + break + + current_node = current_node.get_next_node() + + self.tail = current_node + self.tail.set_next_node(None) + + print("outside value", ret_value) + return ret_value + + def contains(self, value): + # Loop through LL until next pointer is none + # if equal, break and return true + current_node = self.head + while current_node is not None: + if current_node.get_value == value: + return True + return False + + def get_max(self): + # loop and store highest value + pass diff --git a/singly_linked_list/singly_linked_list.py b/singly_linked_list/singly_linked_list.py index e69de29bb2..3f84964b0b 100644 --- a/singly_linked_list/singly_linked_list.py +++ b/singly_linked_list/singly_linked_list.py @@ -0,0 +1,105 @@ +# A class that represents the individual elements in our LL + +class Node: + def __init__(self, value=None, next_node=None): + self.value = value + self.next_node = next_node + + def get_value(self): + return self.value + + def get_next_node(self): + return self.next_node + + def set_next_node(self, new_next): + self.next_node = new_next + + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_head(self, value): + new_node = Node(value) + if self.head is None: + self.head = new_node + self.tail = new_node + else: + new_node.set_next_node(self.head) + # update head attribute + self.head = new_node + + def add_to_tail(self, value): + new_node = Node(value) + + if self.head is None: + self.head = new_node + self.tail = new_node + + else: + self.tail.set_next_node(new_node) + self.tail = new_node + + def remove_head(self): + # empty list + if self.head is None: + return None + + # list with one node + else: + ret_value = self.head.get_value() + + if self.head == self.tail: + self.head = None + self.tail = None + + # list with two nodes + + else: + self.head = self.head.get_next_node() + + return ret_value + + def remove_tail(self): + + if self.tail is None: + return None + + else: + ret_value = self.tail.get_value() + if self.head == self.tail: + self.head = None + self.tail = None + else: + current_node = self.head + while current_node is not None: + if current_node.get_next_node() == self.tail: + break + + current_node = current_node.get_next_node() + + self.tail = current_node + self.tail.set_next_node(None) + + print("outside value", ret_value) + return ret_value + + def contains(self, value): + # Loop through LL until next pointer is none + # if equal, break and return true + current_node = self.head + while current_node is not None: + if current_node.get_value == value: + return True + return False + + def get_max(self): + # loop and store highest value + highest = 0 + current_node = self.head + + while current_node is not None: + if current_node.value > highest: + highest = current_node.value + current_node = current_node.next_node + return highest diff --git a/stack/singly_linked_list.py b/stack/singly_linked_list.py new file mode 100644 index 0000000000..3f84964b0b --- /dev/null +++ b/stack/singly_linked_list.py @@ -0,0 +1,105 @@ +# A class that represents the individual elements in our LL + +class Node: + def __init__(self, value=None, next_node=None): + self.value = value + self.next_node = next_node + + def get_value(self): + return self.value + + def get_next_node(self): + return self.next_node + + def set_next_node(self, new_next): + self.next_node = new_next + + +class LinkedList: + def __init__(self): + self.head = None + self.tail = None + + def add_to_head(self, value): + new_node = Node(value) + if self.head is None: + self.head = new_node + self.tail = new_node + else: + new_node.set_next_node(self.head) + # update head attribute + self.head = new_node + + def add_to_tail(self, value): + new_node = Node(value) + + if self.head is None: + self.head = new_node + self.tail = new_node + + else: + self.tail.set_next_node(new_node) + self.tail = new_node + + def remove_head(self): + # empty list + if self.head is None: + return None + + # list with one node + else: + ret_value = self.head.get_value() + + if self.head == self.tail: + self.head = None + self.tail = None + + # list with two nodes + + else: + self.head = self.head.get_next_node() + + return ret_value + + def remove_tail(self): + + if self.tail is None: + return None + + else: + ret_value = self.tail.get_value() + if self.head == self.tail: + self.head = None + self.tail = None + else: + current_node = self.head + while current_node is not None: + if current_node.get_next_node() == self.tail: + break + + current_node = current_node.get_next_node() + + self.tail = current_node + self.tail.set_next_node(None) + + print("outside value", ret_value) + return ret_value + + def contains(self, value): + # Loop through LL until next pointer is none + # if equal, break and return true + current_node = self.head + while current_node is not None: + if current_node.get_value == value: + return True + return False + + def get_max(self): + # loop and store highest value + highest = 0 + current_node = self.head + + while current_node is not None: + if current_node.value > highest: + highest = current_node.value + current_node = current_node.next_node + return highest diff --git a/stack/stack.py b/stack/stack.py index 6e6d660ac7..83fb9c9cad 100644 --- a/stack/stack.py +++ b/stack/stack.py @@ -10,16 +10,42 @@ 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): +# return len(self.storage) + +# def push(self, value): +# return self.storage.append(value) + +# def pop(self): +# if len(self.storage) == 0: +# return None +# else: +# return self.storage.pop() + class Stack: def __init__(self): self.size = 0 - # self.storage = ? + self.storage = LinkedList() def __len__(self): - pass + return self.size def push(self, value): - pass + self.storage.add_to_tail(value) + self.size = self.size + 1 def pop(self): - pass + if self.size == 0: + return None + else: + popped_value = self.storage.remove_tail() + self.size -= 1 + return popped_value