diff --git a/README.md b/README.md index 8b5d358e8..7468701de 100644 --- a/README.md +++ b/README.md @@ -30,11 +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, which is provided, 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. - -_You may not use a Python List in your implementation of the `append` method (except for the stretch goal)_ - -*Stretch Goal*: Another method of implementing a ring buffer uses an array (Python List) instead of a linked list. What are the advantages and disadvantages of using this method? What disadvantage normally found in arrays is overcome with this arrangement? +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: @@ -66,16 +62,13 @@ buffer.get() # should return ['d', 'e', 'f'] 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? - -*You may not use the built in Python list, set, or dictionary in your solution for this problem. However, you can and should use the provided `duplicates` list to return your solution.* - -(Hint: You might try importing a data structure you built during the week) +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²). +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? #### Task 3. Reverse a Linked List -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` reverse the contents of the list. +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`. For example, ``` @@ -94,14 +87,12 @@ While credit will be given for a functional solution, only optimal solutions wil ### Rubric -| OBJECTIVE | TASK | 1 - DOES NOT MEET Expectations | 2 - MEETS Expectations | 3 - EXCEEDS Expectations | SCORE | -| ---------- | ----- | ------- | ------- | ------- | -- | -| _Student should be able to construct a queue and stack and justify the decision to use a linked list instead of an array._ | 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 3 or more tests | Solution in `ring_buffer.py` runs, but may have one or two logical errors; passes at least 7/9 tests | Solution in `ring_buffer.py` has no syntax or logical errors and passes 9/9 tests | | -| _Student should be able to construct a binary search tree class that can perform basic operations with O(log n) runtime._ | Task 2. Runtime Optimization | Student does NOT correctly identify the runtime of the starter code in `name.py` and optimize it to run in under 6 seconds | 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 of O(n log n) or better | Student does BOTH correctly identify the runtime of the starter code in `name.py` and optimizes it to run in under 6 seconds, with a solution of 0(n log n) or better | | -| _Student should be able to construct a linked list and compare the runtime of operations to an array to make the optimal choice between them._ | 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 it has a runtime of O(n) or better | | -| _Student should be able to write code that utilizes Python Collections_ | [STRETCH] Optimize code from Task 2, given the constraint that **only standard Python collections** may be used to store names | Level 1 solution provided | Level 2 solution provided | Level 3 solution provided | | -| **FINAL SCORE** | _(3 tasks + 1 STRETCH goal)_ | **0-5** | **6-9** | **10-12** | | +| 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. | | #### 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 **average of at least 2** for all items on the rubric. +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/reverse/reverse.py b/reverse/reverse.py index 95ed9d1d4..6116252d1 100644 --- a/reverse/reverse.py +++ b/reverse/reverse.py @@ -1,47 +1,42 @@ class Node: - def __init__(self, value=None, next_node=None): - # the value at this linked list node - self.value = value - # reference to the next node in the list - self.next_node = next_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_value(self): + return self.value - def get_next(self): - return self.next_node + def get_next(self): + return self.next_node - def set_next(self, new_next): - # set this node's next_node reference to the passed in node - self.next_node = new_next + def set_next(self, new_next): + self.next_node = new_next class LinkedList: - def __init__(self): - # reference to the head of the list - 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 - # get a reference to the node we're currently at; update this as we traverse the list - current = self.head - # check to see if we're at a valid node - while current: - # return True if the current value we're looking at matches our target value - if current.get_value() == value: - return True - # update our current node to the current node's next node - current = current.get_next() - # if we've gotten here, then the target node isn't in our list - return False - - def reverse_list(self): - # TO BE COMPLETED - pass \ No newline at end of file + 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 index 50cbfe3f3..0199b1a20 100644 --- a/reverse/test_reverse.py +++ b/reverse/test_reverse.py @@ -20,12 +20,12 @@ def test_contains(self): self.assertFalse(self.list.contains(1000)) def test_empty_reverse(self): - self.list.reverse_list() + 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.reverse_list(self.list.head, None) self.assertEqual(self.list.head.value, 1) def test_longer_reverse(self): @@ -35,13 +35,10 @@ def test_longer_reverse(self): 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.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/doubly_linked_list.py b/ring_buffer/doubly_linked_list.py deleted file mode 100644 index 4f445a868..000000000 --- a/ring_buffer/doubly_linked_list.py +++ /dev/null @@ -1,138 +0,0 @@ -"""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.value = value - self.prev = prev - self.next = next - - """Wrap the given value in a ListNode and insert it - after this node. Note that this node could already - have a next node it is point to.""" - def insert_after(self, value): - current_next = self.next - self.next = ListNode(value, self, current_next) - if current_next: - current_next.prev = self.next - - """Wrap the given value in a ListNode and insert it - before this node. Note that this node could already - have a previous node it is point to.""" - def insert_before(self, value): - current_prev = self.prev - self.prev = ListNode(value, current_prev, self) - if current_prev: - current_prev.next = self.prev - - """Rearranges this ListNode's previous and next pointers - accordingly, effectively deleting this ListNode.""" - def delete(self): - if self.prev: - self.prev.next = self.next - if self.next: - self.next.prev = self.prev - - -"""Our doubly-linked list class. It holds references to -the list's head and tail nodes.""" - - -class DoublyLinkedList: - def __init__(self, node=None): - self.head = node - self.tail = node - self.length = 1 if node is not None else 0 - - 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): - 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): - 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): - 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.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): - 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): - if node is self.head: - return - value = node.value - self.delete(node) - 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): - if node is self.tail: - return - value = node.value - self.delete(node) - self.add_to_tail(value) - - """Removes a node from the list and handles cases where - the node was the head or the tail""" - def delete(self, node): - self.length -= 1 - if self.head is self.tail: - self.head = None - self.tail = None - elif self.head is node: - self.head = node.next - node.delete() - elif self.tail is node: - self.tail = node.prev - node.delete() - else: - node.delete() - - """Returns the highest value currently in the list""" - def get_max(self): - max_value = self.head.value - current = self.head - while current is not None: - if current.value > max_value: - max_value = current.value - current = current.next - - return max_value diff --git a/ring_buffer/ring_buffer.py b/ring_buffer/ring_buffer.py index ef88f0d6b..37e9fb0dd 100644 --- a/ring_buffer/ring_buffer.py +++ b/ring_buffer/ring_buffer.py @@ -1,27 +1,4 @@ -from doubly_linked_list import DoublyLinkedList - - class RingBuffer: - def __init__(self, capacity): - self.capacity = capacity - self.current = None - self.storage = DoublyLinkedList() - - def append(self, item): - pass - - def get(self): - # Note: This is the only [] allowed - list_buffer_contents = [] - - # TODO: Your code here - - return list_buffer_contents - -# ----------------Stretch Goal------------------- - - -class ArrayRingBuffer: def __init__(self, capacity): pass @@ -29,4 +6,4 @@ def append(self, item): pass def get(self): - pass + pass \ No newline at end of file diff --git a/ring_buffer/test_ring_buffer.py b/ring_buffer/test_ring_buffer.py index 3eb2bda1f..31e1f247d 100644 --- a/ring_buffer/test_ring_buffer.py +++ b/ring_buffer/test_ring_buffer.py @@ -1,78 +1,52 @@ import unittest -from ring_buffer import RingBuffer, ArrayRingBuffer - +from ring_buffer import RingBuffer class RingBufferTests(unittest.TestCase): def setUp(self): - self.buffer = RingBuffer(5) - self.buffer_2 = 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(self.buffer.storage.length, 0) + 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(self.buffer.storage.length, 4) - self.assertEqual(self.buffer.get(), ['a', 'b', 'c', 'd']) - self.buffer.append('e') - self.assertEqual(self.buffer.storage.length, 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(self.buffer.storage.length, 5) self.assertEqual(self.buffer.get(), ['f', 'b', 'c', 'd', 'e']) - self.buffer.append('g') - self.buffer.append('h') - self.buffer.append('i') - self.assertEqual(self.buffer.storage.length, 5) - self.assertEqual(self.buffer.get(), ['f', 'g', 'h', 'i', 'e']) - - self.buffer.append('j') - self.buffer.append('k') - self.assertEqual(self.buffer.get(), ['k', 'g', 'h', 'i', 'j']) - - for i in range(50): - self.buffer_2.append(i) - self.assertEqual(self.buffer_2.get(), [45, 46, 47, 48, 49]) - - -class ArrayRingBufferTests(unittest.TestCase): - def setUp(self): - self.buffer = ArrayRingBuffer(5) - self.buffer_2 = ArrayRingBuffer(5) - - def test__array_ring_buffer(self): - self.assertEqual(len(self.buffer.storage), 5) - + 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.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']) - self.buffer.append('f') - self.assertEqual(len(self.buffer.storage), 5) - self.assertEqual(self.buffer.get(), ['f', 'b', 'c', 'd', 'e']) - 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_2.append(i) - self.assertEqual(self.buffer_2.get(), [45, 46, 47, 48, 49]) + self.buffer.append(i) + self.assertEqual(self.buffer.get(), [45, 46, 47, 48, 49]) if __name__ == '__main__': - unittest.main() + unittest.main() \ No newline at end of file