From 5ee360b2b564699f2ecf546e9eef87b34ed7b92e Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 12:56:53 +0100 Subject: [PATCH 01/12] feat: implement Node class for doubly linked list - Define Node class to store value - Add pointers for next and previous nodes --- Sprint-2/implement_linked_list/linked_list.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index e69de29..edc9b8f 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -0,0 +1,9 @@ +class Node: + """ + Represents a single person in line. + Stores a value and links to the people ahead and behind. + """ + def __init__(self, value): + self.value = value + self.next = None # The person ahead + self.previous = None # The person behind From c99312813b9a1f69b547cc9470ec4f64d386e8eb Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:00:54 +0100 Subject: [PATCH 02/12] feat: add LinkedList class and initialize head and tail pointers - Define the LinkedList class in linked_list.py. - Implement the __init__ method to track the head and tail of the list. - Initialize head and tail to None for an empty list state. --- Sprint-2/implement_linked_list/linked_list.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index edc9b8f..d4dc52e 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -7,3 +7,12 @@ def __init__(self, value): self.value = value self.next = None # The person ahead self.previous = None # The person behind + + +class LinkedList: + """ + Manages the line by keeping track of the Head and the Tail. + """ + def __init__(self): + self.head = None + self.tail = None \ No newline at end of file From 47ed0155276a969860a56ee0cf858154a9703f07 Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:06:16 +0100 Subject: [PATCH 03/12] feat: implement push_head method in LinkedList - Added the logic to create a new node and insert it at the front of the list. - Handled pointer updates for both empty and non-empty list states. - Returns the new node to allow for future removal/referencing. --- Sprint-2/implement_linked_list/linked_list.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index d4dc52e..fb7d728 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -15,4 +15,23 @@ class LinkedList: """ def __init__(self): self.head = None - self.tail = None \ No newline at end of file + self.tail = None + + def push_head(self, value): + """Adds a new node to the front of the list.""" + new_node = Node(value) + + # Logic 2: If the list is empty + if self.head is None: + self.head = new_node + self.tail = new_node + # Logic 3: If the list is not empty + else: + new_node.next = self.head # New person grabs old head's hand + self.head.previous = new_node # Old head reaches back to new person + self.head = new_node # New person is now the head + + # Logic 4: Return the node so we can reference it later + return new_node + + \ No newline at end of file From a15f3520735b67dd95b31c0d21b4b757c3d9fa66 Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:13:01 +0100 Subject: [PATCH 04/12] Implement pop_tail method in LinkedList - Added logic to remove the last node from the list. - Handles edge cases for empty lists and single-node lists. - Updates tail pointers and returns the value of the removed node. --- Sprint-2/implement_linked_list/linked_list.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index fb7d728..6200ad9 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -34,4 +34,23 @@ def push_head(self, value): # Logic 4: Return the node so we can reference it later return new_node - \ No newline at end of file + def pop_tail(self): + """Removes the last node and returns its value.""" + # Safety check: if list is empty + if self.tail is None: + return None + + # Logic 1: Store the value to return later + value_to_return = self.tail.value + + # Logic 2: If there is only one person in line + if self.head == self.tail: + self.head = None + self.tail = None + # Logic 3: If there are more people + else: + self.tail = self.tail.previous # Move tail back one + self.tail.next = None # New tail lets go of the old tail + + # Logic 4: Return the name/value + return value_to_return \ No newline at end of file From eaaa1f9c8951be11345b1eae29ce0c35342aa21f Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:15:06 +0100 Subject: [PATCH 05/12] feat: implement remove method in LinkedList - Added logic to remove a specific node from any position. - Included pointer updates for head and tail edge cases. - Added neighbor re-linking to maintain list integrity. --- Sprint-2/implement_linked_list/linked_list.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 6200ad9..bcfd767 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -53,4 +53,29 @@ def pop_tail(self): self.tail.next = None # New tail lets go of the old tail # Logic 4: Return the name/value - return value_to_return \ No newline at end of file + return value_to_return + + def remove(self, node): + """Removes a specific node from anywhere in the list.""" + if node is None: + return + + # Logic 3: Update Head/Tail pointers if the node is at the ends + if node == self.head: + self.head = node.next + + if node == self.tail: + self.tail = node.previous + + # Logic 1 & 2: Re-link the neighbors + # If there's someone behind, they skip over 'node' to grab 'node.next' + if node.previous is not None: + node.previous.next = node.next + + # If there's someone ahead, they reach back to grab 'node.previous' + if node.next is not None: + node.next.previous = node.previous + + # Optional: Clean up the removed node's own pointers + node.next = None + node.previous = None \ No newline at end of file From dd21292ba6fe7a8d7c648a816dc3046d06f87535 Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:30:02 +0100 Subject: [PATCH 06/12] test: add unit tests for node removal and edge cases - Added test_remove_middle to verify pointer re-linking when deleting from the center. - Added test_pop_empty_list to ensure the program doesn't crash on empty lists. - Added test_remove_none to verify handling of null input. --- .../implement_linked_list/linked_list_test.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list_test.py b/Sprint-2/implement_linked_list/linked_list_test.py index d59d9c5..fb9a02c 100644 --- a/Sprint-2/implement_linked_list/linked_list_test.py +++ b/Sprint-2/implement_linked_list/linked_list_test.py @@ -34,6 +34,34 @@ def test_remove_tail(self): self.assertIsNone(b.next) self.assertIsNone(b.previous) + def test_remove_middle(self): + l = LinkedList() + l.push_head("tail_node") + middle = l.push_head("middle_node") + l.push_head("head_node") + + l.remove(middle) + + self.assertEqual(l.head.value, "head_node") + self.assertEqual(l.tail.value, "tail_node") + self.assertEqual(l.head.next.value, "tail_node") + self.assertEqual(l.tail.previous.value, "head_node") + + def test_pop_empty_list(self): + l = LinkedList() + # If the list is empty, popping the tail should return None + # instead of crashing the program with an error. + result = l.pop_tail() + + self.assertIsNone(result) + + def test_remove_none(self): + l = LinkedList() + l.push_head("a") + # This should just do nothing and not crash + l.remove(None) + + self.assertEqual(l.pop_tail(), "a") if __name__ == "__main__": unittest.main() From 8935eb0f48831e77f70d01be41cd0ce207004221 Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:38:12 +0100 Subject: [PATCH 07/12] docs: add CHANGES-MADE.md documenting Linked List implementation - Documented core logic for Node and LinkedList classes - Explained testing strategy for edge cases (empty lists, middle removal) - Traded extra memory space to achieve O(1) time (constant speed). --- Sprint-2/implement_linked_list/CHANGES-MADE.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Sprint-2/implement_linked_list/CHANGES-MADE.md diff --git a/Sprint-2/implement_linked_list/CHANGES-MADE.md b/Sprint-2/implement_linked_list/CHANGES-MADE.md new file mode 100644 index 0000000..8db7fdd --- /dev/null +++ b/Sprint-2/implement_linked_list/CHANGES-MADE.md @@ -0,0 +1,16 @@ +# Changes Made: Linked List Implementation + +## 1. Core Logic (`linked_list.py`) +- **Node Class**: Created a `Node` class to act as the building block of the list. Each node stores a `value` and has pointers for `next` and `previous`, allowing for bi-directional traversal. +- **LinkedList Class**: + - Implemented `push_head(value)`: Adds a new node to the beginning of the list in **O(1)** time. + - Implemented `pop_tail()`: Removes and returns the value from the end of the list in **O(1)** time. Includes logic for empty lists and single-node lists. + - Implemented `remove(node)`: Removes a specific node from the list and re-wires the surrounding nodes' pointers. Handles edge cases where the node is the current `head` or `tail`. + +## 2. Testing & Verification (`linked_list_test.py`) +- **Middle Removal**: Added `test_remove_middle` to verify that when a node in the center of the list is removed, the nodes ahead and behind it correctly link to each other. +- **Edge Case - Empty Pop**: Added `test_pop_empty_list` to ensure the program returns `None` rather than crashing when attempting to remove from an empty list. +- **Edge Case - Null Removal**: Added `test_remove_none` to verify the `remove` method handles `None` inputs gracefully. + +## 3. Technical Trade-offs +- **Space vs. Time**: Used a Doubly Linked List structure, trading slightly more memory (for the `previous` pointer) to achieve faster removal and tail-access times **`O(1)`**. \ No newline at end of file From 2f0b1b75850e0dc36d1e706eec1db49b5840edd2 Mon Sep 17 00:00:00 2001 From: iswat Date: Tue, 30 Jun 2026 13:41:46 +0100 Subject: [PATCH 08/12] docs: update terminology in CHANGES-MADE.md - Changed "Linked List" to "Doubly Linked List" to accurately reflect the bi-directional implementation. - Updated header for better technical clarity. --- Sprint-2/implement_linked_list/CHANGES-MADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement_linked_list/CHANGES-MADE.md b/Sprint-2/implement_linked_list/CHANGES-MADE.md index 8db7fdd..020399f 100644 --- a/Sprint-2/implement_linked_list/CHANGES-MADE.md +++ b/Sprint-2/implement_linked_list/CHANGES-MADE.md @@ -1,4 +1,4 @@ -# Changes Made: Linked List Implementation +# Changes Made: Doubly Linked List Implementation ## 1. Core Logic (`linked_list.py`) - **Node Class**: Created a `Node` class to act as the building block of the list. Each node stores a `value` and has pointers for `next` and `previous`, allowing for bi-directional traversal. From b756034243576fc8a356e119e08c7307e2bbf83e Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 3 Jul 2026 11:50:39 +0100 Subject: [PATCH 09/12] perf: implement __slots__ in Node class to optimize memory - Add __slots__ to prevent per-instance dictionary overhead - Optimize space complexity for large-scale list usage --- Sprint-2/implement_linked_list/linked_list.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index bcfd767..b1e53fb 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -3,6 +3,8 @@ class Node: Represents a single person in line. Stores a value and links to the people ahead and behind. """ + __slots__ = ['value', 'next', 'previous'] + def __init__(self, value): self.value = value self.next = None # The person ahead From eb716e687ee89ea44350ebcedbb62f30e9bef48b Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 3 Jul 2026 12:03:03 +0100 Subject: [PATCH 10/12] refactor: remove redundant comments and metaphors from LinkedList - Remove numbered logic comments (# Logic 1, 2, 3) to reduce noise - Remove non-technical metaphors (e.g., "New person") in favor of self-documenting code - Clean up method bodies to follow professional clean code standards --- Sprint-2/implement_linked_list/linked_list.py | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index b1e53fb..29d2fe9 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -4,11 +4,11 @@ class Node: Stores a value and links to the people ahead and behind. """ __slots__ = ['value', 'next', 'previous'] - + def __init__(self, value): self.value = value - self.next = None # The person ahead - self.previous = None # The person behind + self.next = None + self.previous = None class LinkedList: @@ -23,38 +23,33 @@ def push_head(self, value): """Adds a new node to the front of the list.""" new_node = Node(value) - # Logic 2: If the list is empty if self.head is None: self.head = new_node self.tail = new_node - # Logic 3: If the list is not empty + else: - new_node.next = self.head # New person grabs old head's hand - self.head.previous = new_node # Old head reaches back to new person - self.head = new_node # New person is now the head + new_node.next = self.head + self.head.previous = new_node + self.head = new_node - # Logic 4: Return the node so we can reference it later return new_node def pop_tail(self): """Removes the last node and returns its value.""" - # Safety check: if list is empty + if self.tail is None: return None - # Logic 1: Store the value to return later value_to_return = self.tail.value - # Logic 2: If there is only one person in line if self.head == self.tail: self.head = None self.tail = None - # Logic 3: If there are more people + else: self.tail = self.tail.previous # Move tail back one self.tail.next = None # New tail lets go of the old tail - # Logic 4: Return the name/value return value_to_return def remove(self, node): @@ -62,22 +57,17 @@ def remove(self, node): if node is None: return - # Logic 3: Update Head/Tail pointers if the node is at the ends if node == self.head: self.head = node.next if node == self.tail: self.tail = node.previous - # Logic 1 & 2: Re-link the neighbors - # If there's someone behind, they skip over 'node' to grab 'node.next' if node.previous is not None: node.previous.next = node.next - # If there's someone ahead, they reach back to grab 'node.previous' if node.next is not None: node.next.previous = node.previous - # Optional: Clean up the removed node's own pointers node.next = None node.previous = None \ No newline at end of file From 6e4d93adb5b44495cf3c61b5114de4cf777a8623 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 3 Jul 2026 12:16:38 +0100 Subject: [PATCH 11/12] refactor: add type hints to Node and LinkedList classes - Implement typing (Any, Optional) for parameters and return values - Add forward references for Node pointers --- Sprint-2/implement_linked_list/linked_list.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 29d2fe9..1d3de26 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -1,3 +1,5 @@ +from typing import Any, Optional + class Node: """ Represents a single person in line. @@ -5,10 +7,10 @@ class Node: """ __slots__ = ['value', 'next', 'previous'] - def __init__(self, value): + def __init__(self, value: Any): self.value = value - self.next = None - self.previous = None + self.next: Optional['Node'] = None + self.previous: Optional['Node'] = None class LinkedList: @@ -16,10 +18,10 @@ class LinkedList: Manages the line by keeping track of the Head and the Tail. """ def __init__(self): - self.head = None - self.tail = None + self.head: Optional[Node] = None + self.tail: Optional[Node] = None - def push_head(self, value): + def push_head(self, value: Any) -> Node: """Adds a new node to the front of the list.""" new_node = Node(value) @@ -34,7 +36,7 @@ def push_head(self, value): return new_node - def pop_tail(self): + def pop_tail(self) -> Optional[Any]: """Removes the last node and returns its value.""" if self.tail is None: @@ -52,7 +54,7 @@ def pop_tail(self): return value_to_return - def remove(self, node): + def remove(self, node: Optional[Node]) -> None: """Removes a specific node from anywhere in the list.""" if node is None: return From 1e507e23d9b5c5a6745c70e429d649553ed4ff4b Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 3 Jul 2026 12:21:30 +0100 Subject: [PATCH 12/12] refactor: simplify pop_tail by reusing remove method - Replace manual pointer logic in pop_tail with a call to self.remove() - Adhere to DRY (Don't Repeat Yourself) principles - Reduce code duplication and improve maintainability --- Sprint-2/implement_linked_list/linked_list.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Sprint-2/implement_linked_list/linked_list.py b/Sprint-2/implement_linked_list/linked_list.py index 1d3de26..559f0b1 100644 --- a/Sprint-2/implement_linked_list/linked_list.py +++ b/Sprint-2/implement_linked_list/linked_list.py @@ -44,13 +44,7 @@ def pop_tail(self) -> Optional[Any]: value_to_return = self.tail.value - if self.head == self.tail: - self.head = None - self.tail = None - - else: - self.tail = self.tail.previous # Move tail back one - self.tail.next = None # New tail lets go of the old tail + self.remove(self.tail) return value_to_return