├── .gitignore ├── Data_Structures_Questions.md ├── FAQ.md ├── README.md ├── avl_tree ├── avl_tree.py └── test_avl_tree.py ├── binary_search_tree ├── binary_search_tree.py └── test_binary_search_tree.py ├── doubly_linked_list ├── doubly_linked_list.py └── test_doubly_linked_list.py ├── heap ├── README.md ├── generic_heap.py ├── max_heap.py ├── test_generic_heap.py └── test_max_heap.py ├── lru_cache ├── lru_cache.py └── test_lru_cache.py ├── queue ├── queue.py └── test_queue.py ├── singly_linked_list ├── singly_linked_list.py └── test_singly_linked_list.py └── stack ├── stack.py └── test_stack.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.pyc 4 | .vscode/ -------------------------------------------------------------------------------- /Data_Structures_Questions.md: -------------------------------------------------------------------------------- 1 | Answer the following questions for each of the data structures you implemented as part of this project. 2 | 3 | ## Stack 4 | 5 | 1. What is the runtime complexity of `push` using a list? 6 | 7 | 2. What is the runtime complexity of `push` using a linked list? 8 | 9 | 3. What is the runtime complexity of `pop` using a list? 10 | 11 | 4. What is the runtime complexity of `pop` using a linked list? 12 | 13 | 5. What is the runtime complexity of `len` using a list? 14 | 15 | 6. What is the runtime complexity of `len` using a linked list? 16 | 17 | ## Queue 18 | 19 | 1. What is the runtime complexity of `enqueue` using a list? 20 | 21 | 2. What is the runtime complexity of `enqueue` using a linked list? 22 | 23 | 3. What is the runtime complexity of `dequeue` using a list? 24 | 25 | 4. What is the runtime complexity of `dequeue` using a linked list? 26 | 27 | 5. What is the runtime complexity of `len` using a list? 28 | 29 | 6. What is the runtime complexity of `len` using a linked list? 30 | 31 | ## Doubly Linked List 32 | 33 | 1. What is the runtime complexity of `ListNode.insert_after`? 34 | 35 | 2. What is the runtime complexity of `ListNode.insert_before`? 36 | 37 | 3. What is the runtime complexity of `ListNode.delete`? 38 | 39 | 4. What is the runtime complexity of `DoublyLinkedList.add_to_head`? 40 | 41 | 5. What is the runtime complexity of `DoublyLinkedList.remove_from_head`? 42 | 43 | 6. What is the runtime complexity of `DoublyLinkedList.add_to_tail`? 44 | 45 | 7. What is the runtime complexity of `DoublyLinkedList.remove_from_tail`? 46 | 47 | 8. What is the runtime complexity of `DoublyLinkedList.move_to_front`? 48 | 49 | 9. What is the runtime complexity of `DoublyLinkedList.move_to_end`? 50 | 51 | 10. What is the runtime complexity of `DoublyLinkedList.delete`? 52 | 53 | a. Compare the runtime of the doubly linked list's `delete` method with the worst-case runtime of the JS `Array.splice` method. Which method generally performs better? 54 | 55 | ## Binary Search Tree 56 | 57 | 1. What is the runtime complexity of `insert`? 58 | 59 | 2. What is the runtime complexity of `contains`? 60 | 61 | 3. What is the runtime complexity of `get_max`? 62 | 63 | 4. What is the runtime complexity of `for_each`? 64 | 65 | ## Heap 66 | 67 | 1. What is the runtime complexity of `_bubble_up`? 68 | 69 | 2. What is the runtime complexity of `_sift_down`? 70 | 71 | 3. What is the runtime complexity of `insert`? 72 | 73 | 4. What is the runtime complexity of `delete`? 74 | 75 | 5. What is the runtime complexity of `get_max`? 76 | -------------------------------------------------------------------------------- /FAQ.md: -------------------------------------------------------------------------------- 1 | # Data Structures FAQ 2 | ## Contents 3 | ### General 4 | 5 | #### My imports aren't working, help! 6 | Python can be tricky with how imports work and can vary from environment to environment. If you are having trouble, the easiest solution is to copy the file you want to import into the same folder as the file you want to import it into and use `from file import class`. 7 | 8 | This is not a best practice for a real application, but it is acceptable for this exercise. Just remember that this may mean if you want to change or update your code, you will have to do it in multiple places. 9 | 10 | #### What are real-world use-cases for a Queue data structure? 11 | Queues are used anywhere in which you want to work with data `First in First Out` (FIFO). Server requests, without any prioritization, are handled this way. We'll also use it to conduct a breadth first traversal and breadth first search. 12 | 13 | #### What are real-world use-cases for a Stack data structure? 14 | Stacks are used when you want to work with data `Last in First Out` (LIFO or FILO). They're used in processor architecture, undo logic, and depth first searches and traversals. 15 | 16 | #### What are real-world use-cases for a Linked List data structure? 17 | Linked Lists can be used to create `queues` and `stacks`. They're also a key part of resolving collisions in hash tables, which we'll learn more about in a few weeks. 18 | 19 | #### How are Linked-Lists different than an Array? 20 | Both linked lists and arrays are linear data structures. An array is the most space efficient type of storage possible and has great time complexity for most operations. Logically, the array is linear in structure, and it is stored in a linear segment of memory. It is accessed by starting at the memory address of the pointer and counting forward the number of bits resulting from the `index` times the size of the data type. One weakness is the time complexity of operations that take data out of anywhere but the end and another is changing the size of the array. 21 | 22 | A linked list is not as efficient for storage because each element requires a pointer to the next, and in a doubly-linked list, previous element. It is also more difficult to access the elements. Because there's no index, you must loop through the list to search for the item you want, which is O(n). However, a linked list does not require a contiguous block of memory. It has 0(1) to remove or add items anywhere in the list. 23 | 24 | Generally speaking, it's usually best to use an array unless you expect to frequently add and remove items from anywhere other than the end. In that case, it's better to use a linked list. 25 | 26 | #### I've always been able to add as much as I want to an Array and take things out from the beginning, end, or anywhere else. It's never been a problem before, why are we bothering with all of this? 27 | We're looking under the hood! High level languages like Python abstract away most of the inner workings of everything we do. Most of the time this is a good thing and most of the time it doesn't matter. However, we're professional engineers and sometimes we need to solve problems where the details can have a major impact on success or failure. Think about your car. Do you know exactly how much weight you can put in it? Probably not, nor do you need to. But if you find yourself needing to put a load of bags of concrete in the trunk it suddenly becomes very important. As an engineer, you'll be expected to understand when the details do and do not matter. 28 | 29 | #### What are real-world use-cases for a LRU Cache? 30 | An LRU cache is an efficient type of caching system that keeps recently used items and when the cache becomes full, pushes out the least recently used item in the cache. It can be used any time a subset of data is used frequently that needs to be pulled from a source with a long lookup time. For example, cacheing the most frequently accessed items from a database on a remote server. 31 | 32 | #### What is the dictionary being used for in the LRU Cache? 33 | We can't access items in a linked list directly because linked lists are not indexed. To see if an item is already in the cache, we'd need to loop through the cache at O(n). By also adding a dictionary to organize the nodes that are already present in memory, we index the linked list for a very small overhead cost. 34 | 35 | #### What are real-world use-cases for a Binary Search Tree data structure? 36 | A BST in the way that is being implemented for this Sprint is a bit too simple to see any real-world use-cases. There are many (more complex) variants of BSTs that do see production use. One very notable variant is the [B-tree](https://en.wikipedia.org/wiki/B-tree), which is a self-balancing ordered variant of the BST. B-trees play a critical role in database and file system indexing. Other notable variants include the AVL tree, which is a self-balancing BST and the prefix tree, which is specialized for handling text. 37 | 38 | #### How is the root element of a Binary Search Tree decided? 39 | The first element added to a BST is the root of the tree. However, doing it this way means that it's a very simple matter to end up with a lopsided BST. If we simply insert a monotonically ascending or descending sequence of values, then the tree would essentially flatten down to a linked list, and we'd lose all the benefits that a BST is supposed to confer. Self-balancing variants of the BST exist in order to alleviate this exact problem. 40 | 41 | #### What is the difference between Breadth First and Depth First? 42 | In depth first, we pick one path at each branch and keep going forward until we hit a dead end, then backtrack and take the first branch we find. In breadth first, we go by layers, one row deeper each time. This means that we jump around a bit. 43 | 44 | #### What is the difference between a Search and a Traversal? 45 | A search and a traversal are processed exactly the same. The difference is that we stop a search when we find what we were looking for, or when all nodes have been visited without finding it. In a traversal, we always keep going until we've visited every node. 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | 3 | Topics: 4 | * Singly Linked Lists 5 | * Queues and Stacks 6 | * Doubly Linked Lists 7 | * Binary Search Trees 8 | * Related Code Challenge Problems 9 | 10 | Stretch Goals: 11 | * LRU Cache 12 | * Heaps 13 | * AVL Trees 14 | 15 | ## Completion Requirements 16 | * Module 1: Implement the Stack and Queue classes using built-in Python lists and the Node and LinkedList classes you created during the Module 1 Guided Project. 17 | * Module 2: Implement the Doubly Linked List class 18 | * Module 3: Implement the Binary Search Tree class 19 | * Module 4: Implement traversal methods on Binary Search Trees 20 | 21 | > NOTE: The quickest and easiest way to reliably import a file in Python is to just copy and paste the file you want to import into the same directory as the file that wants to import. This obviously isn't considered best practice, but it is the most reliable way to do it across all platforms. If the import isn't working, feel free to try this method. 22 | 23 | ### Stacks 24 | * Should have the methods: `push`, `pop`, and `len`. 25 | * `push` adds an item to the top of the stack. 26 | * `pop` removes and returns the element at the top of the stack 27 | * `len` returns the number of elements in the stack. 28 | 29 | ### Queues 30 | * Has the methods: `enqueue`, `dequeue`, and `len`. 31 | * `enqueue` adds an element to the back of the queue. 32 | * `dequeue` removes and returns the element at the front of the queue. 33 | * `len` returns the number of elements in the queue. 34 | 35 | ![Image of Queue](https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Data_Queue.svg/600px-Data_Queue.svg.png) 36 | 37 | ### Doubly Linked Lists 38 | * The `ListNode` class, which represents a single node in the doubly-linked list, has already been implemented for you. Inspect this code and try to understand what it is doing to the best of your ability. 39 | * The `DoublyLinkedList` class itself should have the methods: `add_to_head`, `add_to_tail`, `remove_from_head`, `remove_from_tail`, `move_to_front`, `move_to_end`, `delete`, and `get_max`. 40 | * `add_to_head` replaces the head of the list with a new value that is passed in. 41 | * `add_to_tail` replaces the tail of the list with a new value that is passed in. 42 | * `remove_from_head` removes the head node and returns the value stored in it. 43 | * `remove_from_tail` removes the tail node and returns the value stored in it. 44 | * `move_to_front` takes a reference to a node in the list and moves it to the front of the list, shifting all other list nodes down. 45 | * `move_to_end` takes a reference to a node in the list and moves it to the end of the list, shifting all other list nodes up. 46 | * `delete` takes a reference to a node in the list and removes it from the list. The deleted node's `previous` and `next` pointers should point to each afterwards. 47 | * `get_max` returns the maximum value in the list. 48 | * The `head` property is a reference to the first node and the `tail` property is a reference to the last node. 49 | 50 | ![Image of Doubly Linked List](https://upload.wikimedia.org/wikipedia/commons/thumb/5/5e/Doubly-linked-list.svg/610px-Doubly-linked-list.svg.png) 51 | 52 | ### Binary Search Trees 53 | * Should have the methods `insert`, `contains`, `get_max`. 54 | * `insert` adds the input value to the binary search tree, adhering to the rules of the ordering of elements in a binary search tree. 55 | * `contains` searches the binary search tree for the input value, returning a boolean indicating whether the value exists in the tree or not. 56 | * `get_max` returns the maximum value in the binary search tree. 57 | * `for_each` performs a traversal of _every_ node in the tree, executing the passed-in callback function on each tree node value. There is a myriad of ways to perform tree traversal; in this case any of them should work. 58 | 59 | ![Image of Binary Search Tree](https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Binary_search_tree.svg/300px-Binary_search_tree.svg.png) 60 | 61 | --- 62 | 63 | Once you've gotten the tests passing, it's time to analyze the runtime complexity of your `get` and `set` operations. There's a way to get both operations down to sub-linear time. In fact, we can get them each down to constant time by picking the right data structures to use. 64 | 65 | Here are you some things to think about with regards to optimizing your implementation: If you opted to use a dictionary to work with key-value pairs, we know that dictionaries give us constant access time, which is great. It's cheap and efficient to fetch pairs. A problem arises though from the fact that dictionaries don't have any way of remembering the order in which key-value pairs are added. But we definitely need something to remember the order in which pairs are added. Can you think of some ways to get around this constraint? 66 | 67 | ## Stretch Goals 68 | 69 | ### LRU Cache 70 | An LRU (Least Recently Used) cache is an in-memory storage structure that adheres to the [Least Recently Used](https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)) caching strategy. 71 | 72 | In essence, you can think of an LRU cache as a data structure that keeps track of the order in which elements (which take the form of key-value pairs) it holds are added and updated. The cache also has a max number of entries it can hold. This is important because once the cache is holding the max number of entries, if a new entry is to be inserted, another pre-existing entry needs to be evicted from the cache. Because the cache is using a least-recently used strategy, the oldest entry (the one that was added/updated the longest time ago) is removed to make space for the new entry. 73 | 74 | So what operations will we need on our cache? We'll certainly need some sort of `set` operation to add key-value pairs to the cache. Newly-set pairs will get moved up the priority order such that every other pair in the cache is now one spot lower in the priority order that the cache maintains. The lowest-priority pair will get removed from the cache if the cache is already at its maximal capacity. Additionally, in the case that the key already exists in the cache, we simply want to overwrite the old value associated with the key with the newly-specified value. 75 | 76 | We'll also need a `get` operation that fetches a value given a key. When a key-value pair is fetched from the cache, we'll go through the same priority-increase dance that also happens when a new pair is added to the cache. 77 | 78 | Note that the only way for entries to be removed from the cache is when one needs to be evicted to make room for a new one. Thus, there is no explicit `remove` method. 79 | 80 | Given the above spec, try to get a working implementation that passes the tests. What data structure(s) might be good candidates with which to build our cache on top of? Hint: Since our cache is going to be storing key-value pairs, we might want to use a structure that is adept at handling those. 81 | 82 | ### AVL Tree 83 | An AVL tree (Georgy Adelson-Velsky and Landis' tree, named after the inventors) is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. 84 | 85 | We define balance factor for each node as : 86 | ``` 87 | balanceFactor = height(left subtree) - height(right subtree) 88 | ``` 89 | 90 | The balance factor of any node of an AVL tree is in the integer range [-1,+1]. If after any modification in the tree, the balance factor becomes less than −1 or greater than +1, the subtree rooted at this node is unbalanced, and a rotation is needed. 91 | 92 | ![AVL tree rebalancing](https://s3.amazonaws.com/hr-challenge-images/0/1436854305-b167cc766c-AVL_Tree_Rebalancing.svg.png) 93 | 94 | Implement an AVL Tree class that exhibits the aforementioned behavior. The tree's `insert` method should perform the same logic as what was implemented for the binary search tree, with the caveat that upon inserting a new element into the tree, it will then check to see if the tree needs to be rebalanced. 95 | 96 | How does the time complexity of the AVL Tree's insertion method differ from the binary search tree's? 97 | -------------------------------------------------------------------------------- /avl_tree/avl_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Node class to keep track of 3 | the data internal to individual nodes 4 | """ 5 | class Node: 6 | def __init__(self, key): 7 | self.key = key 8 | self.left = None 9 | self.right = None 10 | 11 | """ 12 | A tree class to keep track of things like the 13 | balance factor and the rebalancing logic 14 | """ 15 | class AVLTree: 16 | def __init__(self, node=None): 17 | self.node = node 18 | # init height to -1 because of 0-indexing 19 | self.height = -1 20 | self.balance = 0 21 | 22 | """ 23 | Display the whole tree. Uses recursive def. 24 | """ 25 | def display(self, level=0, pref=''): 26 | self.update_height() # Update height before balancing 27 | self.update_balance() 28 | 29 | if self.node != None: 30 | print ('-' * level * 2, pref, self.node.key, 31 | f'[{self.height}:{self.balance}]', 32 | 'L' if self.height == 0 else ' ') 33 | if self.node.left != None: 34 | self.node.left.display(level + 1, '<') 35 | if self.node.right != None: 36 | self.node.right.display(level + 1, '>') 37 | 38 | """ 39 | Computes the maximum number of levels there are 40 | in the tree 41 | """ 42 | def update_height(self): 43 | pass 44 | 45 | """ 46 | Updates the balance factor on the AVLTree class 47 | """ 48 | def update_balance(self): 49 | pass 50 | 51 | """ 52 | Perform a left rotation, making the right child of this 53 | node the parent and making the old parent the left child 54 | of the new parent. 55 | """ 56 | def left_rotate(self): 57 | pass 58 | 59 | """ 60 | Perform a right rotation, making the left child of this 61 | node the parent and making the old parent the right child 62 | of the new parent. 63 | """ 64 | def right_rotate(self): 65 | pass 66 | 67 | """ 68 | Sets in motion the rebalancing logic to ensure the 69 | tree is balanced such that the balance factor is 70 | 1 or -1 71 | """ 72 | def rebalance(self): 73 | pass 74 | 75 | """ 76 | Uses the same insertion logic as a binary search tree 77 | after the value is inserted, we need to check to see 78 | if we need to rebalance 79 | """ 80 | def insert(self, key): 81 | pass 82 | -------------------------------------------------------------------------------- /avl_tree/test_avl_tree.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from avl_tree import AVLTree 3 | from avl_tree import Node 4 | 5 | class AVLTreeTests(unittest.TestCase): 6 | def setUp(self): 7 | self.tree = AVLTree() 8 | 9 | def test_update_height(self): 10 | self.assertEqual(self.tree.height, -1) 11 | self.tree.node = Node(5) 12 | self.tree.update_height() 13 | self.assertEqual(self.tree.height, 0) 14 | 15 | self.tree.node.left = AVLTree(Node(3)) 16 | self.tree.update_height() 17 | self.assertEqual(self.tree.node.left.height, 0) 18 | self.assertEqual(self.tree.height, 1) 19 | 20 | self.tree.node.right = AVLTree(Node(6)) 21 | self.tree.update_height() 22 | self.assertEqual(self.tree.height, 1) 23 | 24 | self.tree.node.right.node.right = AVLTree(Node(8)) 25 | self.tree.update_height() 26 | self.assertEqual(self.tree.height, 2) 27 | 28 | def test_left_rotation(self): 29 | self.tree.node = Node(5) 30 | self.tree.node.left = AVLTree(Node('x')) 31 | self.tree.node.right = AVLTree(Node(8)) 32 | self.tree.node.right.node.left = AVLTree(Node('c')) 33 | self.tree.node.right.node.right = AVLTree(Node(9)) 34 | self.tree.node.right.node.right.node.left = AVLTree(Node('y')) 35 | self.tree.node.right.node.right.node.right = AVLTree(Node('z')) 36 | 37 | self.tree.left_rotate() 38 | 39 | self.assertEqual(self.tree.node.key, 8) 40 | self.assertEqual(self.tree.node.left.node.key, 5) 41 | self.assertEqual(self.tree.node.right.node.key, 9) 42 | self.assertEqual(self.tree.node.left.node.left.node.key, 'x') 43 | self.assertEqual(self.tree.node.left.node.right.node.key, 'c') 44 | self.assertEqual(self.tree.node.right.node.left.node.key, 'y') 45 | self.assertEqual(self.tree.node.right.node.right.node.key, 'z') 46 | 47 | def test_right_rotation(self): 48 | self.tree.node = Node(5) 49 | self.tree.node.right = AVLTree(Node('x')) 50 | self.tree.node.left = AVLTree(Node(4)) 51 | self.tree.node.left.node.right = AVLTree(Node('c')) 52 | self.tree.node.left.node.left = AVLTree(Node(3)) 53 | self.tree.node.left.node.left.node.left = AVLTree(Node('y')) 54 | self.tree.node.left.node.left.node.right = AVLTree(Node('z')) 55 | 56 | self.tree.right_rotate() 57 | 58 | self.assertEqual(self.tree.node.key, 4) 59 | self.assertEqual(self.tree.node.left.node.key, 3) 60 | self.assertEqual(self.tree.node.right.node.key, 5) 61 | self.assertEqual(self.tree.node.left.node.left.node.key, 'y') 62 | self.assertEqual(self.tree.node.left.node.right.node.key, 'z') 63 | self.assertEqual(self.tree.node.right.node.left.node.key, 'c') 64 | self.assertEqual(self.tree.node.right.node.right.node.key, 'x') 65 | 66 | def test_rebalancing(self): 67 | self.tree.node = Node(5) 68 | self.tree.node.right = AVLTree(Node('x')) 69 | self.tree.node.left = AVLTree(Node(3)) 70 | self.tree.node.left.node.right = AVLTree(Node(4)) 71 | self.tree.node.left.node.left = AVLTree(Node('c')) 72 | self.tree.node.left.node.right.node.left = AVLTree(Node('y')) 73 | self.tree.node.left.node.right.node.right = AVLTree(Node('z')) 74 | 75 | self.tree.rebalance() 76 | 77 | self.assertEqual(self.tree.node.key, 4) 78 | self.assertEqual(self.tree.node.left.node.key, 3) 79 | self.assertEqual(self.tree.node.right.node.key, 5) 80 | self.assertEqual(self.tree.node.left.node.left.node.key, 'c') 81 | self.assertEqual(self.tree.node.left.node.right.node.key, 'y') 82 | self.assertEqual(self.tree.node.right.node.left.node.key, 'z') 83 | self.assertEqual(self.tree.node.right.node.right.node.key, 'x') 84 | 85 | def test_insertion(self): 86 | self.tree.insert(5) 87 | self.assertEqual(self.tree.node.key, 5) 88 | 89 | self.tree.insert(3) 90 | self.assertEqual(self.tree.node.left.node.key, 3) 91 | 92 | self.tree.insert(6) 93 | self.assertEqual(self.tree.node.right.node.key, 6) 94 | 95 | self.tree.insert(7) 96 | self.assertEqual(self.tree.node.right.node.right.node.key, 7) 97 | 98 | self.tree.insert(8) 99 | self.assertEqual(self.tree.node.right.node.key, 7) 100 | self.assertEqual(self.tree.node.right.node.left.node.key, 6) 101 | self.assertEqual(self.tree.node.right.node.right.node.key, 8) 102 | 103 | if __name__ == '__main__': 104 | unittest.main() -------------------------------------------------------------------------------- /binary_search_tree/binary_search_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | Binary search trees are a data structure that enforce an ordering over 3 | the data they store. That ordering in turn makes it a lot more efficient 4 | at searching for a particular piece of data in the tree. 5 | 6 | This part of the project comprises two days: 7 | 1. Implement the methods `insert`, `contains`, `get_max`, and `for_each` 8 | on the BSTNode class. 9 | 2. Implement the `in_order_print`, `bft_print`, and `dft_print` methods 10 | on the BSTNode class. 11 | """ 12 | class BSTNode: 13 | def __init__(self, value): 14 | self.value = value 15 | self.left = None 16 | self.right = None 17 | 18 | # Insert the given value into the tree 19 | def insert(self, value): 20 | pass 21 | 22 | # Return True if the tree contains the value 23 | # False if it does not 24 | def contains(self, target): 25 | pass 26 | 27 | # Return the maximum value found in the tree 28 | def get_max(self): 29 | pass 30 | 31 | # Call the function `fn` on the value of each node 32 | def for_each(self, fn): 33 | pass 34 | 35 | # Part 2 ----------------------- 36 | 37 | # Print all the values in order from low to high 38 | # Hint: Use a recursive, depth first traversal 39 | def in_order_print(self): 40 | pass 41 | 42 | # Print the value of every node, starting with the given node, 43 | # in an iterative breadth first traversal 44 | def bft_print(self): 45 | pass 46 | 47 | # Print the value of every node, starting with the given node, 48 | # in an iterative depth first traversal 49 | def dft_print(self): 50 | pass 51 | 52 | # Stretch Goals ------------------------- 53 | # Note: Research may be required 54 | 55 | # Print Pre-order recursive DFT 56 | def pre_order_dft(self): 57 | pass 58 | 59 | # Print Post-order recursive DFT 60 | def post_order_dft(self): 61 | pass 62 | 63 | """ 64 | This code is necessary for testing the `print` methods 65 | """ 66 | bst = BSTNode(1) 67 | 68 | bst.insert(8) 69 | bst.insert(5) 70 | bst.insert(7) 71 | bst.insert(6) 72 | bst.insert(3) 73 | bst.insert(4) 74 | bst.insert(2) 75 | 76 | bst.bft_print() 77 | bst.dft_print() 78 | 79 | print("elegant methods") 80 | print("pre order") 81 | bst.pre_order_dft() 82 | print("in order") 83 | bst.in_order_dft() 84 | print("post order") 85 | bst.post_order_dft() 86 | -------------------------------------------------------------------------------- /binary_search_tree/test_binary_search_tree.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import random 3 | import sys 4 | import io 5 | from binary_search_tree import BSTNode 6 | 7 | class BinarySearchTreeTests(unittest.TestCase): 8 | def setUp(self): 9 | self.bst = BSTNode(5) 10 | 11 | def test_insert(self): 12 | self.bst.insert(2) 13 | self.bst.insert(3) 14 | self.bst.insert(7) 15 | self.bst.insert(6) 16 | self.assertEqual(self.bst.left.right.value, 3) 17 | self.assertEqual(self.bst.right.left.value, 6) 18 | 19 | def test_handle_dupe_insert(self): 20 | self.bst2 = BSTNode(1) 21 | self.bst2.insert(1) 22 | self.assertEqual(self.bst2.right.value, 1) 23 | 24 | def test_contains(self): 25 | self.bst.insert(2) 26 | self.bst.insert(3) 27 | self.bst.insert(7) 28 | self.assertTrue(self.bst.contains(7)) 29 | self.assertFalse(self.bst.contains(8)) 30 | 31 | def test_get_max(self): 32 | self.assertEqual(self.bst.get_max(), 5) 33 | self.bst.insert(30) 34 | self.assertEqual(self.bst.get_max(), 30) 35 | self.bst.insert(300) 36 | self.bst.insert(3) 37 | self.assertEqual(self.bst.get_max(), 300) 38 | 39 | def test_for_each(self): 40 | arr = [] 41 | cb = lambda x: arr.append(x) 42 | 43 | v1 = random.randint(1, 101) 44 | v2 = random.randint(1, 101) 45 | v3 = random.randint(1, 101) 46 | v4 = random.randint(1, 101) 47 | v5 = random.randint(1, 101) 48 | 49 | self.bst.insert(v1) 50 | self.bst.insert(v2) 51 | self.bst.insert(v3) 52 | self.bst.insert(v4) 53 | self.bst.insert(v5) 54 | 55 | self.bst.for_each(cb) 56 | 57 | self.assertTrue(5 in arr) 58 | self.assertTrue(v1 in arr) 59 | self.assertTrue(v2 in arr) 60 | self.assertTrue(v3 in arr) 61 | self.assertTrue(v4 in arr) 62 | self.assertTrue(v5 in arr) 63 | 64 | def test_print_traversals(self): 65 | # WARNING: Tests are for Print() 66 | # Debug calls to Print() in functions will cause failure 67 | 68 | stdout_ = sys.stdout # Keep previous value 69 | sys.stdout = io.StringIO() 70 | 71 | self.bst = BSTNode(1) 72 | self.bst.insert(8) 73 | self.bst.insert(5) 74 | self.bst.insert(7) 75 | self.bst.insert(6) 76 | self.bst.insert(3) 77 | self.bst.insert(4) 78 | self.bst.insert(2) 79 | 80 | self.bst.in_order_print() 81 | 82 | output = sys.stdout.getvalue() 83 | self.assertEqual(output, "1\n2\n3\n4\n5\n6\n7\n8\n") 84 | 85 | sys.stdout = io.StringIO() 86 | self.bst.bft_print() 87 | output = sys.stdout.getvalue() 88 | self.assertTrue(output == "1\n8\n5\n3\n7\n2\n4\n6\n" or 89 | output == "1\n8\n5\n7\n3\n6\n4\n2\n") 90 | 91 | sys.stdout = io.StringIO() 92 | self.bst.dft_print() 93 | output = sys.stdout.getvalue() 94 | self.assertTrue(output == "1\n8\n5\n7\n6\n3\n4\n2\n" or 95 | output == "1\n8\n5\n3\n2\n4\n7\n6\n") 96 | 97 | sys.stdout = io.StringIO() 98 | self.bst.pre_order_dft() 99 | output = sys.stdout.getvalue() 100 | self.assertEqual(output, "1\n8\n5\n3\n2\n4\n7\n6\n") 101 | 102 | sys.stdout = io.StringIO() 103 | self.bst.post_order_dft() 104 | output = sys.stdout.getvalue() 105 | self.assertEqual(output, "2\n4\n3\n6\n7\n5\n8\n1\n") 106 | 107 | sys.stdout = stdout_ # Restore stdout 108 | 109 | if __name__ == '__main__': 110 | unittest.main() 111 | -------------------------------------------------------------------------------- /doubly_linked_list/doubly_linked_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | Each ListNode holds a reference to its previous node 3 | as well as its next node in the List. 4 | """ 5 | class ListNode: 6 | def __init__(self, value, prev=None, next=None): 7 | self.prev = prev 8 | self.value = value 9 | self.next = next 10 | 11 | """ 12 | Our doubly-linked list class. It holds references to 13 | the list's head and tail nodes. 14 | """ 15 | class DoublyLinkedList: 16 | def __init__(self, node=None): 17 | self.head = node 18 | self.tail = node 19 | self.length = 1 if node is not None else 0 20 | 21 | def __len__(self): 22 | return self.length 23 | 24 | """ 25 | Wraps the given value in a ListNode and inserts it 26 | as the new head of the list. Don't forget to handle 27 | the old head node's previous pointer accordingly. 28 | """ 29 | def add_to_head(self, value): 30 | pass 31 | 32 | """ 33 | Removes the List's current head node, making the 34 | current head's next node the new head of the List. 35 | Returns the value of the removed Node. 36 | """ 37 | def remove_from_head(self): 38 | pass 39 | 40 | """ 41 | Wraps the given value in a ListNode and inserts it 42 | as the new tail of the list. Don't forget to handle 43 | the old tail node's next pointer accordingly. 44 | """ 45 | def add_to_tail(self, value): 46 | pass 47 | 48 | """ 49 | Removes the List's current tail node, making the 50 | current tail's previous node the new tail of the List. 51 | Returns the value of the removed Node. 52 | """ 53 | def remove_from_tail(self): 54 | pass 55 | 56 | """ 57 | Removes the input node from its current spot in the 58 | List and inserts it as the new head node of the List. 59 | """ 60 | def move_to_front(self, node): 61 | pass 62 | 63 | """ 64 | Removes the input node from its current spot in the 65 | List and inserts it as the new tail node of the List. 66 | """ 67 | def move_to_end(self, node): 68 | pass 69 | 70 | """ 71 | Deletes the input node from the List, preserving the 72 | order of the other elements of the List. 73 | """ 74 | def delete(self, node): 75 | pass 76 | 77 | """ 78 | Finds and returns the maximum value of all the nodes 79 | in the List. 80 | """ 81 | def get_max(self): 82 | pass -------------------------------------------------------------------------------- /doubly_linked_list/test_doubly_linked_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from doubly_linked_list import ListNode 3 | from doubly_linked_list import DoublyLinkedList 4 | 5 | class DoublyLinkedListTests(unittest.TestCase): 6 | def setUp(self): 7 | self.node = ListNode(1) 8 | self.dll = DoublyLinkedList(self.node) 9 | 10 | def test_list_remove_from_tail(self): 11 | self.dll.remove_from_tail() 12 | self.assertIsNone(self.dll.head) 13 | self.assertIsNone(self.dll.tail) 14 | self.assertEqual(len(self.dll), 0) 15 | 16 | self.dll.add_to_tail(33) 17 | self.assertEqual(self.dll.head.value, 33) 18 | self.assertEqual(self.dll.tail.value, 33) 19 | self.assertEqual(len(self.dll), 1) 20 | self.assertEqual(self.dll.remove_from_tail(), 33) 21 | self.assertEqual(len(self.dll), 0) 22 | 23 | self.dll.add_to_tail(68) 24 | self.assertEqual(len(self.dll), 1) 25 | self.assertEqual(self.dll.remove_from_tail(), 68) 26 | self.assertEqual(len(self.dll), 0) 27 | 28 | def test_list_remove_from_head(self): 29 | self.dll.remove_from_head() 30 | self.assertIsNone(self.dll.head) 31 | self.assertIsNone(self.dll.tail) 32 | self.assertEqual(len(self.dll), 0) 33 | 34 | self.dll.add_to_head(2) 35 | self.assertEqual(self.dll.head.value, 2) 36 | self.assertEqual(self.dll.tail.value, 2) 37 | self.assertEqual(len(self.dll), 1) 38 | self.assertEqual(self.dll.remove_from_head(), 2) 39 | self.assertEqual(len(self.dll), 0) 40 | 41 | self.dll.add_to_head(55) 42 | self.assertEqual(len(self.dll), 1) 43 | self.assertEqual(self.dll.remove_from_head(), 55) 44 | self.assertEqual(len(self.dll), 0) 45 | 46 | def test_list_add_to_tail(self): 47 | self.assertEqual(self.dll.tail.value, 1) 48 | self.assertEqual(len(self.dll), 1) 49 | 50 | self.dll.add_to_tail(30) 51 | self.assertEqual(self.dll.tail.prev.value, 1) 52 | self.assertEqual(self.dll.tail.value, 30) 53 | self.assertEqual(len(self.dll), 2) 54 | 55 | self.dll.add_to_tail(20) 56 | self.assertEqual(self.dll.tail.prev.value, 30) 57 | self.assertEqual(self.dll.tail.value, 20) 58 | self.assertEqual(len(self.dll), 3) 59 | 60 | def test_list_add_to_head(self): 61 | self.assertEqual(self.dll.head.value, 1) 62 | 63 | self.dll.add_to_head(10) 64 | self.assertEqual(self.dll.head.value, 10) 65 | self.assertEqual(self.dll.head.next.value, 1) 66 | self.assertEqual(len(self.dll), 2) 67 | 68 | def test_list_move_to_end(self): 69 | self.dll.add_to_head(40) 70 | self.assertEqual(self.dll.tail.value, 1) 71 | self.assertEqual(self.dll.head.value, 40) 72 | 73 | self.dll.move_to_end(self.dll.head) 74 | self.assertEqual(self.dll.tail.value, 40) 75 | self.assertEqual(self.dll.tail.prev.value, 1) 76 | self.assertEqual(len(self.dll), 2) 77 | 78 | self.dll.add_to_tail(4) 79 | self.dll.move_to_end(self.dll.head.next) 80 | self.assertEqual(self.dll.tail.value, 40) 81 | self.assertEqual(self.dll.tail.prev.value, 4) 82 | self.assertEqual(len(self.dll), 3) 83 | 84 | def test_list_move_to_front(self): 85 | self.dll.add_to_tail(3) 86 | self.assertEqual(self.dll.head.value, 1) 87 | self.assertEqual(self.dll.tail.value, 3) 88 | 89 | self.dll.move_to_front(self.dll.tail) 90 | self.assertEqual(self.dll.head.value, 3) 91 | self.assertEqual(self.dll.head.next.value, 1) 92 | self.assertEqual(len(self.dll), 2) 93 | 94 | self.dll.add_to_head(29) 95 | self.dll.move_to_front(self.dll.head.next) 96 | self.assertEqual(self.dll.head.value, 3) 97 | self.assertEqual(self.dll.head.next.value, 29) 98 | self.assertEqual(len(self.dll), 3) 99 | 100 | def test_list_delete(self): 101 | self.dll.delete(self.node) 102 | self.assertIsNone(self.dll.head) 103 | self.assertIsNone(self.dll.tail) 104 | self.assertEqual(len(self.dll), 0) 105 | 106 | self.dll.add_to_tail(1) 107 | self.dll.add_to_head(9) 108 | self.dll.add_to_tail(6) 109 | 110 | self.dll.delete(self.dll.head.next) 111 | self.assertEqual(self.dll.head.value, 9) 112 | self.assertEqual(self.dll.head.next, self.dll.tail) 113 | self.assertEqual(self.dll.tail.value, 6) 114 | 115 | self.dll.delete(self.dll.head) 116 | self.assertEqual(self.dll.head.value, 6) 117 | self.assertEqual(self.dll.tail.value, 6) 118 | self.assertEqual(len(self.dll), 1) 119 | 120 | self.dll.delete(self.dll.head) 121 | self.assertIsNone(self.dll.head) 122 | self.assertIsNone(self.dll.tail) 123 | self.assertEqual(len(self.dll), 0) 124 | 125 | def test_get_max(self): 126 | self.assertEqual(self.dll.get_max(), 1) 127 | self.dll.add_to_tail(100) 128 | self.assertEqual(self.dll.get_max(), 100) 129 | self.dll.add_to_tail(55) 130 | self.assertEqual(self.dll.get_max(), 100) 131 | self.dll.add_to_tail(101) 132 | self.assertEqual(self.dll.get_max(), 101) 133 | 134 | if __name__ == '__main__': 135 | unittest.main() 136 | -------------------------------------------------------------------------------- /heap/README.md: -------------------------------------------------------------------------------- 1 | # Stretch Goal: Heaps 2 | 3 | ## Max Heaps 4 | * Should have the methods `insert`, `delete`, `get_max`, `_bubble_up`, and `_sift_down`. 5 | * `insert` adds the input value into the heap; this method should ensure that the inserted value is in the correct spot in the heap 6 | * `delete` removes and returns the 'topmost' value from the heap; this method needs to ensure that the heap property is maintained after the topmost element has been removed. 7 | * `get_max` returns the maximum value in the heap _in constant time_. 8 | * `get_size` returns the number of elements stored in the heap. 9 | * `_bubble_up` moves the element at the specified index "up" the heap by swapping it with its parent if the parent's value is less than the value at the specified index. 10 | * `_sift_down` grabs the indices of this element's children and determines which child has a larger value. If the larger child's value is larger than the parent's value, the child element is swapped with the parent. 11 | 12 | ![Image of a Heap in Tree form](https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Max-Heap.svg/501px-Max-Heap.svg.png) 13 | 14 | ![Image of a Heap in Array form](https://upload.wikimedia.org/wikipedia/commons/thumb/d/d2/Heap-as-array.svg/603px-Heap-as-array.svg.png) 15 | 16 | ## Generic Heaps 17 | A max heap is pretty useful, but what's even more useful is to have our heap be generic such that the user can define their own priority function and pass it to the heap to use. 18 | 19 | Augment your heap implementation so that it exhibits this behavior. If no comparator function is passed in to the heap constructor, it should default to being a max heap. Also change the name of the `get_max` function to `get_priority`. 20 | 21 | You can test your implementation against the tests in `test_generic_heap.py`. The test expects your augmented heap implementation lives in a file called `generic_heap.py`. Feel free to change the import statement to work with your file structure or copy/paste your implementation into a file with the expected name. -------------------------------------------------------------------------------- /heap/generic_heap.py: -------------------------------------------------------------------------------- 1 | class Heap: 2 | # defaults to a max heap if no comparator is specified 3 | def __init__(self, comparator=lambda x, y: x > y): 4 | self.storage = [] 5 | self.comparator = comparator 6 | 7 | def insert(self, value): 8 | pass 9 | 10 | def delete(self): 11 | pass 12 | 13 | def get_priority(self): 14 | pass 15 | 16 | def get_size(self): 17 | pass 18 | 19 | def _bubble_up(self, index): 20 | pass 21 | 22 | def _sift_down(self, index): 23 | pass 24 | -------------------------------------------------------------------------------- /heap/max_heap.py: -------------------------------------------------------------------------------- 1 | class Heap: 2 | def __init__(self): 3 | self.storage = [] 4 | 5 | def insert(self, value): 6 | pass 7 | 8 | def delete(self): 9 | pass 10 | 11 | def get_max(self): 12 | pass 13 | 14 | def get_size(self): 15 | pass 16 | 17 | def _bubble_up(self, index): 18 | pass 19 | 20 | def _sift_down(self, index): 21 | pass 22 | -------------------------------------------------------------------------------- /heap/test_generic_heap.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import MagicMock 3 | from generic_heap import Heap 4 | 5 | class HeapTests(unittest.TestCase): 6 | def setUp(self): 7 | self.heap = Heap() 8 | 9 | def test_default_heap_insert_works(self): 10 | self.heap.insert(6) 11 | self.heap.insert(8) 12 | self.heap.insert(10) 13 | self.heap.insert(9) 14 | self.heap.insert(1) 15 | self.heap.insert(9) 16 | self.heap.insert(9) 17 | self.heap.insert(5) 18 | self.assertEqual(self.heap.storage, [10, 9, 9, 6, 1, 8, 9, 5]) 19 | 20 | def test_default_get_priority_works(self): 21 | self.heap.insert(6) 22 | self.heap.insert(8) 23 | self.heap.insert(10) 24 | self.heap.insert(9) 25 | self.heap.insert(1) 26 | self.heap.insert(9) 27 | self.heap.insert(9) 28 | self.heap.insert(5) 29 | self.assertEqual(self.heap.get_size(), 8) 30 | self.assertEqual(self.heap.get_priority(), 10) 31 | 32 | def test_default_get_priority_after_delete(self): 33 | self.heap.insert(6) 34 | self.heap.insert(8) 35 | self.heap.insert(10) 36 | self.heap.insert(9) 37 | self.heap.insert(1) 38 | self.heap.insert(9) 39 | self.heap.insert(9) 40 | self.heap.insert(5) 41 | self.heap.delete() 42 | self.assertEqual(self.heap.get_priority(), 9) 43 | self.heap.delete() 44 | self.assertEqual(self.heap.get_priority(), 9) 45 | self.heap.delete() 46 | self.assertEqual(self.heap.get_priority(), 9) 47 | self.heap.delete() 48 | self.assertEqual(self.heap.get_priority(), 8) 49 | self.heap.delete() 50 | self.assertEqual(self.heap.get_priority(), 6) 51 | 52 | def test_default_delete_elements_in_order(self): 53 | self.heap.insert(6) 54 | self.heap.insert(7) 55 | self.heap.insert(5) 56 | self.heap.insert(8) 57 | self.heap.insert(10) 58 | self.heap.insert(1) 59 | self.heap.insert(2) 60 | self.heap.insert(5) 61 | 62 | descending_order = [] 63 | 64 | while self.heap.get_size() > 0: 65 | descending_order.append(self.heap.delete()) 66 | 67 | self.assertEqual(descending_order, [10, 8, 7, 6, 5, 5, 2, 1]) 68 | 69 | def test_custom_heap_insert_works(self): 70 | self.heap = Heap(lambda x, y: x < y) 71 | 72 | self.heap.insert(6) 73 | self.heap.insert(8) 74 | self.heap.insert(10) 75 | self.heap.insert(9) 76 | self.heap.insert(1) 77 | self.heap.insert(9) 78 | self.heap.insert(9) 79 | self.heap.insert(5) 80 | self.assertEqual(self.heap.storage, [1, 5, 9, 6, 8, 10, 9, 9]) 81 | 82 | def test_custom_get_priority_works(self): 83 | self.heap = Heap(lambda x, y: x < y) 84 | 85 | self.heap.insert(6) 86 | self.heap.insert(8) 87 | self.heap.insert(10) 88 | self.heap.insert(9) 89 | self.heap.insert(1) 90 | self.heap.insert(9) 91 | self.heap.insert(9) 92 | self.heap.insert(5) 93 | self.assertEqual(self.heap.get_size(), 8) 94 | self.assertEqual(self.heap.get_priority(), 1) 95 | 96 | def test_custom_get_priority_after_delete(self): 97 | self.heap = Heap(lambda x, y: x < y) 98 | 99 | self.heap.insert(6) 100 | self.heap.insert(8) 101 | self.heap.insert(10) 102 | self.heap.insert(9) 103 | self.heap.insert(1) 104 | self.heap.insert(9) 105 | self.heap.insert(9) 106 | self.heap.insert(5) 107 | self.heap.delete() 108 | self.assertEqual(self.heap.get_priority(), 5) 109 | self.heap.delete() 110 | self.assertEqual(self.heap.get_priority(), 6) 111 | self.heap.delete() 112 | self.assertEqual(self.heap.get_priority(), 8) 113 | self.heap.delete() 114 | self.assertEqual(self.heap.get_priority(), 9) 115 | self.heap.delete() 116 | self.assertEqual(self.heap.get_priority(), 9) 117 | 118 | def test_custom_delete_elements_in_order(self): 119 | self.heap = Heap(lambda x, y: x < y) 120 | 121 | self.heap.insert(6) 122 | self.heap.insert(7) 123 | self.heap.insert(5) 124 | self.heap.insert(8) 125 | self.heap.insert(10) 126 | self.heap.insert(1) 127 | self.heap.insert(2) 128 | self.heap.insert(5) 129 | 130 | ascending_order = [] 131 | 132 | while self.heap.get_size() > 0: 133 | ascending_order.append(self.heap.delete()) 134 | 135 | self.assertEqual(ascending_order, [1, 2, 5, 5, 6, 7, 8, 10]) 136 | 137 | def test_bubble_up_was_called(self): 138 | self.heap._bubble_up = MagicMock() 139 | self.heap.insert(5) 140 | self.assertTrue(self.heap._bubble_up.called) 141 | 142 | def test_sift_down_was_called(self): 143 | self.heap._sift_down = MagicMock() 144 | self.heap.insert(10) 145 | self.heap.insert(11) 146 | self.heap.delete() 147 | self.assertTrue(self.heap._sift_down.called) 148 | 149 | 150 | if __name__ == '__main__': 151 | unittest.main() 152 | -------------------------------------------------------------------------------- /heap/test_max_heap.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from unittest.mock import MagicMock 3 | from max_heap import Heap 4 | 5 | 6 | class HeapTests(unittest.TestCase): 7 | def setUp(self): 8 | self.heap = Heap() 9 | 10 | def test_heap_insert_works(self): 11 | self.heap.insert(6) 12 | self.heap.insert(8) 13 | self.heap.insert(10) 14 | self.heap.insert(9) 15 | self.heap.insert(1) 16 | self.heap.insert(9) 17 | self.heap.insert(9) 18 | self.heap.insert(5) 19 | self.assertEqual(self.heap.storage, [10, 9, 9, 6, 1, 8, 9, 5]) 20 | 21 | def test_get_max_works(self): 22 | self.heap.insert(6) 23 | self.heap.insert(8) 24 | self.heap.insert(10) 25 | self.heap.insert(9) 26 | self.heap.insert(1) 27 | self.heap.insert(9) 28 | self.heap.insert(9) 29 | self.heap.insert(5) 30 | self.assertEqual(self.heap.get_size(), 8) 31 | self.assertEqual(self.heap.get_max(), 10) 32 | 33 | def test_get_max_after_delete(self): 34 | self.heap.insert(6) 35 | self.heap.insert(8) 36 | self.heap.insert(10) 37 | self.heap.insert(9) 38 | self.heap.insert(1) 39 | self.heap.insert(9) 40 | self.heap.insert(9) 41 | self.heap.insert(5) 42 | self.heap.delete() 43 | self.assertEqual(self.heap.get_max(), 9) 44 | self.heap.delete() 45 | self.assertEqual(self.heap.get_max(), 9) 46 | self.heap.delete() 47 | self.assertEqual(self.heap.get_max(), 9) 48 | self.heap.delete() 49 | self.assertEqual(self.heap.get_max(), 8) 50 | self.heap.delete() 51 | self.assertEqual(self.heap.get_max(), 6) 52 | 53 | def test_delete_elements_in_order(self): 54 | self.heap.insert(6) 55 | self.heap.insert(7) 56 | self.heap.insert(5) 57 | self.heap.insert(8) 58 | self.heap.insert(10) 59 | self.heap.insert(1) 60 | self.heap.insert(2) 61 | self.heap.insert(5) 62 | 63 | descending_order = [] 64 | 65 | while self.heap.get_size() > 0: 66 | descending_order.append(self.heap.delete()) 67 | 68 | self.assertEqual(descending_order, [10, 8, 7, 6, 5, 5, 2, 1]) 69 | 70 | def test_bubble_up_was_called(self): 71 | self.heap._bubble_up = MagicMock() 72 | self.heap.insert(5) 73 | self.assertTrue(self.heap._bubble_up.called) 74 | 75 | def test_sift_down_was_called(self): 76 | self.heap._sift_down = MagicMock() 77 | self.heap.insert(10) 78 | self.heap.insert(11) 79 | self.heap.delete() 80 | self.assertTrue(self.heap._sift_down.called) 81 | 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /lru_cache/lru_cache.py: -------------------------------------------------------------------------------- 1 | class LRUCache: 2 | """ 3 | Our LRUCache class keeps track of the max number of nodes it 4 | can hold, the current number of nodes it is holding, a doubly- 5 | linked list that holds the key-value entries in the correct 6 | order, as well as a storage dict that provides fast access 7 | to every node stored in the cache. 8 | """ 9 | def __init__(self, limit=10): 10 | pass 11 | 12 | """ 13 | Retrieves the value associated with the given key. Also 14 | needs to move the key-value pair to the end of the order 15 | such that the pair is considered most-recently used. 16 | Returns the value associated with the key or None if the 17 | key-value pair doesn't exist in the cache. 18 | """ 19 | def get(self, key): 20 | pass 21 | 22 | """ 23 | Adds the given key-value pair to the cache. The newly- 24 | added pair should be considered the most-recently used 25 | entry in the cache. If the cache is already at max capacity 26 | before this entry is added, then the oldest entry in the 27 | cache needs to be removed to make room. Additionally, in the 28 | case that the key already exists in the cache, we simply 29 | want to overwrite the old value associated with the key with 30 | the newly-specified value. 31 | """ 32 | def set(self, key, value): 33 | pass 34 | -------------------------------------------------------------------------------- /lru_cache/test_lru_cache.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from lru_cache import LRUCache 3 | 4 | 5 | class CacheTests(unittest.TestCase): 6 | def setUp(self): 7 | self.cache = LRUCache(3) 8 | 9 | def test_cache_overwrite_appropriately(self): 10 | self.cache.set('item1', 'a') 11 | self.cache.set('item2', 'b') 12 | self.cache.set('item3', 'c') 13 | 14 | self.cache.set('item2', 'z') 15 | 16 | self.assertEqual(self.cache.get('item1'), 'a') 17 | self.assertEqual(self.cache.get('item2'), 'z') 18 | 19 | def test_cache_insertion_and_retrieval(self): 20 | self.cache.set('item1', 'a') 21 | self.cache.set('item2', 'b') 22 | self.cache.set('item3', 'c') 23 | 24 | self.assertEqual(self.cache.get('item1'), 'a') 25 | self.cache.set('item4', 'd') 26 | 27 | self.assertEqual(self.cache.get('item1'), 'a') 28 | self.assertEqual(self.cache.get('item3'), 'c') 29 | self.assertEqual(self.cache.get('item4'), 'd') 30 | self.assertIsNone(self.cache.get('item2')) 31 | 32 | def test_cache_nonexistent_retrieval(self): 33 | self.assertIsNone(self.cache.get('nonexistent')) 34 | 35 | 36 | if __name__ == '__main__': 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /queue/queue.py: -------------------------------------------------------------------------------- 1 | """ 2 | A queue is a data structure whose primary purpose is to store and 3 | return elements in First In First Out order. 4 | 5 | 1. Implement the Queue class using an array as the underlying storage structure. 6 | Make sure the Queue tests pass. 7 | 2. Re-implement the Queue class, this time using the linked list implementation 8 | as the underlying storage structure. 9 | Make sure the Queue tests pass. 10 | 3. What is the difference between using an array vs. a linked list when 11 | implementing a Queue? 12 | 13 | Stretch: What if you could only use instances of your Stack class to implement the Queue? 14 | What would that look like? How many Stacks would you need? Try it! 15 | """ 16 | class Queue: 17 | def __init__(self): 18 | self.size = 0 19 | # self.storage = ? 20 | 21 | def __len__(self): 22 | pass 23 | 24 | def enqueue(self, value): 25 | pass 26 | 27 | def dequeue(self): 28 | pass 29 | -------------------------------------------------------------------------------- /queue/test_queue.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from queue import Queue 3 | 4 | class QueueTests(unittest.TestCase): 5 | def setUp(self): 6 | self.q = Queue() 7 | 8 | def test_len_returns_0_for_empty_queue(self): 9 | self.assertEqual(len(self.q), 0) 10 | 11 | def test_len_returns_correct_length_after_enqueue(self): 12 | self.assertEqual(len(self.q), 0) 13 | self.q.enqueue(2) 14 | self.assertEqual(len(self.q), 1) 15 | self.q.enqueue(4) 16 | self.assertEqual(len(self.q), 2) 17 | self.q.enqueue(6) 18 | self.q.enqueue(8) 19 | self.q.enqueue(10) 20 | self.q.enqueue(12) 21 | self.q.enqueue(14) 22 | self.q.enqueue(16) 23 | self.q.enqueue(18) 24 | self.assertEqual(len(self.q), 9) 25 | 26 | def test_empty_dequeue(self): 27 | self.assertIsNone(self.q.dequeue()) 28 | self.assertEqual(len(self.q), 0) 29 | 30 | def test_dequeue_respects_order(self): 31 | self.q.enqueue(100) 32 | self.q.enqueue(101) 33 | self.q.enqueue(105) 34 | self.assertEqual(self.q.dequeue(), 100) 35 | self.assertEqual(len(self.q), 2) 36 | self.assertEqual(self.q.dequeue(), 101) 37 | self.assertEqual(len(self.q), 1) 38 | self.assertEqual(self.q.dequeue(), 105) 39 | self.assertEqual(len(self.q), 0) 40 | self.assertIsNone(self.q.dequeue()) 41 | self.assertEqual(len(self.q), 0) 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /singly_linked_list/singly_linked_list.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloominstituteoftechnology/Data-Structures/dbd57ddcb9097bbe8a6e595da107f1b1b965d1b8/singly_linked_list/singly_linked_list.py -------------------------------------------------------------------------------- /singly_linked_list/test_singly_linked_list.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from singly_linked_list import LinkedList 3 | 4 | class LinkedListTests(unittest.TestCase): 5 | def setUp(self): 6 | self.list = LinkedList() 7 | 8 | def test_add_to_tail(self): 9 | self.list.add_to_tail(1) 10 | self.assertEqual(self.list.tail.value, 1) 11 | self.assertEqual(self.list.head.value, 1) 12 | self.list.add_to_tail(2) 13 | self.assertEqual(self.list.tail.value, 2) 14 | self.assertEqual(self.list.head.value, 1) 15 | 16 | def test_remove_head(self): 17 | self.list.add_to_tail(10) 18 | self.list.add_to_tail(20) 19 | self.assertEqual(self.list.remove_head(), 10) 20 | self.assertEqual(self.list.remove_head(), 20) 21 | 22 | self.list.add_to_tail(10) 23 | self.assertEqual(self.list.remove_head(), 10) 24 | self.assertIsNone(self.list.head) 25 | self.assertIsNone(self.list.tail) 26 | self.assertIsNone(self.list.remove_head()) 27 | 28 | def test_remove_tail(self): 29 | self.list.add_to_tail(30) 30 | self.list.add_to_tail(40) 31 | self.assertEqual(self.list.remove_tail(), 40) 32 | self.assertEqual(self.list.remove_tail(), 30) 33 | 34 | self.list.add_to_tail(100) 35 | self.assertEqual(self.list.remove_tail(), 100) 36 | self.assertIsNone(self.list.head) 37 | self.assertIsNone(self.list.tail) 38 | self.assertIsNone(self.list.remove_tail()) 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /stack/stack.py: -------------------------------------------------------------------------------- 1 | """ 2 | A stack is a data structure whose primary purpose is to store and 3 | return elements in Last In First Out order. 4 | 5 | 1. Implement the Stack class using an array as the underlying storage structure. 6 | Make sure the Stack tests pass. 7 | 2. Re-implement the Stack class, this time using the linked list implementation 8 | as the underlying storage structure. 9 | Make sure the Stack tests pass. 10 | 3. What is the difference between using an array vs. a linked list when 11 | implementing a Stack? 12 | """ 13 | class Stack: 14 | def __init__(self): 15 | self.size = 0 16 | # self.storage = ? 17 | 18 | def __len__(self): 19 | pass 20 | 21 | def push(self, value): 22 | pass 23 | 24 | def pop(self): 25 | pass 26 | -------------------------------------------------------------------------------- /stack/test_stack.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from stack import Stack 3 | 4 | class QueueTests(unittest.TestCase): 5 | def setUp(self): 6 | self.stack = Stack() 7 | 8 | def test_len_returns_0_for_empty_stack(self): 9 | self.assertEqual(len(self.stack), 0) 10 | 11 | def test_len_returns_correct_length_after_push(self): 12 | self.assertEqual(len(self.stack), 0) 13 | self.stack.push(2) 14 | self.assertEqual(len(self.stack), 1) 15 | self.stack.push(4) 16 | self.assertEqual(len(self.stack), 2) 17 | self.stack.push(6) 18 | self.stack.push(8) 19 | self.stack.push(10) 20 | self.stack.push(12) 21 | self.stack.push(14) 22 | self.stack.push(16) 23 | self.stack.push(18) 24 | self.assertEqual(len(self.stack), 9) 25 | 26 | def test_empty_pop(self): 27 | self.assertIsNone(self.stack.pop()) 28 | self.assertEqual(len(self.stack), 0) 29 | 30 | def test_pop_respects_order(self): 31 | self.stack.push(100) 32 | self.stack.push(101) 33 | self.stack.push(105) 34 | self.assertEqual(self.stack.pop(), 105) 35 | self.assertEqual(len(self.stack), 2) 36 | self.assertEqual(self.stack.pop(), 101) 37 | self.assertEqual(len(self.stack), 1) 38 | self.assertEqual(self.stack.pop(), 100) 39 | self.assertEqual(len(self.stack), 0) 40 | self.assertIsNone(self.stack.pop()) 41 | self.assertEqual(len(self.stack), 0) 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest.main() 46 | --------------------------------------------------------------------------------