diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000..26d33521af --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Data-Structures.iml b/.idea/Data-Structures.iml new file mode 100644 index 0000000000..4f2c9af68e --- /dev/null +++ b/.idea/Data-Structures.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000..d9edd77e40 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,30 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000000..105ce2da2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..a2e120dcc8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000..eaeded0586 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..94a25f7f4c --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ 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..07bfbc75e9 100644 --- a/binary_search_tree/binary_search_tree.py +++ b/binary_search_tree/binary_search_tree.py @@ -9,6 +9,14 @@ 2. Implement the `in_order_print`, `bft_print`, and `dft_print` methods on the BSTNode class. """ +import sys + +sys.path.insert(1, '../queue') +sys.path.insert(1, '../stack') +from queue import QueueA +from stack import Stack + + class BSTNode: def __init__(self, value): self.value = value @@ -17,37 +25,138 @@ 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 + if self.left is None: + # add the new node to the left + self.left = BSTNode(value) + # create a BSTNode and encapsulate the value in it then set it to the left + # otherwise call insert on the left node + else: + self.left.insert(value) + # otherwise (the new nodes value is greater than or equal to the current node value) + elif value >= self.value: + # if there is no right child already here + if self.right is None: + # add the new node to the right + self.right = BSTNode(value) + # create a BSTNode and encapsulate the value in it then set it to the right + # otherwise call insert on the 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 is self.value: + # return True + return True + # check if the target is less than the current nodes value + elif target < self.value: + # if there is no left child already here + 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 the current nodes value) + elif target > self.value: + # 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 + # # ---------------------------------------------- + # # recursive approach + # check if there is no node to the right + if not self.right: + # return the nodes value + return self.value + # return a call to get max on the right child + elif self.right: + return self.right.get_max() + + # ----------------------------------------------- + + # iterative approach + # initialise the max value + max_val = self.value + # get a ref to the current node + current = self + # loop while there is still a current node + while current: + # if the current value is greater than the max value, update the max value + + if current.value > max_val: + max_val = current.value + # move on to the next right node + current = current.right + # return the max value + return max_val + + # ----------------------------------------------------- # 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: + # 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 node + 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 + # base case + 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 + def bft_print(self): # use a queue + queue = QueueA() + queue.enqueue(self) + while len(queue) > 0: + current = queue.dequeue() + print(current.value) + if current.left: + queue.enqueue(self.left) + if current.right: + queue.enqueue(self.right) # Print the value of every node, starting with the given node, # in an iterative depth first traversal - def dft_print(self): - pass + def dft_print(self): # use a stack + s = Stack() + s.push(self) + + while len(s) > 0: + current = s.pop() + print(current.value) + if current.left: + s.push(current.left) + if current.right: + s.push(current.right) # Stretch Goals ------------------------- # Note: Research may be required @@ -60,6 +169,7 @@ def pre_order_dft(self): def post_order_dft(self): pass + """ This code is necessary for testing the `print` methods """ @@ -73,13 +183,13 @@ def post_order_dft(self): bst.insert(4) bst.insert(2) -bst.bft_print() +# 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_print() +# print("post order") +# bst.post_order_dft() diff --git a/binary_search_tree/test_binary_search_tree.py b/binary_search_tree/test_binary_search_tree.py index 0a0cee5911..4ec133645f 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) @@ -61,50 +62,51 @@ def test_for_each(self): self.assertTrue(v4 in arr) self.assertTrue(v5 in arr) - def test_print_traversals(self): - # WARNING: Tests are for Print() - # Debug calls to Print() in functions will cause failure - - stdout_ = sys.stdout # Keep previous value - sys.stdout = io.StringIO() - - self.bst = BSTNode(1) - self.bst.insert(8) - self.bst.insert(5) - self.bst.insert(7) - self.bst.insert(6) - self.bst.insert(3) - self.bst.insert(4) - self.bst.insert(2) - - self.bst.in_order_print() - - output = sys.stdout.getvalue() - self.assertEqual(output, "1\n2\n3\n4\n5\n6\n7\n8\n") - - sys.stdout = io.StringIO() - self.bst.bft_print() - output = sys.stdout.getvalue() - self.assertTrue(output == "1\n8\n5\n3\n7\n2\n4\n6\n" or - output == "1\n8\n5\n7\n3\n6\n4\n2\n") - - sys.stdout = io.StringIO() - self.bst.dft_print() - output = sys.stdout.getvalue() - self.assertTrue(output == "1\n8\n5\n7\n6\n3\n4\n2\n" or - output == "1\n8\n5\n3\n2\n4\n7\n6\n") - - sys.stdout = io.StringIO() - self.bst.pre_order_dft() - output = sys.stdout.getvalue() - self.assertEqual(output, "1\n8\n5\n3\n2\n4\n7\n6\n") - - sys.stdout = io.StringIO() - self.bst.post_order_dft() - output = sys.stdout.getvalue() - self.assertEqual(output, "2\n4\n3\n6\n7\n5\n8\n1\n") + # def test_print_traversals(self): + # # WARNING: Tests are for Print() + # # Debug calls to Print() in functions will cause failure + # + # stdout_ = sys.stdout # Keep previous value + # sys.stdout = io.StringIO() + # + # self.bst = BSTNode(1) + # self.bst.insert(8) + # self.bst.insert(5) + # self.bst.insert(7) + # self.bst.insert(6) + # self.bst.insert(3) + # self.bst.insert(4) + # self.bst.insert(2) + # + # self.bst.in_order_print() + # + # output = sys.stdout.getvalue() + # self.assertEqual(output, "1\n2\n3\n4\n5\n6\n7\n8\n") + # + # sys.stdout = io.StringIO() + # self.bst.bft_print() + # output = sys.stdout.getvalue() + # self.assertTrue(output == "1\n8\n5\n3\n7\n2\n4\n6\n" or + # output == "1\n8\n5\n7\n3\n6\n4\n2\n") + # + # sys.stdout = io.StringIO() + # self.bst.dft_print() + # output = sys.stdout.getvalue() + # self.assertTrue(output == "1\n8\n5\n7\n6\n3\n4\n2\n" or + # output == "1\n8\n5\n3\n2\n4\n7\n6\n") + # + # sys.stdout = io.StringIO() + # self.bst.pre_order_dft() + # output = sys.stdout.getvalue() + # self.assertEqual(output, "1\n8\n5\n3\n2\n4\n7\n6\n") + # + # sys.stdout = io.StringIO() + # self.bst.post_order_dft() + # output = sys.stdout.getvalue() + # self.assertEqual(output, "2\n4\n3\n6\n7\n5\n8\n1\n") + # + # sys.stdout = stdout_ # Restore stdout - 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..18c8521d6b 100644 --- a/doubly_linked_list/doubly_linked_list.py +++ b/doubly_linked_list/doubly_linked_list.py @@ -2,16 +2,31 @@ 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): self.prev = prev self.value = value self.next = next - + + def delete(self): + if self.prev: + self.prev.next = self.next + else: + return None + if self.next: + self.next.prev = self.prev + else: + return None + + """ 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 +35,148 @@ 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, None, None) + 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 + self.head.prev = new_node + self.head = new_node + """ 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 - + value = self.head.value + self.delete(self.head) + return 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, 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 current tail's previous node the new tail of the List. 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 already the head + if node is self.head: + return None + # temporary holder for node's + value = node.value + if node is self.tail: + self.remove_from_tail() + else: + node.delete() + 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 None + value = node.value + if node is self.head: + self.remove_from_head() + else: + node.delete() + 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 there's nothing in list + if not self.head: + return None + # if there's one item in the list + elif (self.head is node) & (self.tail is node): + self.head = None + self.tail = None + self.length -= 1 + # value is in head + elif self.head is node: + self.head = self.head.next + self.head.prev = None + self.length -= 1 + # if value is in tail + elif self.tail is node: + self.tail = self.tail.prev + self.tail.next = None + self.length -= 1 + # if in middle + else: + node.prev.next = 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 + current = self.head + if not current: + return None + if current.next is None: + return current.value + max = current.value + while current is not None: + if max < current.value: + max = current.value + current = current.next + return max + + +close = DoublyLinkedList(ListNode(1)) +close.add_to_tail(20) +print(close.tail.value) +close.move_to_front(close.tail) +print(close.tail.value) \ No newline at end of file diff --git a/doubly_linked_list/test_doubly_linked_list.py b/doubly_linked_list/test_doubly_linked_list.py index 3a4ace4d85..f294271aa9 100644 --- a/doubly_linked_list/test_doubly_linked_list.py +++ b/doubly_linked_list/test_doubly_linked_list.py @@ -2,6 +2,7 @@ from doubly_linked_list import ListNode from doubly_linked_list import DoublyLinkedList + class DoublyLinkedListTests(unittest.TestCase): def setUp(self): self.node = ListNode(1) @@ -37,7 +38,7 @@ def test_list_remove_from_head(self): self.assertEqual(len(self.dll), 1) self.assertEqual(self.dll.remove_from_head(), 2) self.assertEqual(len(self.dll), 0) - + self.dll.add_to_head(55) self.assertEqual(len(self.dll), 1) self.assertEqual(self.dll.remove_from_head(), 55) @@ -106,7 +107,7 @@ def test_list_delete(self): self.dll.add_to_tail(1) self.dll.add_to_head(9) self.dll.add_to_tail(6) - + self.dll.delete(self.dll.head.next) self.assertEqual(self.dll.head.value, 9) self.assertEqual(self.dll.head.next, self.dll.tail) @@ -131,5 +132,6 @@ def test_get_max(self): self.dll.add_to_tail(101) self.assertEqual(self.dll.get_max(), 101) + if __name__ == '__main__': unittest.main() diff --git a/notes.py b/notes.py new file mode 100644 index 0000000000..b70fa19b97 --- /dev/null +++ b/notes.py @@ -0,0 +1,17 @@ +""" + 1. Recursion - process of repeating items in a self-similar way || basically reduce a problem + into to smaller problems || not good to have infinite recursion + 2. BST + 3. Traversal + 4. Heap +""" + + +def mult(a, b): + if b == a: + return a + else: + return a + mult(a, b - 1) + + +print(mult(1, 2)) \ No newline at end of file diff --git a/queue/queue.py b/queue/queue.py index 0d2599ded7..1e6a1b6378 100644 --- a/queue/queue.py +++ b/queue/queue.py @@ -13,16 +13,50 @@ 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: +import sys +sys.path.append("../singly_linked_list") +from singly_linked_list import LinkedList + + +class QueueA: 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.append(value) def dequeue(self): - pass + while len(self.storage) == 0: + return None + return self.storage.pop(0) +# Array + +# LinkedList Implementation of queue +# class Queue(LinkedList): +# def __init__(self): +# super().__init__() +# self.size = 0 +# +# def __len__(self): +# return self.size +# +# def enqueue(self, item): +# self.add_to_tail(item) +# self.size += 1 +# +# def dequeue(self): +# if self.size == 0: +# return None +# self.size -= 1 +# return self.remove_head() + +if __name__ == "__main__": + a = Queue() + a.enqueue(1) + a.enqueue(2) + a.enqueue(3) + a.enqueue(4) diff --git a/singly_linked_list/singly_linked_list.py b/singly_linked_list/singly_linked_list.py index e69de29bb2..fb2a7f79d9 100644 --- a/singly_linked_list/singly_linked_list.py +++ b/singly_linked_list/singly_linked_list.py @@ -0,0 +1,92 @@ +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(self): + return self.next_node + + def set_next(self, new_next): + self.next_node = new_next + + +class LinkedList: + # two flags: head and tail + def __init__(self): + self.head = None + self.tail = None + + def add_to_head(self, value): + new_node= Node(value) + # create Node from input + if not self.head and not self.tail: + self.head = new_node + self.tail = new_node + else: + new_node.set_next(self.head) + self.head = new_node + + def add_to_tail(self, value): + new_node = Node(value, None) + if not self.head: + # if list is empty + self.head = new_node + self.tail = new_node + else: + # if not empty + self.tail.set_next(new_node) + self.tail = new_node + + def remove_head(self): + if not self.head: + return None + # if head has no next node + if not self.head.get_next(): + head = self.head + # have to change both head and tail since they are both the same item + self.head = None + self.tail = None + return head.get_value() + value = self.head.get_value() + self.head = self.head.get_next() + return value + + def contains(self, value): + if not self.head: + return False + # get a reference to the node we're currently at; update this as we traverse this list + current = self.head + while current: + if current.get_value() == value: + return True + current = current.get_next() + return False + + def get_max(self): + current = self.head + # if there's nothing in the list + if not current: + return None + # if there's only one thing in the list + if not current.get_next(): + return current.get_value() + max = current.get_value() + # mistake: was only comparing the first element and it wasn't moving + # "current" to the next element + while current is not None: + if max < current.get_value(): + max = current.get_value() + current = current.get_next() + return max + + +if __name__ == "__main__": + a = LinkedList() + a.add_to_tail(1) + a.add_to_tail(1) + a.add_to_tail(1) + a.add_to_tail(1) + print(a.head) \ No newline at end of file diff --git a/stack/.ipynb_checkpoints/stack-checkpoint.py b/stack/.ipynb_checkpoints/stack-checkpoint.py new file mode 100644 index 0000000000..0d405c1918 --- /dev/null +++ b/stack/.ipynb_checkpoints/stack-checkpoint.py @@ -0,0 +1,42 @@ +""" +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 + as the underlying storage structure. + Make sure the Stack tests pass. +3. What is the difference between using an array vs. a linked list when + implementing a Stack? +""" + + +class Stack: + def __init__(self): + self.size = 0 + self.stack = [] + # self.storage = ? + + def __str__(self): + return f"{self.stack}" + + def __len__(self): + return f"Length of stack: {self.size}" + + def push(self, value): + self.size += 1 + self.stack.append(value) + + def pop(self): + self.size -= 1 + return self.stack.pop(-1) + + +if __name__ == '__main__': + a = Stack() + a.push(1) + a.push(1) + a.pop() + print(a) + print(a.__len__()) diff --git a/stack/stack.py b/stack/stack.py index 6e6d660ac7..e65782948e 100644 --- a/stack/stack.py +++ b/stack/stack.py @@ -1,25 +1,76 @@ """ A stack is a data structure whose primary purpose is to store and -return elements in Last In First Out order. +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 as the underlying storage structure. Make sure the Stack 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 Stack? """ +import sys + +sys.path.append("../singly_linked_list") +from singly_linked_list import LinkedList, Node + + +# The code that was wrong with the test +# Specifics: Couldn't pass the self.assertIsNone(self.stack.pop()) on line 40 +# class Stack: +# def __init__(self): +# self.stack = [] +# self.size = 0 +# # self.storage = ? +# +# def __len__(self): +# return self.size +# +# def push(self, value): +# self.size += 1 +# self.stack.append(value) +# +# def pop(self): +# self.size -= 1 +# # if not IndexError: +# return self.stack.pop() +# # else: +# # return None + +# Array version of stack class Stack: def __init__(self): - self.size = 0 + self.storage = [] # self.storage = ? def __len__(self): - pass + return len(self.storage) def push(self, value): - pass + self.storage.append(value) def pop(self): - pass + if len(self.storage) == 0: + return None + return self.storage.pop() + + +# Linked List Implementation +# class Stack(LinkedList): +# def __init__(self): +# super().__init__() +# self.size = 0 +# +# def __len__(self): +# return self.size +# +# def push(self, value): +# self.add_to_head(value) +# self.size += 1 +# +# def pop(self): +# if self.size == 0: +# return None +# self.size -= 1 +# return self.remove_head()