diff --git a/Data_Structures_Answers.md b/Data_Structures_Answers.md deleted file mode 100644 index e39545492..000000000 --- a/Data_Structures_Answers.md +++ /dev/null @@ -1,18 +0,0 @@ -Add your answers to the questions below. - -1. What is the runtime complexity of your ring buffer's `append` method? - -2. What is the space complexity of your ring buffer's `append` function? - -3. What is the runtime complexity of your ring buffer's `get` method? - -4. What is the space complexity of your ring buffer's `get` method? - - -5. What is the runtime complexity of the provided code in `names.py`? - -6. What is the space complexity of the provided code in `names.py`? - -7. What is the runtime complexity of your optimized code in `names.py`? - -8. What is the space complexity of your optimized code in `names.py`? diff --git a/README.md b/README.md index 30881a7fb..7468701de 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ In this week's Sprint you implemented some classic and fundamental data structur This is an individual assessment. All work must be your own. Your Challenge score is a measure of your ability to work independently using the material covered throughout this sprint. You need to demonstrate proficiency in the concepts and objectives that were introduced and that you practiced in the preceding days. -You are not allowed to collaborate during the Sprint Challenge. However, you are encouraged to follow the twenty-minute rule and seek support from your PM and Instructor in your cohort help channel on Slack. Your submitted work reflects your proficiency in the concepts and topics that were covered this week. +You are not allowed to collaborate during the Sprint Challenge. However, you are encouraged to follow the twenty-minute rule and seek support from your TL and Instructor in your cohort help channel on Slack. Your submitted work reflects your proficiency in the concepts and topics that were covered this week. You have three hours to complete this Sprint Challenge. Plan your time accordingly. @@ -22,7 +22,7 @@ This Sprint Challenge is split into three parts: 1. Implement a data structure called a ring buffer (more details below) 2. Optimizing some inefficient code -3. Analyzing time and space complexities from parts 1 and 2 +3. Reversing the contents of a singly linked list ### Minimum Viable Product @@ -30,7 +30,7 @@ This Sprint Challenge is split into three parts: A ring buffer is a non-growable buffer with a fixed size. When the ring buffer is full and a new element is inserted, the oldest element in the ring buffer is overwritten with the newest element. This kind of data structure is very useful for use cases such as storing logs and history information, where you typically want to store information up until it reaches a certain age, after which you don't care about it anymore and don't mind seeing it overwritten by newer data. -Implement this behavior in the RingBuffer class. RingBuffer has two methods, `append` and `get`. The `append` method adds elements to the buffer. The `get` method returns all of the elements in the buffer in a list in their given order. It should not return any `None` values in the list even if they are present in the ring buffer. +Implement this behavior in the RingBuffer class. RingBuffer has two methods, `append` and `get`. The `append` method adds the given element to the buffer. The `get` method returns all of the elements in the buffer in a list in their given order. It should not return any `None` values in the list even if they are present in the ring buffer. For example: @@ -58,43 +58,41 @@ buffer.get() # should return ['d', 'e', 'f'] #### Task 2. Runtime Optimization -Navigate into the `names` directory. Here you will find two text files containing 10,000 names each, along with a program `names.py` that compares the two files and prints out duplicate name entries. Try running the code with `python3 names.py`. Be patient because it might take a while: approximately six seconds on my laptop. What is the runtime complexity of this code? - -Six seconds is an eternity so you've been tasked with speeding up the code. Can you get the runtime to under a second? Under one hundredth of a second? - -(Hint: You might try importing a data structure you built during the week) - -#### Task 3. Analyze Some Runtimes - -Open up the `Data_Structures_Answers.md` file. This is where you'll jot down your answers for the runtimes of the functions/data structures you just implemented. Also include the runtime and space complexities of the original code and your optimized solution from `names.py`. - -### Stretch Problems - -1. Say your code from `names.py` is to run on an embedded computer with very limited RAM. Because of this, memory is extremely constrained and you are only allowed to store names in arrays (i.e. Python lists). How would you go about optimizing the code under these conditions? Try it out and compare your solution to the original runtime. (If this solution is less efficient than your original solution, include both and label the strech solution with a comment) +***!Important!*** If you are running this using PowerShell by clicking on the green play button, you will get an error that `names1.txt` is not found. To resolve this, run it, get the error, then `cd` into the `names` directory in the `python` terminal that opens in VSCode. +Navigate into the `names` directory. Here you will find two text files containing 10,000 names each, along with a program `names.py` that compares the two files and prints out duplicate name entries. Try running the code with `python3 names.py`. Be patient because it might take a while: approximately six seconds on my laptop. What is the runtime complexity of this code? -### Rubric +Six seconds is an eternity so you've been tasked with speeding up the code. Your goal is to use one of the data structures we built out over the course of this week in order to optimize and improve on the runtime so that it's more efficient than O(n²). -#### Ring Buffer +A follow-up question to think about: _*once you've used one of the data structures we implemented over the course of the week*_ in order to improve the runtime of the implementation, what other data structures (including ones from Python's standard library) are also possible candidates for improving the runtime of the implementation? -- Ring buffer implementation passes the tests: 10 points total +#### Task 3. Reverse a Linked List -#### Names +Inside of the `reverse` directory, you'll find a basic implementation of a Singly Linked List. _Without_ making it a Doubly Linked List (adding a tail attribute), complete the `reverse_list()` function within `reverse/reverse.py`. -- Optimize with an O(n log n) runtime solution: 8 points total -- Optimize with an O(n) runtime solution: 10 points total +For example, +``` +1->2->3->None +``` +would become... +``` +3->2->1->None +``` -#### Complexity +While credit will be given for a functional solution, only optimal solutions will earn a ***3*** on this task. -- One point each: 8 points total +#### Stretch -#### Stretch +* Say your code from `names.py` is to run on an embedded computer with very limited RAM. Because of this, memory is extremely constrained and you are only allowed to store names in arrays (i.e. Python lists). How would you go about optimizing the code under these conditions? Try it out and compare your solution to the original runtime. (If this solution is less efficient than your original solution, include both and label the strech solution with a comment) -- `names.py` is optimized with sub-quadratic runtime complexity and tightly constrained linear space complexity: 4 points +### Rubric +| TASK | 1 - DOES NOT MEET Expectations | 2 - MEETS Expectations | 3 - EXCEEDS Expectations | SCORE | +| ----- | ------- | ------- | ------- | -- | +| Task 1. Implement a Ring Buffer Data Structure | Solution in `ring_buffer.py` DOES NOT run OR it runs but has multiple logical errors, failing 2 or more tests. | Solution in `ring_buffer.py` runs, but may have one or two logical errors; passes at least 5/6 tests (Note that each function in the test file that begins with `test` is a test). | Solution in `ring_buffer.py` has no syntax or logical errors and passes all tests (Note that each function in the test file that begins with `test` is a test). | | +| Task 2. Runtime Optimization | Student does NOT correctly identify the runtime of the starter code in `name.py` and is not able to optimize it to run in under 6 seconds using a data structure that was implemented during the week. | Student does not identify the runtime of the starter code in `name.py`, but optimizes it to run in under 6 seconds, with a solution that exhibits the appropriate runtime, using a data structure that was implemented during the week | Student does BOTH correctly identify the runtime of the starter code in `name.py` and optimizes it to run in under 6 seconds, with an appropriate runtime using a data structure that was implemented during the week. | | +| Task 3. Reverse the contents of a Singly Linked List | Student's solution in `reverse.py` is failing one or more tests. | Student's solution in `reverse.py` is able to correctly print out the contents of the Linked List in reverse order, passing all tests, BUT, the runtime of their solution is not optimal (requires looping through the list more than once). | Student's solution in `reverse.py` is able to correctly print out the contents of the Linked List in reverse order, passing all tests AND exhibits an appropriate runtime. | | -#### Grading -* *3*: 28+ -* *2*: 20-27 -* *1*: 0-19 +#### Passing the Sprint +Score ranges for a 1, 2, and 3 are shown in the rubric above. For a student to have _passed_ a sprint challenge, they need to earn an **at least 2** for all items on the rubric. diff --git a/names/names.py b/names/names.py index 586e8393e..ea158997f 100644 --- a/names/names.py +++ b/names/names.py @@ -10,7 +10,9 @@ names_2 = f.read().split("\n") # List containing 10000 names f.close() -duplicates = [] +duplicates = [] # Return the list of duplicates in this data structure + +# Replace the nested for loops below with your improvements for name_1 in names_1: for name_2 in names_2: if name_1 == name_2: @@ -20,3 +22,7 @@ print (f"{len(duplicates)} duplicates:\n\n{', '.join(duplicates)}\n\n") print (f"runtime: {end_time - start_time} seconds") +# ---------- Stretch Goal ----------- +# Python has built-in tools that allow for a very efficient approach to this problem +# What's the best time you can accomplish? Thare are no restrictions on techniques or data +# structures, but you may not import any additional libraries that you did not write yourself. diff --git a/reverse/reverse.py b/reverse/reverse.py new file mode 100644 index 000000000..6116252d1 --- /dev/null +++ b/reverse/reverse.py @@ -0,0 +1,42 @@ +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: + def __init__(self): + self.head = None + + def add_to_head(self, value): + node = Node(value) + + if self.head is not None: + node.set_next(self.head) + + self.head = node + + def contains(self, value): + if not self.head: + return False + + current = self.head + + while current: + if current.get_value() == value: + return True + + current = current.get_next() + + return False + + def reverse_list(self, node, prev): + pass diff --git a/reverse/test_reverse.py b/reverse/test_reverse.py new file mode 100644 index 000000000..0199b1a20 --- /dev/null +++ b/reverse/test_reverse.py @@ -0,0 +1,44 @@ +import unittest +from reverse import LinkedList + +class LinkedListTests(unittest.TestCase): + def setUp(self): + self.list = LinkedList() + + def test_add_to_head(self): + self.list.add_to_head(1) + self.assertEqual(self.list.head.value, 1) + self.list.add_to_head(2) + self.assertEqual(self.list.head.value, 2) + + def test_contains(self): + self.list.add_to_head(1) + self.list.add_to_head(2) + self.list.add_to_head(10) + self.assertTrue(self.list.contains(2)) + self.assertTrue(self.list.contains(10)) + self.assertFalse(self.list.contains(1000)) + + def test_empty_reverse(self): + self.list.reverse_list(self.list.head, None) + self.assertEqual(self.list.head, None) + + def test_single_reverse(self): + self.list.add_to_head(1) + self.list.reverse_list(self.list.head, None) + self.assertEqual(self.list.head.value, 1) + + def test_longer_reverse(self): + self.list.add_to_head(1) + self.list.add_to_head(2) + self.list.add_to_head(3) + self.list.add_to_head(4) + self.list.add_to_head(5) + self.assertEqual(self.list.head.value, 5) + self.list.reverse_list(self.list.head, None) + self.assertEqual(self.list.head.value, 1) + self.assertEqual(self.list.head.get_next().value, 2) + self.assertEqual(self.list.head.get_next().get_next().value, 3) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/ring_buffer/ring_buffer.py b/ring_buffer/ring_buffer.py index 35fd33cac..37e9fb0dd 100644 --- a/ring_buffer/ring_buffer.py +++ b/ring_buffer/ring_buffer.py @@ -1,11 +1,9 @@ class RingBuffer: - def __init__(self, capacity): - self.capacity = capacity - self.current = 0 - self.storage = [None]*capacity + def __init__(self, capacity): + pass - def append(self, item): - pass + def append(self, item): + pass - def get(self): - pass \ No newline at end of file + def get(self): + pass \ No newline at end of file diff --git a/ring_buffer/test_ring_buffer.py b/ring_buffer/test_ring_buffer.py index 6508c10af..31e1f247d 100644 --- a/ring_buffer/test_ring_buffer.py +++ b/ring_buffer/test_ring_buffer.py @@ -3,32 +3,50 @@ class RingBufferTests(unittest.TestCase): def setUp(self): - self.buffer = RingBuffer(5) + self.capacity = 5 + self.buffer = RingBuffer(self.capacity) + + def test_new_buffer_has_appropriate_capacity(self): + self.assertEqual(self.buffer.capacity, self.capacity) - def test_ring_buffer(self): - self.assertEqual(len(self.buffer.storage), 5) + def test_adding_one_element_to_buffer(self): + self.buffer.append('a') + self.assertEqual(self.buffer.get(), ['a']) + def test_filling_buffer_to_capacity(self): self.buffer.append('a') self.buffer.append('b') self.buffer.append('c') self.buffer.append('d') - self.assertEqual(len(self.buffer.storage), 5) - self.assertEqual(self.buffer.get(), ['a', 'b', 'c', 'd']) - self.buffer.append('e') - self.assertEqual(len(self.buffer.storage), 5) self.assertEqual(self.buffer.get(), ['a', 'b', 'c', 'd', 'e']) + def test_adding_one_element_to_full_buffer(self): + self.buffer.append('a') + self.buffer.append('b') + self.buffer.append('c') + self.buffer.append('d') + self.buffer.append('e') self.buffer.append('f') - self.assertEqual(len(self.buffer.storage), 5) self.assertEqual(self.buffer.get(), ['f', 'b', 'c', 'd', 'e']) + def test_adding_many_elements_to_full_buffer(self): + self.buffer.append('a') + self.buffer.append('b') + self.buffer.append('c') + self.buffer.append('d') + self.buffer.append('e') + self.buffer.append('f') self.buffer.append('g') self.buffer.append('h') self.buffer.append('i') - self.assertEqual(len(self.buffer.storage), 5) self.assertEqual(self.buffer.get(), ['f', 'g', 'h', 'i', 'e']) + def test_adding_50_elements_to_buffer(self): + for i in range(50): + self.buffer.append(i) + + self.assertEqual(self.buffer.get(), [45, 46, 47, 48, 49]) if __name__ == '__main__': unittest.main() \ No newline at end of file